Completed
Branch uploads (08d8c9)
by Stephanie
04:20
created

FrmEntryValidate::is_akismet_spam()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 3
nc 4
nop 1
dl 0
loc 4
rs 9.2
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 View Code Duplication
		if ( ! is_numeric( $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...
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::is_akismet_spam( $values ) ) {
275
			if ( self::is_akismet_enabled_for_user( $values['form_id'] ) ) {
276
				$errors['spam'] = __( 'Your entry appears to be spam!', 'formidable' );
277
			}
278
	    }
279
280
    	if ( self::blacklist_check( $values ) ) {
281
            $errors['spam'] = __( 'Your entry appears to be spam!', 'formidable' );
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
302
    	if ( empty( $mod_keys ) ) {
303
    		return false;
304
    	}
305
306
    	$content = FrmEntriesHelper::entry_array_to_string($values);
307
308
		if ( empty($content) ) {
309
		    return false;
310
		}
311
312
		$content = strtolower( $content );
313
		$content_without_html = wp_strip_all_tags( $content );
314
		$ip = FrmAppHelper::get_ip_address();
315
		$user_agent = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
316
317
    	$words = explode( "\n", $mod_keys );
318
319
    	foreach ( (array) $words as $word ) {
320
    		$word = trim( $word );
321
322
			if ( empty( $word ) ) {
323
				continue;
324
			}
325
326
			// Do some escaping magic so that '#' chars in the
327
			// spam words don't break things:
328
			$word = preg_quote( $word, '#' );
329
330
			$pattern = "#$word#i";
331
332
			$in_content = preg_match( $pattern, $content ) || preg_match( $pattern, $content_without_html );
333
			$blacklist_user = preg_match( $pattern, $ip ) || preg_match( $pattern, $user_agent );
334
			if ( $in_content || $blacklist_user ) {
335
    			return true;
336
    		}
337
    	}
338
339
    	return false;
340
    }
341
342
    /**
343
     * Check entries for spam
344
     *
345
     * @return boolean true if is spam
346
     */
347
    public static function akismet( $values ) {
348
	    $content = FrmEntriesHelper::entry_array_to_string( $values );
349
350
		if ( empty( $content ) ) {
351
		    return false;
352
		}
353
354
        $datas = array();
355
        self::parse_akismet_array( $datas, $content );
356
357
		$query_string = '';
358
		foreach ( $datas as $key => $data ) {
359
			$query_string .= $key . '=' . urlencode( stripslashes( $data ) ) . '&';
360
			unset( $key, $data );
361
		}
362
363
        $response = Akismet::http_post($query_string, 'comment-check');
364
365
		return ( is_array( $response ) && $response[1] == 'true' );
366
    }
367
368
    /**
369
     * @since 2.0
370
     * @param string $content
371
     */
372
    private  static function parse_akismet_array( &$datas, $content ) {
373
        $datas['blog'] = FrmAppHelper::site_url();
374
        $datas['user_ip'] = preg_replace( '/[^0-9., ]/', '', FrmAppHelper::get_ip_address() );
375
		$datas['user_agent'] = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
376
		$datas['referrer'] = isset( $_SERVER['HTTP_REFERER'] ) ? FrmAppHelper::get_server_value( 'HTTP_REFERER' ) : false;
377
        $datas['comment_type'] = 'formidable';
378
        $datas['comment_content'] = $content;
379
380
        if ( $permalink = get_permalink() ) {
381
            $datas['permalink'] = $permalink;
382
        }
383
384
        foreach ( $_SERVER as $key => $value ) {
385
			if ( ! in_array( $key, array( 'HTTP_COOKIE', 'HTTP_COOKIE2', 'PHP_AUTH_PW' ) ) && is_string( $value ) ) {
386
				$datas[ $key ] = wp_strip_all_tags( $value );
387
            } else {
388
				$datas[ $key ] = '';
389
            }
390
391
            unset($key, $value);
392
        }
393
    }
394
}
395