Passed
Push — master ( 4562dd...74d0bd )
by Paul
04:12
created

ValidateReview::isRequestValid()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.2109

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 1
dl 0
loc 10
ccs 5
cts 8
cp 0.625
crap 2.2109
rs 10
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 )->dataGet( $this->options, $path, $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 = explode( ',', glsr_get( $request, 'excluded' ));
138 1
		return array_diff_key( $rules, array_flip( $excluded ));
139
	}
140
141
	/**
142
	 * @return bool
143
	 */
144 1
	protected function isRequestValid( array $request )
145
	{
146 1
		$rules = $this->getValidationRules( $request );
147 1
		$errors = glsr( Validator::class )->validate( $request, $rules );
148 1
		if( empty( $errors )) {
149 1
			return true;
150
		}
151
		$this->setSessionValues( 'errors', $errors );
152
		$this->setSessionValues( 'values', $request );
153
		return false;
154
	}
155
156
	/**
157
	 * @param string $type
158
	 * @param mixed $value
159
	 * @param string $loggedMessage
160
	 * @return void
161
	 */
162
	protected function setSessionValues( $type, $value, $loggedMessage = '' )
163
	{
164
		glsr( Session::class )->set( $this->form_id.$type, $value );
165
		if( !empty( $loggedMessage )) {
166
			glsr_log()->warning( $loggedMessage )->debug( $this->request );
167
		}
168
	}
169
170
	/**
171
	 * @return void
172
	 */
173 1
	protected function validateAkismet()
174
	{
175 1
		if( !empty( $this->error ))return;
176 1
		if( !glsr( Akismet::class )->isSpam( $this->request ))return;
177
		$this->setSessionValues( 'errors', [], 'Akismet caught a spam submission (consider adding the IP address to the blacklist):' );
178
		$this->error = __( 'This review has been flagged as possible spam and cannot be submitted.', 'site-reviews' );
179
	}
180
181
	/**
182
	 * @return void
183
	 */
184 1
	protected function validateBlacklist()
185
	{
186 1
		if( !empty( $this->error ))return;
187 1
		if( !glsr( Blacklist::class )->isBlacklisted( $this->request ))return;
188
		$blacklistAction = $this->getOption( 'settings.submissions.blacklist.action' );
189
		if( $blacklistAction == 'reject' ) {
190
			$this->setSessionValues( 'errors', [], 'Blacklisted submission detected:' );
191
			$this->error = __( 'Your review cannot be submitted at this time.', 'site-reviews' );
192
			return;
193
		}
194
		$this->request['blacklisted'] = true;
195
	}
196
197
	/**
198
	 * @return void
199
	 */
200 1
	protected function validateCustom()
201
	{
202 1
		if( !empty( $this->error ))return;
203 1
		$validated = apply_filters( 'site-reviews/validate/custom', true, $this->request );
204 1
		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 1
	protected function validateHoneyPot()
216
	{
217 1
		if( !empty( $this->error ))return;
218 1
		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 1
	protected function validateRecaptcha()
227
	{
228 1
		if( !empty( $this->error ))return;
229 1
		$status = $this->getRecaptchaStatus();
230 1
		if( in_array( $status, [static::RECAPTCHA_DISABLED, static::RECAPTCHA_VALID] ))return;
231
		if( $status == static::RECAPTCHA_EMPTY ) {
232
			$this->setSessionValues( 'recaptcha', 'unset' );
233
			$this->recaptchaIsUnset = true;
234
			return;
235
		}
236
		$this->setSessionValues( 'errors', [] );
237
		$this->setSessionValues( 'recaptcha', 'reset' );
238
		$errors = [
239
			static::RECAPTCHA_FAILED => __( 'The reCAPTCHA failed to load, please refresh the page and try again.', 'site-reviews' ),
240
			static::RECAPTCHA_INVALID => __( 'The reCAPTCHA verification failed, please try again.', 'site-reviews' ),
241
		];
242
		$this->error = $errors[$status];
243
	}
244
245
	/**
246
	 * @return array
247
	 */
248 1
	protected function validateRequest( array $request )
249
	{
250 1
		if( !$this->isRequestValid( $request )) {
251
			$this->error = __( 'Please fix the submission errors.', 'site-reviews' );
252
			return $request;
253
		}
254 1
		return array_merge( glsr( ValidateReviewDefaults::class )->defaults(), $request );
255
	}
256
}
257