Passed
Push — master ( 6b8ca8...3384db )
by Paul
04:57
created

ValidateReview::validateHoneyPot()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 0
cts 6
cp 0
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 4
nc 3
nop 1
crap 12
1
<?php
2
3
namespace GeminiLabs\SiteReviews\Modules\Validator;
4
5
use GeminiLabs\SiteReviews\Database\OptionManager;
6
use GeminiLabs\SiteReviews\Defaults\ValidateReviewDefaults;
7
use GeminiLabs\SiteReviews\Helper;
8
use GeminiLabs\SiteReviews\Modules\Akismet;
9
use GeminiLabs\SiteReviews\Modules\Blacklist;
10
use GeminiLabs\SiteReviews\Modules\Session;
11
use GeminiLabs\SiteReviews\Modules\Validator;
12
13
class ValidateReview
14
{
15
	const VALIDATION_RULES = [
16
		'content' => 'required|min:0',
17
		'email'   => 'required|email|min:5',
18
		'name'    => 'required',
19
		'rating'  => 'required|numeric|between:1,5',
20
		'terms'   => 'accepted',
21
		'title'   => 'required',
22
	];
23
24
	/**
25
	 * @var string|void
26
	 */
27
	public $error;
28
29
	/**
30
	 * @var bool
31
	 */
32
	public $recaptchaIsUnset = false;
33
34
	/**
35
	 * @var array
36
	 */
37
	public $request;
38
39
	/**
40
	 * @return array|string
41
	 */
42
	public function validate( array $request )
43
	{
44
		$this->form_id = $this->request['form_id']; // @todo verify this exists
0 ignored issues
show
Bug Best Practice introduced by
The property form_id does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
45
		$this->request = $this->validateRequest( $request );
46
		$this->validateCustom();
47
		$this->validateHoneyPot();
0 ignored issues
show
Bug introduced by
The call to GeminiLabs\SiteReviews\M...iew::validateHoneyPot() has too few arguments starting with request. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

47
		$this->/** @scrutinizer ignore-call */ 
48
         validateHoneyPot();

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
48
		$this->validateBlacklist();
49
		$this->validateAkismet();
50
		$this->validateRecaptcha();
0 ignored issues
show
Bug introduced by
The call to GeminiLabs\SiteReviews\M...ew::validateRecaptcha() has too few arguments starting with request. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

50
		$this->/** @scrutinizer ignore-call */ 
51
         validateRecaptcha();

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
51
	}
52
53
	/**
54
	 * @return array
55
	 */
56
	public function validateRequest( array $request )
57
	{
58
		if( !$this->isRequestValid( $request )) {
59
			$this->error = __( 'Please fix the submission errors.', 'site-reviews' );
60
			return $request;
61
		}
62
		if( empty( $request['title'] )) {
63
			$request['title'] = __( 'No Title', 'site-reviews' );
64
		}
65
		return array_merge( glsr( ValidateReviewDefaults::class )->defaults(), $request );
66
	}
67
68
	/**
69
	 * @return array
70
	 */
71
	protected function getValidationRules( array $request )
72
	{
73
		$rules = array_intersect_key(
74
			apply_filters( 'site-reviews/validation/rules', static::VALIDATION_RULES ),
75
			array_flip( array_merge(
76
				['rating','terms'],
77
				glsr( OptionManager::class )->get( 'settings.reviews-form.required', [] )
78
			))
79
		);
80
		$excluded = isset( $request['excluded'] )
81
			? json_decode( $request['excluded'] )
82
			: [];
83
		return array_diff_key( $rules, array_flip( $excluded ));
84
	}
85
86
	/**
87
	 * @return bool
88
	 */
89
	protected function isRecaptchaResponseValid()
90
	{
91
		$integration = glsr( OptionManager::class )->get( 'settings.reviews-form.recaptcha.integration' );
92
		if( !$integration ) {
93
			return true;
94
		}
95
		$recaptchaResponse = filter_input( INPUT_POST, 'g-recaptcha-response' );
96
		if( empty( $recaptchaResponse ))return; //if response is empty we need to return null
97
		if( $integration == 'custom' ) {
98
			return $this->isRecaptchaValid( $recaptchaResponse );
99
		}
100
		if( $integration == 'invisible-recaptcha' ) {
101
			return boolval( apply_filters( 'google_invre_is_valid_request_filter', true ));
102
		}
103
		return false;
104
	}
105
106
	/**
107
	 * @return bool
108
	 */
109
	protected function isRecaptchaValid( $recaptchaResponse )
110
	{
111
		$endpoint = add_query_arg([
112
			'remoteip' => glsr( Helper::class )->getIpAddress(),
113
			'response' => $recaptchaResponse,
114
			'secret' => glsr( OptionManager::class )->get( 'settings.reviews-form.recaptcha.secret' ),
115
		], 'https://www.google.com/recaptcha/api/siteverify' );
116
		$response = json_decode( wp_remote_retrieve_body( wp_remote_get( $endpoint )));
0 ignored issues
show
Bug introduced by
It seems like wp_remote_get($endpoint) can also be of type WP_Error; however, parameter $response of wp_remote_retrieve_body() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

116
		$response = json_decode( wp_remote_retrieve_body( /** @scrutinizer ignore-type */ wp_remote_get( $endpoint )));
Loading history...
117
		if( !empty( $response->success )) {
118
			return boolval( $response->success );
119
		}
120
		$errorCodes = [
121
			'missing-input-secret' => 'The secret parameter is missing.',
122
			'invalid-input-secret' => 'The secret parameter is invalid or malformed.',
123
			'missing-input-response' => 'The response parameter is missing.',
124
			'invalid-input-response' => 'The response parameter is invalid or malformed.',
125
			'bad-request' => 'The request is invalid or malformed.',
126
		];
127
		foreach( $response->{'error-codes'} as $error ) {
128
			glsr_log()->error( 'reCAPTCHA: '.$errorCodes[$error] );
129
		}
130
		return false;
131
	}
132
133
	/**
134
	 * @return bool
135
	 */
136
	protected function isRequestValid( array $request )
137
	{
138
		$rules = $this->getValidationRules( $request );
139
		$errors = glsr( Validator::class )->validate( $request, $rules );
140
		if( empty( $errors )) {
141
			return true;
142
		}
143
		$this->setSession( 'errors', $errors );
0 ignored issues
show
Bug introduced by
The method setSession() does not exist on GeminiLabs\SiteReviews\M...alidator\ValidateReview. Did you maybe mean setSessionValues()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

143
		$this->/** @scrutinizer ignore-call */ 
144
         setSession( 'errors', $errors );

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
144
		$this->setSession( 'values', $request );
145
		return false;
146
	}
147
148
	/**
149
	 * @param string $type
150
	 * @param mixed $value
151
	 * @param string $loggedMessage
152
	 * @return void
153
	 */
154
	protected function setSessionValues( $type, $value, $loggedMessage = '' )
155
	{
156
		glsr( Session::class )->set( $this->form_id.$type, $value );
157
		if( !empty( $loggedMessage )) {
158
			glsr_log()->warning( $loggedMessage );
159
			glsr_log()->warning( $this->request );
0 ignored issues
show
Bug introduced by
$this->request of type array is incompatible with the type string expected by parameter $message of GeminiLabs\SiteReviews\Modules\Logger::warning(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

159
			glsr_log()->warning( /** @scrutinizer ignore-type */ $this->request );
Loading history...
160
		}
161
	}
162
163
	/**
164
	 * @return void
165
	 */
166
	protected function validateAkismet()
167
	{
168
		if( !empty( $this->error ))return;
169
		if( !glsr( Akismet::class )->isSpam( $this->request ))return;
170
		$this->setSessionValues( 'errors', [], 'Akismet caught a spam submission:' );
171
		$this->error = __( 'Your review cannot be submitted at this time. Please try again later.', 'site-reviews' );
172
	}
173
174
	/**
175
	 * @return void
176
	 */
177
	protected function validateBlacklist()
178
	{
179
		if( !empty( $this->error ))return;
180
		if( !glsr( Blacklist::class )->isBlacklisted( $this->request ))return;
181
		$blacklistAction = glsr( OptionManager::class )->get( 'settings.reviews-form.blacklist.action' );
182
		if( $blacklistAction == 'unapprove' ) {
183
			$this->request['blacklisted'] = true;
184
		}
185
		else if( $blacklistAction == 'reject' ) {
186
			$this->setSessionValues( 'errors', [], 'Blacklisted submission detected:' );
187
			$this->error = __( 'Your review cannot be submitted at this time.', 'site-reviews' );
188
		}
189
	}
190
191
	/**
192
	 * @return void
193
	 */
194
	protected function validateCustom()
195
	{
196
		if( !empty( $this->error ))return;
197
		$validated = apply_filters( 'site-reviews/validate/review/submission', true, $this->request );
198
		if( $validated === true )return;
199
		$this->setSessionValues( 'errors', [] );
200
		$this->setSessionValues( 'values', $this->request );
201
		$this->error = is_string( $validated )
202
			? $validated
203
			: __( 'The review submission failed. Please notify the site administrator.', 'site-reviews' );
204
	}
205
206
	/**
207
	 * @return void
208
	 */
209
	protected function validateHoneyPot( array $request )
210
	{
211
		if( !empty( $this->error ))return;
212
		if( empty( $request['gotcha'] ))return;
213
		$this->setSessionValues( 'errors', [], 'The Honeypot caught a bad submission:' );
214
		$this->error = __( 'The review submission failed. Please notify the site administrator.', 'site-reviews' );
215
	}
216
217
	/**
218
	 * @return void
219
	 */
220
	protected function validateRecaptcha( array $request )
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

220
	protected function validateRecaptcha( /** @scrutinizer ignore-unused */ array $request )

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
221
	{
222
		if( !empty( $this->error ))return;
223
		$isValid = $this->isRecaptchaResponseValid();
224
		if( is_null( $isValid )) {
0 ignored issues
show
introduced by
The condition is_null($isValid) is always false.
Loading history...
225
			$this->setSessionValues( 'recaptcha', true );
226
			$this->recaptchaIsUnset = true;
227
		}
228
		else if( !$isValid ) {
229
			$this->setSessionValues( 'errors', [] );
230
			$this->setSessionValues( 'recaptcha', 'reset' );
231
			$this->error = __( 'The reCAPTCHA verification failed. Please notify the site administrator.', 'site-reviews' );
232
		}
233
	}
234
}
235