Completed
Push — master ( 93790a...73acfe )
by Jamie
04:17
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)?:\/\/([\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' ) {
158
			return;
159
		}
160
161
		$pattern = self::phone_format( $field );
162
163 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...
164
			$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
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_akismet_spam( $values ) ) {
276
			if ( self::is_akismet_enabled_for_user( $values['form_id'] ) ) {
277
				$errors['spam'] = __( 'Your entry appears to be spam!', 'formidable' );
278
			}
279
	    }
280
281
    	if ( self::blacklist_check( $values ) ) {
282
            $errors['spam'] = __( 'Your entry appears to be spam!', 'formidable' );
283
    	}
284
    }
285
286
	private static function is_akismet_spam( $values ) {
287
		global $wpcom_api_key;
288
		return ( is_callable('Akismet::http_post') && ( get_option('wordpress_api_key') || $wpcom_api_key ) && self::akismet( $values ) );
289
	}
290
291
	private static function is_akismet_enabled_for_user( $form_id ) {
292
		$form = FrmForm::getOne( $form_id );
293
		return ( isset( $form->options['akismet'] ) && ! empty( $form->options['akismet'] ) && ( $form->options['akismet'] != 'logged' || ! is_user_logged_in() ) );
294
	}
295
296
    public static function blacklist_check( $values ) {
297
        if ( ! apply_filters('frm_check_blacklist', true, $values) ) {
298
            return false;
299
        }
300
301
    	$mod_keys = trim( get_option( 'blacklist_keys' ) );
302
303
    	if ( empty( $mod_keys ) ) {
304
    		return false;
305
    	}
306
307
    	$content = FrmEntriesHelper::entry_array_to_string($values);
308
309
		if ( empty($content) ) {
310
		    return false;
311
		}
312
313
    	$words = explode( "\n", $mod_keys );
314
315
    	foreach ( (array) $words as $word ) {
316
    		$word = trim( $word );
317
318
    		if ( empty($word) ) {
319
    			continue;
320
    		}
321
322
    		if ( preg_match('#' . preg_quote( $word, '#' ) . '#', $content) ) {
323
    			return true;
324
    		}
325
    	}
326
327
    	return false;
328
    }
329
330
    /**
331
     * Check entries for spam
332
     *
333
     * @return boolean true if is spam
334
     */
335
    public static function akismet( $values ) {
336
	    $content = FrmEntriesHelper::entry_array_to_string( $values );
337
338
		if ( empty( $content ) ) {
339
		    return false;
340
		}
341
342
        $datas = array();
343
        self::parse_akismet_array( $datas, $content );
344
345
		$query_string = '';
346
		foreach ( $datas as $key => $data ) {
347
			$query_string .= $key . '=' . urlencode( stripslashes( $data ) ) . '&';
348
			unset( $key, $data );
349
		}
350
351
        $response = Akismet::http_post($query_string, 'comment-check');
352
353
		return ( is_array( $response ) && $response[1] == 'true' );
354
    }
355
356
    /**
357
     * @since 2.0
358
     * @param string $content
359
     */
360
    private  static function parse_akismet_array( &$datas, $content ) {
361
        $datas['blog'] = FrmAppHelper::site_url();
362
        $datas['user_ip'] = preg_replace( '/[^0-9., ]/', '', FrmAppHelper::get_ip_address() );
363
		$datas['user_agent'] = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
364
		$datas['referrer'] = isset( $_SERVER['HTTP_REFERER'] ) ? FrmAppHelper::get_server_value( 'HTTP_REFERER' ) : false;
365
        $datas['comment_type'] = 'formidable';
366
        $datas['comment_content'] = $content;
367
368
        if ( $permalink = get_permalink() ) {
369
            $datas['permalink'] = $permalink;
370
        }
371
372
        foreach ( $_SERVER as $key => $value ) {
373
			if ( ! in_array( $key, array( 'HTTP_COOKIE', 'HTTP_COOKIE2', 'PHP_AUTH_PW' ) ) && is_string( $value ) ) {
374
				$datas[ $key ] = wp_strip_all_tags( $value );
375
            } else {
376
				$datas[ $key ] = '';
377
            }
378
379
            unset($key, $value);
380
        }
381
    }
382
}
383