Passed
Push — master ( 78f140...0de278 )
by Paul
04:49
created

ValidateReview::isRecaptchaValid()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 26
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 26
ccs 0
cts 26
cp 0
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 20
nc 4
nop 1
crap 20
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['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();
48
		$this->validateBlacklist();
49
		$this->validateAkismet();
50
		$this->validateRecaptcha();
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|null
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 )) {
97
			return null; //if response is empty we need to return null
98
		}
99
		if( $integration == 'custom' ) {
100
			return $this->isRecaptchaValid( $recaptchaResponse );
101
		}
102
		if( $integration == 'invisible-recaptcha' ) {
103
			return boolval( apply_filters( 'google_invre_is_valid_request_filter', true ));
104
		}
105
		return false;
106
	}
107
108
	/**
109
	 * @return bool
110
	 */
111
	protected function isRecaptchaValid( $recaptchaResponse )
112
	{
113
		$endpoint = add_query_arg([
114
			'remoteip' => glsr( Helper::class )->getIpAddress(),
115
			'response' => $recaptchaResponse,
116
			'secret' => glsr( OptionManager::class )->get( 'settings.reviews-form.recaptcha.secret' ),
117
		], 'https://www.google.com/recaptcha/api/siteverify' );
118
		if( is_wp_error( $response = wp_remote_get( $endpoint ))) {
119
			glsr_log()->error( $response->get_error_message() );
120
			return false;
121
		}
122
		$response = json_decode( wp_remote_retrieve_body( $response ));
1 ignored issue
show
Bug introduced by
It seems like $response 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

122
		$response = json_decode( wp_remote_retrieve_body( /** @scrutinizer ignore-type */ $response ));
Loading history...
123
		if( !empty( $response->success )) {
124
			return boolval( $response->success );
125
		}
126
		$errorCodes = [
127
			'missing-input-secret' => 'The secret parameter is missing.',
128
			'invalid-input-secret' => 'The secret parameter is invalid or malformed.',
129
			'missing-input-response' => 'The response parameter is missing.',
130
			'invalid-input-response' => 'The response parameter is invalid or malformed.',
131
			'bad-request' => 'The request is invalid or malformed.',
132
		];
133
		foreach( $response->{'error-codes'} as $error ) {
134
			glsr_log()->error( 'reCAPTCHA: '.$errorCodes[$error] );
135
		}
136
		return false;
137
	}
138
139
	/**
140
	 * @return bool
141
	 */
142
	protected function isRequestValid( array $request )
143
	{
144
		$rules = $this->getValidationRules( $request );
145
		$errors = glsr( Validator::class )->validate( $request, $rules );
146
		if( empty( $errors )) {
147
			return true;
148
		}
149
		$this->setSessionValues( 'errors', $errors );
150
		$this->setSessionValues( 'values', $request );
151
		return false;
152
	}
153
154
	/**
155
	 * @param string $type
156
	 * @param mixed $value
157
	 * @param string $loggedMessage
158
	 * @return void
159
	 */
160
	protected function setSessionValues( $type, $value, $loggedMessage = '' )
161
	{
162
		glsr( Session::class )->set( $this->form_id.$type, $value );
163
		if( !empty( $loggedMessage )) {
164
			glsr_log()->warning( $loggedMessage );
165
			glsr_log()->warning( $this->request );
166
		}
167
	}
168
169
	/**
170
	 * @return void
171
	 */
172
	protected function validateAkismet()
173
	{
174
		if( !empty( $this->error ))return;
175
		if( !glsr( Akismet::class )->isSpam( $this->request ))return;
176
		$this->setSessionValues( 'errors', [], 'Akismet caught a spam submission:' );
177
		$this->error = __( 'Your review cannot be submitted at this time. Please try again later.', 'site-reviews' );
178
	}
179
180
	/**
181
	 * @return void
182
	 */
183
	protected function validateBlacklist()
184
	{
185
		if( !empty( $this->error ))return;
186
		if( !glsr( Blacklist::class )->isBlacklisted( $this->request ))return;
187
		$blacklistAction = glsr( OptionManager::class )->get( 'settings.reviews-form.blacklist.action' );
188
		if( $blacklistAction == 'unapprove' ) {
189
			$this->request['blacklisted'] = true;
190
		}
191
		else if( $blacklistAction == 'reject' ) {
192
			$this->setSessionValues( 'errors', [], 'Blacklisted submission detected:' );
193
			$this->error = __( 'Your review cannot be submitted at this time.', 'site-reviews' );
194
		}
195
	}
196
197
	/**
198
	 * @return void
199
	 */
200
	protected function validateCustom()
201
	{
202
		if( !empty( $this->error ))return;
203
		$validated = apply_filters( 'site-reviews/validate/review/submission', true, $this->request );
204
		if( $validated === true )return;
205
		$this->setSessionValues( 'errors', [] );
206
		$this->setSessionValues( 'values', $this->request );
207
		$this->error = is_string( $validated )
208
			? $validated
209
			: __( 'The review submission failed. Please notify the site administrator.', 'site-reviews' );
210
	}
211
212
	/**
213
	 * @return void
214
	 */
215
	protected function validateHoneyPot()
216
	{
217
		if( !empty( $this->error ))return;
218
		if( empty( $this->request['gotcha'] ))return;
219
		$this->setSessionValues( 'errors', [], 'The Honeypot caught a bad submission:' );
220
		$this->error = __( 'The review submission failed. Please notify the site administrator.', 'site-reviews' );
221
	}
222
223
	/**
224
	 * @return void
225
	 */
226
	protected function validateRecaptcha()
227
	{
228
		if( !empty( $this->error ))return;
229
		$isValid = $this->isRecaptchaResponseValid();
230
		if( is_null( $isValid )) {
231
			$this->setSessionValues( 'recaptcha', true );
232
			$this->recaptchaIsUnset = true;
233
		}
234
		else if( !$isValid ) {
235
			$this->setSessionValues( 'errors', [] );
236
			$this->setSessionValues( 'recaptcha', 'reset' );
237
			$this->error = __( 'The reCAPTCHA verification failed. Please notify the site administrator.', 'site-reviews' );
238
		}
239
	}
240
}
241