|
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 VALIDATION_RULES = [ |
|
18
|
|
|
'content' => 'required|min:0', |
|
19
|
|
|
'email' => 'required|email|min:5', |
|
20
|
|
|
'name' => 'required', |
|
21
|
|
|
'rating' => 'required|numeric|between:1,5', |
|
22
|
|
|
'terms' => 'accepted', |
|
23
|
|
|
'title' => 'required', |
|
24
|
|
|
]; |
|
25
|
|
|
|
|
26
|
|
|
/** |
|
27
|
|
|
* @var string|void |
|
28
|
|
|
*/ |
|
29
|
|
|
public $error; |
|
30
|
|
|
|
|
31
|
|
|
/** |
|
32
|
|
|
* @var string |
|
33
|
|
|
*/ |
|
34
|
|
|
public $form_id; |
|
35
|
|
|
|
|
36
|
|
|
/** |
|
37
|
|
|
* @var bool |
|
38
|
|
|
*/ |
|
39
|
|
|
public $recaptchaIsUnset = false; |
|
40
|
|
|
|
|
41
|
|
|
/** |
|
42
|
|
|
* @var array |
|
43
|
|
|
*/ |
|
44
|
|
|
public $request; |
|
45
|
|
|
|
|
46
|
|
|
/** |
|
47
|
|
|
* @var array |
|
48
|
|
|
*/ |
|
49
|
|
|
protected $options; |
|
50
|
|
|
|
|
51
|
|
|
/** |
|
52
|
|
|
* @return static |
|
53
|
|
|
*/ |
|
54
|
1 |
|
public function validate( array $request ) |
|
55
|
|
|
{ |
|
56
|
1 |
|
$this->form_id = $request['form_id']; |
|
57
|
1 |
|
$this->options = glsr( OptionManager::class )->all(); |
|
58
|
1 |
|
$this->request = $this->validateRequest( $request ); |
|
59
|
1 |
|
$this->validateCustom(); |
|
60
|
1 |
|
$this->validateHoneyPot(); |
|
61
|
1 |
|
$this->validateBlacklist(); |
|
62
|
1 |
|
$this->validateAkismet(); |
|
63
|
1 |
|
$this->validateRecaptcha(); |
|
64
|
1 |
|
if( !empty( $this->error )) { |
|
65
|
|
|
$this->setSessionValues( 'message', $this->error ); |
|
66
|
|
|
} |
|
67
|
1 |
|
return $this; |
|
68
|
|
|
} |
|
69
|
|
|
|
|
70
|
|
|
/** |
|
71
|
|
|
* @param string $path |
|
72
|
|
|
* @param mixed $fallback |
|
73
|
|
|
* @return mixed |
|
74
|
|
|
*/ |
|
75
|
1 |
|
protected function getOption( $path, $fallback = '' ) |
|
76
|
|
|
{ |
|
77
|
1 |
|
return glsr( Helper::class )->getPathValue( $path, $this->options, $fallback ); |
|
78
|
|
|
} |
|
79
|
|
|
|
|
80
|
|
|
/** |
|
81
|
|
|
* @return array |
|
82
|
|
|
*/ |
|
83
|
1 |
|
protected function getValidationRules( array $request ) |
|
84
|
|
|
{ |
|
85
|
1 |
|
$rules = array_intersect_key( |
|
86
|
1 |
|
apply_filters( 'site-reviews/validation/rules', static::VALIDATION_RULES ), |
|
87
|
1 |
|
$this->getOption( 'settings.submissions.required', [] ) |
|
88
|
|
|
); |
|
89
|
1 |
|
$excluded = isset( $request['excluded'] ) |
|
90
|
1 |
|
? (array)json_decode( $request['excluded'] ) |
|
91
|
1 |
|
: []; |
|
92
|
1 |
|
return array_diff_key( $rules, array_flip( $excluded )); |
|
93
|
|
|
} |
|
94
|
|
|
|
|
95
|
|
|
/** |
|
96
|
|
|
* @return bool|null |
|
97
|
|
|
*/ |
|
98
|
1 |
|
protected function isRecaptchaResponseValid() |
|
99
|
|
|
{ |
|
100
|
1 |
|
$integration = $this->getOption( 'settings.submissions.recaptcha.integration' ); |
|
101
|
1 |
|
if( !$integration ) { |
|
102
|
1 |
|
return true; |
|
103
|
|
|
} |
|
104
|
|
|
if( empty( $this->request['recaptcha-token'] )) { |
|
105
|
|
|
return null; // @see $this->validateRecaptcha() |
|
|
|
|
|
|
106
|
|
|
} |
|
107
|
|
|
if( $integration == 'custom' ) { |
|
108
|
|
|
return $this->isRecaptchaValid( $this->request['recaptcha-token'] ); |
|
109
|
|
|
} |
|
110
|
|
|
if( $integration == 'invisible-recaptcha' ) { |
|
111
|
|
|
return boolval( apply_filters( 'google_invre_is_valid_request_filter', true )); |
|
112
|
|
|
} |
|
113
|
|
|
return false; |
|
114
|
|
|
} |
|
115
|
|
|
|
|
116
|
|
|
/** |
|
117
|
|
|
* @param string $recaptchaToken |
|
118
|
|
|
* @return bool |
|
119
|
|
|
*/ |
|
120
|
|
|
protected function isRecaptchaValid( $recaptchaToken ) |
|
121
|
|
|
{ |
|
122
|
|
|
$endpoint = add_query_arg([ |
|
123
|
|
|
'remoteip' => glsr( Helper::class )->getIpAddress(), |
|
124
|
|
|
'response' => $recaptchaToken, |
|
125
|
|
|
'secret' => $this->getOption( 'settings.submissions.recaptcha.secret' ), |
|
126
|
|
|
], static::RECAPTCHA_ENDPOINT ); |
|
127
|
|
|
if( is_wp_error( $response = wp_remote_get( $endpoint ))) { |
|
128
|
|
|
glsr_log()->error( $response->get_error_message() ); |
|
129
|
|
|
return false; |
|
130
|
|
|
} |
|
131
|
|
|
$response = json_decode( wp_remote_retrieve_body( $response )); |
|
132
|
|
|
if( !empty( $response->success )) { |
|
133
|
|
|
return boolval( $response->success ); |
|
134
|
|
|
} |
|
135
|
|
|
$errorCodes = [ |
|
136
|
|
|
'missing-input-secret' => 'The secret parameter is missing.', |
|
137
|
|
|
'invalid-input-secret' => 'The secret parameter is invalid or malformed.', |
|
138
|
|
|
'missing-input-response' => 'The response parameter is missing.', |
|
139
|
|
|
'invalid-input-response' => 'The response parameter is invalid or malformed.', |
|
140
|
|
|
'bad-request' => 'The request is invalid or malformed.', |
|
141
|
|
|
]; |
|
142
|
|
|
foreach( $response->{'error-codes'} as $error ) { |
|
143
|
|
|
glsr_log()->error( 'reCAPTCHA: '.$errorCodes[$error] ); |
|
144
|
|
|
} |
|
145
|
|
|
return false; |
|
146
|
|
|
} |
|
147
|
|
|
|
|
148
|
|
|
/** |
|
149
|
|
|
* @return bool |
|
150
|
|
|
*/ |
|
151
|
1 |
|
protected function isRequestValid( array $request ) |
|
152
|
|
|
{ |
|
153
|
1 |
|
$rules = $this->getValidationRules( $request ); |
|
154
|
1 |
|
$errors = glsr( Validator::class )->validate( $request, $rules ); |
|
155
|
1 |
|
if( empty( $errors )) { |
|
156
|
1 |
|
return true; |
|
157
|
|
|
} |
|
158
|
|
|
$this->setSessionValues( 'errors', $errors ); |
|
159
|
|
|
$this->setSessionValues( 'values', $request ); |
|
160
|
|
|
return false; |
|
161
|
|
|
} |
|
162
|
|
|
|
|
163
|
|
|
/** |
|
164
|
|
|
* @param string $type |
|
165
|
|
|
* @param mixed $value |
|
166
|
|
|
* @param string $loggedMessage |
|
167
|
|
|
* @return void |
|
168
|
|
|
*/ |
|
169
|
|
|
protected function setSessionValues( $type, $value, $loggedMessage = '' ) |
|
170
|
|
|
{ |
|
171
|
|
|
glsr( Session::class )->set( $this->form_id.$type, $value ); |
|
172
|
|
|
if( !empty( $loggedMessage )) { |
|
173
|
|
|
glsr_log()->warning( $loggedMessage ); |
|
174
|
|
|
glsr_log()->warning( $this->request ); |
|
175
|
|
|
} |
|
176
|
|
|
} |
|
177
|
|
|
|
|
178
|
|
|
/** |
|
179
|
|
|
* @return void |
|
180
|
|
|
*/ |
|
181
|
1 |
|
protected function validateAkismet() |
|
182
|
|
|
{ |
|
183
|
1 |
|
if( !empty( $this->error ))return; |
|
184
|
1 |
|
if( !glsr( Akismet::class )->isSpam( $this->request ))return; |
|
185
|
|
|
$this->setSessionValues( 'errors', [], 'Akismet caught a spam submission:' ); |
|
186
|
|
|
$this->error = __( 'Your review cannot be submitted at this time. Please try again later.', 'site-reviews' ); |
|
187
|
|
|
} |
|
188
|
|
|
|
|
189
|
|
|
/** |
|
190
|
|
|
* @return void |
|
191
|
|
|
*/ |
|
192
|
1 |
|
protected function validateBlacklist() |
|
193
|
|
|
{ |
|
194
|
1 |
|
if( !empty( $this->error ))return; |
|
195
|
1 |
|
if( !glsr( Blacklist::class )->isBlacklisted( $this->request ))return; |
|
196
|
|
|
$blacklistAction = $this->getOption( 'settings.submissions.blacklist.action' ); |
|
197
|
|
|
if( $blacklistAction == 'reject' ) { |
|
198
|
|
|
$this->setSessionValues( 'errors', [], 'Blacklisted submission detected:' ); |
|
199
|
|
|
$this->error = __( 'Your review cannot be submitted at this time.', 'site-reviews' ); |
|
200
|
|
|
return; |
|
201
|
|
|
} |
|
202
|
|
|
$this->request['blacklisted'] = true; |
|
203
|
|
|
} |
|
204
|
|
|
|
|
205
|
|
|
/** |
|
206
|
|
|
* @return void |
|
207
|
|
|
*/ |
|
208
|
1 |
|
protected function validateCustom() |
|
209
|
|
|
{ |
|
210
|
1 |
|
if( !empty( $this->error ))return; |
|
211
|
1 |
|
$validated = apply_filters( 'site-reviews/validate/review/submission', true, $this->request ); |
|
212
|
1 |
|
if( $validated === true )return; |
|
213
|
|
|
$this->setSessionValues( 'errors', [] ); |
|
214
|
|
|
$this->setSessionValues( 'values', $this->request ); |
|
215
|
|
|
$this->error = is_string( $validated ) |
|
216
|
|
|
? $validated |
|
217
|
|
|
: __( 'The review submission failed. Please notify the site administrator.', 'site-reviews' ); |
|
218
|
|
|
} |
|
219
|
|
|
|
|
220
|
|
|
/** |
|
221
|
|
|
* @return void |
|
222
|
|
|
*/ |
|
223
|
1 |
|
protected function validateHoneyPot() |
|
224
|
|
|
{ |
|
225
|
1 |
|
if( !empty( $this->error ))return; |
|
226
|
1 |
|
if( empty( $this->request['gotcha'] ))return; |
|
227
|
|
|
$this->setSessionValues( 'errors', [], 'The Honeypot caught a bad submission:' ); |
|
228
|
|
|
$this->error = __( 'The review submission failed. Please notify the site administrator.', 'site-reviews' ); |
|
229
|
|
|
} |
|
230
|
|
|
|
|
231
|
|
|
/** |
|
232
|
|
|
* @return void |
|
233
|
|
|
*/ |
|
234
|
1 |
|
protected function validateRecaptcha() |
|
235
|
|
|
{ |
|
236
|
1 |
|
if( !empty( $this->error ))return; |
|
237
|
1 |
|
$isValid = $this->isRecaptchaResponseValid(); |
|
238
|
1 |
|
if( is_null( $isValid )) { |
|
239
|
|
|
$this->setSessionValues( 'recaptcha', true ); |
|
240
|
|
|
$this->recaptchaIsUnset = true; |
|
241
|
|
|
} |
|
242
|
1 |
|
else if( !$isValid ) { |
|
243
|
|
|
$this->setSessionValues( 'errors', [] ); |
|
244
|
|
|
$this->setSessionValues( 'recaptcha', 'reset' ); |
|
245
|
|
|
$this->error = __( 'The reCAPTCHA verification failed. Please notify the site administrator.', 'site-reviews' ); |
|
246
|
|
|
} |
|
247
|
1 |
|
} |
|
248
|
|
|
|
|
249
|
|
|
/** |
|
250
|
|
|
* @return array |
|
251
|
|
|
*/ |
|
252
|
1 |
|
protected function validateRequest( array $request ) |
|
253
|
|
|
{ |
|
254
|
1 |
|
if( !$this->isRequestValid( $request )) { |
|
255
|
|
|
$this->error = __( 'Please fix the submission errors.', 'site-reviews' ); |
|
256
|
|
|
return $request; |
|
257
|
|
|
} |
|
258
|
1 |
|
if( empty( $request['title'] )) { |
|
259
|
|
|
$request['title'] = __( 'No Title', 'site-reviews' ); |
|
260
|
|
|
} |
|
261
|
1 |
|
return array_merge( glsr( ValidateReviewDefaults::class )->defaults(), $request ); |
|
262
|
|
|
} |
|
263
|
|
|
} |
|
264
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.