Passed
Push — master ( 042452...c272a3 )
by Paul
05:20
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 static
41
	 */
42
	public function validate( array $request )
43
	{
44
		$this->form_id = $request['form_id'];
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
		if( !empty( $this->error )) {
52
			$this->setSessionValues( 'message', $this->error );
53
		}
54
		return $this;
55
	}
56
57
	/**
58
	 * @return array
59
	 */
60
	public function validateRequest( array $request )
61
	{
62
		if( !$this->isRequestValid( $request )) {
63
			$this->error = __( 'Please fix the submission errors.', 'site-reviews' );
64
			return $request;
65
		}
66
		if( empty( $request['title'] )) {
67
			$request['title'] = __( 'No Title', 'site-reviews' );
68
		}
69
		return array_merge( glsr( ValidateReviewDefaults::class )->defaults(), $request );
70
	}
71
72
	/**
73
	 * @return array
74
	 */
75
	protected function getValidationRules( array $request )
76
	{
77
		$rules = array_intersect_key(
78
			apply_filters( 'site-reviews/validation/rules', static::VALIDATION_RULES ),
79
			array_flip( array_merge(
80
				['rating','terms'],
81
				glsr( OptionManager::class )->get( 'settings.submissions.required', [] )
82
			))
83
		);
84
		$excluded = isset( $request['excluded'] )
85
			? (array)json_decode( $request['excluded'] )
86
			: [];
87
		return array_diff_key( $rules, array_flip( $excluded ));
88
	}
89
90
	/**
91
	 * @return bool|null
92
	 */
93
	protected function isRecaptchaResponseValid()
94
	{
95
		$integration = glsr( OptionManager::class )->get( 'settings.submissions.recaptcha.integration' );
96
		if( !$integration ) {
97
			return true;
98
		}
99
		$recaptchaResponse = filter_input( INPUT_POST, 'g-recaptcha-response' ); // @todo site-reviews[g-recaptcha-response]
100
		if( empty( $recaptchaResponse )) {
101
			return null; //if response is empty we need to return null
102
		}
103
		if( $integration == 'custom' ) {
104
			return $this->isRecaptchaValid( $recaptchaResponse );
105
		}
106
		if( $integration == 'invisible-recaptcha' ) {
107
			return boolval( apply_filters( 'google_invre_is_valid_request_filter', true ));
108
		}
109
		return false;
110
	}
111
112
	/**
113
	 * @return bool
114
	 */
115
	protected function isRecaptchaValid( $recaptchaResponse )
116
	{
117
		$endpoint = add_query_arg([
118
			'remoteip' => glsr( Helper::class )->getIpAddress(),
119
			'response' => $recaptchaResponse,
120
			'secret' => glsr( OptionManager::class )->get( 'settings.submissions.recaptcha.secret' ),
121
		], 'https://www.google.com/recaptcha/api/siteverify' );
122
		if( is_wp_error( $response = wp_remote_get( $endpoint ))) {
123
			glsr_log()->error( $response->get_error_message() );
124
			return false;
125
		}
126
		$response = json_decode( wp_remote_retrieve_body( $response ));
127
		if( !empty( $response->success )) {
128
			return boolval( $response->success );
129
		}
130
		$errorCodes = [
131
			'missing-input-secret' => 'The secret parameter is missing.',
132
			'invalid-input-secret' => 'The secret parameter is invalid or malformed.',
133
			'missing-input-response' => 'The response parameter is missing.',
134
			'invalid-input-response' => 'The response parameter is invalid or malformed.',
135
			'bad-request' => 'The request is invalid or malformed.',
136
		];
137
		foreach( $response->{'error-codes'} as $error ) {
138
			glsr_log()->error( 'reCAPTCHA: '.$errorCodes[$error] );
139
		}
140
		return false;
141
	}
142
143
	/**
144
	 * @return bool
145
	 */
146
	protected function isRequestValid( array $request )
147
	{
148
		$rules = $this->getValidationRules( $request );
149
		$errors = glsr( Validator::class )->validate( $request, $rules );
150
		if( empty( $errors )) {
151
			return true;
152
		}
153
		$this->setSessionValues( 'errors', $errors );
154
		$this->setSessionValues( 'values', $request );
155
		return false;
156
	}
157
158
	/**
159
	 * @param string $type
160
	 * @param mixed $value
161
	 * @param string $loggedMessage
162
	 * @return void
163
	 */
164
	protected function setSessionValues( $type, $value, $loggedMessage = '' )
165
	{
166
		glsr( Session::class )->set( $this->form_id.$type, $value );
167
		if( !empty( $loggedMessage )) {
168
			glsr_log()->warning( $loggedMessage );
169
			glsr_log()->warning( $this->request );
170
		}
171
	}
172
173
	/**
174
	 * @return void
175
	 */
176
	protected function validateAkismet()
177
	{
178
		if( !empty( $this->error ))return;
179
		if( !glsr( Akismet::class )->isSpam( $this->request ))return;
180
		$this->setSessionValues( 'errors', [], 'Akismet caught a spam submission:' );
181
		$this->error = __( 'Your review cannot be submitted at this time. Please try again later.', 'site-reviews' );
182
	}
183
184
	/**
185
	 * @return void
186
	 */
187
	protected function validateBlacklist()
188
	{
189
		if( !empty( $this->error ))return;
190
		if( !glsr( Blacklist::class )->isBlacklisted( $this->request ))return;
191
		$blacklistAction = glsr( OptionManager::class )->get( 'settings.submissions.blacklist.action' );
192
		if( $blacklistAction == 'reject' ) {
193
			$this->setSessionValues( 'errors', [], 'Blacklisted submission detected:' );
194
			$this->error = __( 'Your review cannot be submitted at this time.', 'site-reviews' );
195
			return;
196
		}
197
		$this->request['blacklisted'] = true;
198
	}
199
200
	/**
201
	 * @return void
202
	 */
203
	protected function validateCustom()
204
	{
205
		if( !empty( $this->error ))return;
206
		$validated = apply_filters( 'site-reviews/validate/review/submission', true, $this->request );
207
		if( $validated === true )return;
208
		$this->setSessionValues( 'errors', [] );
209
		$this->setSessionValues( 'values', $this->request );
210
		$this->error = is_string( $validated )
211
			? $validated
212
			: __( 'The review submission failed. Please notify the site administrator.', 'site-reviews' );
213
	}
214
215
	/**
216
	 * @return void
217
	 */
218
	protected function validateHoneyPot()
219
	{
220
		if( !empty( $this->error ))return;
221
		if( empty( $this->request['gotcha'] ))return;
222
		$this->setSessionValues( 'errors', [], 'The Honeypot caught a bad submission:' );
223
		$this->error = __( 'The review submission failed. Please notify the site administrator.', 'site-reviews' );
224
	}
225
226
	/**
227
	 * @return void
228
	 */
229
	protected function validateRecaptcha()
230
	{
231
		if( !empty( $this->error ))return;
232
		$isValid = $this->isRecaptchaResponseValid();
233
		if( is_null( $isValid )) {
234
			$this->setSessionValues( 'recaptcha', true );
235
			$this->recaptchaIsUnset = true;
236
		}
237
		else if( !$isValid ) {
238
			$this->setSessionValues( 'errors', [] );
239
			$this->setSessionValues( 'recaptcha', 'reset' );
240
			$this->error = __( 'The reCAPTCHA verification failed. Please notify the site administrator.', 'site-reviews' );
241
		}
242
	}
243
}
244