Passed
Push — master ( 3e60cb...36c2a1 )
by Paul
07:09 queued 03:38
created

ValidateReview::getRecaptchaTokenStatus()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 21
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
eloc 16
nc 5
nop 0
dl 0
loc 21
ccs 0
cts 17
cp 0
crap 30
rs 9.4222
c 0
b 0
f 0
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 RECAPTCHA_ENDPOINT = 'https://www.google.com/recaptcha/api/siteverify';
16
17
	const RECAPTCHA_DISABLED = 0;
18
	const RECAPTCHA_EMPTY = 1;
19
	const RECAPTCHA_FAILED = 2;
20
	const RECAPTCHA_INVALID = 3;
21
	const RECAPTCHA_VALID = 4;
22
23
	const VALIDATION_RULES = [
24
		'content' => 'required',
25
		'email' => 'required|email',
26
		'name' => 'required',
27
		'rating' => 'required|number|between:1,5',
28
		'terms' => 'accepted',
29
		'title' => 'required',
30
	];
31
32
	/**
33
	 * @var string|void
34
	 */
35
	public $error;
36
37
	/**
38
	 * @var string
39
	 */
40
	public $form_id;
41
42
	/**
43
	 * @var bool
44
	 */
45
	public $recaptchaIsUnset = false;
46
47
	/**
48
	 * @var array
49
	 */
50
	public $request;
51
52
	/**
53
	 * @var array
54
	 */
55
	protected $options;
56
57
	/**
58
	 * @return static
59
	 */
60 1
	public function validate( array $request )
61
	{
62 1
		$this->form_id = $request['form_id'];
63 1
		$this->options = glsr( OptionManager::class )->all();
64 1
		$this->request = $this->validateRequest( $request );
65 1
		$this->validateCustom();
66 1
		$this->validateHoneyPot();
67 1
		$this->validateBlacklist();
68 1
		$this->validateAkismet();
69 1
		$this->validateRecaptcha();
70 1
		if( !empty( $this->error )) {
71
			$this->setSessionValues( 'message', $this->error );
72
		}
73 1
		return $this;
74
	}
75
76
	/**
77
	 * @param string $path
78
	 * @param mixed $fallback
79
	 * @return mixed
80
	 */
81 1
	protected function getOption( $path, $fallback = '' )
82
	{
83 1
		return glsr( Helper::class )->getPathValue( $path, $this->options, $fallback );
84
	}
85
86
	/**
87
	 * @return int
88
	 */
89 1
	protected function getRecaptchaStatus()
90
	{
91 1
		if( !glsr( OptionManager::class )->isRecaptchaEnabled() ) {
92 1
			return static::RECAPTCHA_DISABLED;
93
		}
94
		if( empty( $this->request['_recaptcha-token'] )) {
95
			return $this->request['_counter'] < intval( apply_filters( 'site-reviews/recaptcha/timeout', 5 ))
96
				? static::RECAPTCHA_EMPTY
97
				: static::RECAPTCHA_FAILED;
98
		}
99
		return $this->getRecaptchaTokenStatus();
100
	}
101
102
	/**
103
	 * @return int
104
	 */
105
	protected function getRecaptchaTokenStatus()
106
	{
107
		$endpoint = add_query_arg([
108
			'remoteip' => glsr( Helper::class )->getIpAddress(),
109
			'response' => $this->request['_recaptcha-token'],
110
			'secret' => $this->getOption( 'settings.submissions.recaptcha.secret' ),
111
		], static::RECAPTCHA_ENDPOINT );
112
		if( is_wp_error( $response = wp_remote_get( $endpoint ))) {
113
			glsr_log()->error( $response->get_error_message() );
114
			return static::RECAPTCHA_FAILED;
115
		}
116
		$response = json_decode( wp_remote_retrieve_body( $response ));
117
		if( !empty( $response->success )) {
118
			return boolval( $response->success )
119
				? static::RECAPTCHA_VALID
120
				: static::RECAPTCHA_INVALID;
121
		}
122
		foreach( $response->{'error-codes'} as $error ) {
123
			glsr_log()->error( 'reCAPTCHA error: '.$error );
124
		}
125
		return static::RECAPTCHA_INVALID;
126
	}
127
128
	/**
129
	 * @return array
130
	 */
131 1
	protected function getValidationRules( array $request )
132
	{
133 1
		$rules = array_intersect_key(
134 1
			apply_filters( 'site-reviews/validation/rules', static::VALIDATION_RULES, $request ),
135 1
			array_flip( $this->getOption( 'settings.submissions.required', [] ))
136
		);
137 1
		$excluded = isset( $request['excluded'] )
138 1
			? explode( ',', $request['excluded'] )
139 1
			: [];
140 1
		return array_diff_key( $rules, array_flip( $excluded ));
141
	}
142
143
	/**
144
	 * @return bool
145
	 */
146 1
	protected function isRequestValid( array $request )
147
	{
148 1
		$rules = $this->getValidationRules( $request );
149 1
		$errors = glsr( Validator::class )->validate( $request, $rules );
150 1
		if( empty( $errors )) {
151 1
			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 1
	protected function validateAkismet()
177
	{
178 1
		if( !empty( $this->error ))return;
179 1
		if( !glsr( Akismet::class )->isSpam( $this->request ))return;
180
		$this->setSessionValues( 'errors', [], 'Akismet caught a spam submission (consider adding the IP address to the blacklist):' );
181
		$this->error = __( 'This review has been flagged as possible spam and cannot be submitted.', 'site-reviews' );
182
	}
183
184
	/**
185
	 * @return void
186
	 */
187 1
	protected function validateBlacklist()
188
	{
189 1
		if( !empty( $this->error ))return;
190 1
		if( !glsr( Blacklist::class )->isBlacklisted( $this->request ))return;
191
		$blacklistAction = $this->getOption( '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 1
	protected function validateCustom()
204
	{
205 1
		if( !empty( $this->error ))return;
206 1
		$validated = apply_filters( 'site-reviews/validate/custom', true, $this->request );
207 1
		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 1
	protected function validateHoneyPot()
219
	{
220 1
		if( !empty( $this->error ))return;
221 1
		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 1
	protected function validateRecaptcha()
230
	{
231 1
		if( !empty( $this->error ))return;
232 1
		$status = $this->getRecaptchaStatus();
233 1
		if( in_array( $status, [static::RECAPTCHA_DISABLED, static::RECAPTCHA_VALID] ))return;
234
		if( $status == static::RECAPTCHA_EMPTY ) {
235
			$this->setSessionValues( 'recaptcha', 'unset' );
236
			$this->recaptchaIsUnset = true;
237
			return;
238
		}
239
		$this->setSessionValues( 'errors', [] );
240
		$this->setSessionValues( 'recaptcha', 'reset' );
241
		$errors = [
242
			static::RECAPTCHA_FAILED => __( 'The reCAPTCHA failed to load, please refresh the page and try again.', 'site-reviews' ),
243
			static::RECAPTCHA_INVALID => __( 'The reCAPTCHA verification failed, please try again.', 'site-reviews' ),
244
		];
245
		$this->error = $errors[$status];
246
	}
247
248
	/**
249
	 * @return array
250
	 */
251 1
	protected function validateRequest( array $request )
252
	{
253 1
		if( !$this->isRequestValid( $request )) {
254
			$this->error = __( 'Please fix the submission errors.', 'site-reviews' );
255
			return $request;
256
		}
257 1
		return array_merge( glsr( ValidateReviewDefaults::class )->defaults(), $request );
258
	}
259
}
260