Passed
Push — feature/rebusify ( 0fe5b2...103190 )
by Paul
08:37 queued 03:55
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 1
Bugs 0 Features 1
Metric Value
cc 5
eloc 16
c 1
b 0
f 1
nc 5
nop 0
dl 0
loc 21
ccs 0
cts 17
cp 0
crap 30
rs 9.4222
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\ReviewLimits;
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->validateReviewLimits();
68 1
        $this->validateBlacklist();
69 1
        $this->validateAkismet();
70 1
        $this->validateRecaptcha();
71 1
        if (!empty($this->error)) {
72
            $this->setSessionValues('message', $this->error);
73
        }
74 1
        return $this;
75
    }
76
77
    /**
78
     * @param string $path
79
     * @param mixed $fallback
80
     * @return mixed
81
     */
82 1
    protected function getOption($path, $fallback = '')
83
    {
84 1
        return glsr(Helper::class)->dataGet($this->options, $path, $fallback);
85
    }
86
87
    /**
88
     * @return int
89
     */
90 1
    protected function getRecaptchaStatus()
91
    {
92 1
        if (!glsr(OptionManager::class)->isRecaptchaEnabled()) {
93 1
            return static::RECAPTCHA_DISABLED;
94
        }
95
        if (empty($this->request['_recaptcha-token'])) {
96
            return $this->request['_counter'] < intval(apply_filters('site-reviews/recaptcha/timeout', 5))
97
                ? static::RECAPTCHA_EMPTY
98
                : static::RECAPTCHA_FAILED;
99
        }
100
        return $this->getRecaptchaTokenStatus();
101
    }
102
103
    /**
104
     * @return int
105
     */
106
    protected function getRecaptchaTokenStatus()
107
    {
108
        $endpoint = add_query_arg([
109
            'remoteip' => glsr(Helper::class)->getIpAddress(),
110
            'response' => $this->request['_recaptcha-token'],
111
            'secret' => $this->getOption('settings.submissions.recaptcha.secret'),
112
        ], static::RECAPTCHA_ENDPOINT);
113
        if (is_wp_error($response = wp_remote_get($endpoint))) {
114
            glsr_log()->error($response->get_error_message());
115
            return static::RECAPTCHA_FAILED;
116
        }
117
        $response = json_decode(wp_remote_retrieve_body($response));
118
        if (!empty($response->success)) {
119
            return boolval($response->success)
120
                ? static::RECAPTCHA_VALID
121
                : static::RECAPTCHA_INVALID;
122
        }
123
        foreach ($response->{'error-codes'} as $error) {
124
            glsr_log()->error('reCAPTCHA error: '.$error);
125
        }
126
        return static::RECAPTCHA_INVALID;
127
    }
128
129
    /**
130
     * @return array
131
     */
132 1
    protected function getValidationRules(array $request)
133
    {
134 1
        $rules = array_intersect_key(
135 1
            apply_filters('site-reviews/validation/rules', static::VALIDATION_RULES, $request),
136 1
            array_flip($this->getOption('settings.submissions.required', []))
137
        );
138 1
        $excluded = explode(',', glsr_get($request, 'excluded'));
139 1
        return array_diff_key($rules, array_flip($excluded));
140
    }
141
142
    /**
143
     * @return bool
144
     */
145 1
    protected function isRequestValid(array $request)
146
    {
147 1
        $rules = $this->getValidationRules($request);
148 1
        $errors = glsr(Validator::class)->validate($request, $rules);
149 1
        if (empty($errors)) {
150 1
            return true;
151
        }
152
        $this->setSessionValues('errors', $errors);
153
        $this->setSessionValues('values', $request);
154
        return false;
155
    }
156
157
    protected function setError($message, $loggedMessage = '')
158
    {
159
        $this->setSessionValues('errors', [], $loggedMessage);
160
        $this->error = $message;
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()->sessionSet($this->form_id.$type, $value);
172
        if (!empty($loggedMessage)) {
173
            glsr_log()->warning($loggedMessage)->debug($this->request);
174
        }
175
    }
176
177
    /**
178
     * @return void
179
     */
180 1
    protected function validateAkismet()
181
    {
182 1
        if (!empty($this->error)) {
183
            return;
184
        }
185 1
        if (!glsr(Akismet::class)->isSpam($this->request)) {
186 1
            return;
187
        }
188
        $this->setError(__('This review has been flagged as possible spam and cannot be submitted.', 'site-reviews'),
189
            'Akismet caught a spam submission (consider adding the IP address to the blacklist):'
190
        );
191
    }
192
193
    /**
194
     * @return void
195
     */
196 1
    protected function validateBlacklist()
197
    {
198 1
        if (!empty($this->error)) {
199
            return;
200
        }
201 1
        if (!glsr(Blacklist::class)->isBlacklisted($this->request)) {
202 1
            return;
203
        }
204
        $blacklistAction = $this->getOption('settings.submissions.blacklist.action');
205
        if ('reject' != $blacklistAction) {
206
            $this->request['blacklisted'] = true;
207
            return;
208
        }
209
        $this->setError(__('Your review cannot be submitted at this time.', 'site-reviews'),
210
            'Blacklisted submission detected:'
211
        );
212
    }
213
214
    /**
215
     * @return void
216
     */
217 1
    protected function validateCustom()
218
    {
219 1
        if (!empty($this->error)) {
220
            return;
221
        }
222 1
        $validated = apply_filters('site-reviews/validate/custom', true, $this->request);
223 1
        if (true === $validated) {
224 1
            return;
225
        }
226
        $errorMessage = is_string($validated)
227
            ? $validated
228
            : __('The review submission failed. Please notify the site administrator.', 'site-reviews');
229
        $this->setError($errorMessage);
230
        $this->setSessionValues('values', $this->request);
231
    }
232
233
    /**
234
     * @return void
235
     */
236 1
    protected function validateHoneyPot()
237
    {
238 1
        if (!empty($this->error)) {
239
            return;
240
        }
241 1
        if (empty($this->request['gotcha'])) {
242 1
            return;
243
        }
244
        $this->setError(__('The review submission failed. Please notify the site administrator.', 'site-reviews'),
245
            'The Honeypot caught a bad submission:'
246
        );
247
    }
248
249
    /**
250
     * @return void
251
     */
252 1
    protected function validateReviewLimits()
253
    {
254 1
        if (!empty($this->error)) {
255
            return;
256
        }
257 1
        if (!glsr(ReviewLimits::class)->hasReachedLimit($this->request)) {
258 1
            return;
259
        }
260
        $this->setError(__('You have already submitted a review.', 'site-reviews'));
261
    }
262
263
    /**
264
     * @return void
265
     */
266 1
    protected function validateRecaptcha()
267
    {
268 1
        if (!empty($this->error)) {
269
            return;
270
        }
271 1
        $status = $this->getRecaptchaStatus();
272 1
        if (in_array($status, [static::RECAPTCHA_DISABLED, static::RECAPTCHA_VALID])) {
273 1
            return;
274
        }
275
        if (static::RECAPTCHA_EMPTY === $status) {
276
            $this->setSessionValues('recaptcha', 'unset');
277
            $this->recaptchaIsUnset = true;
278
            return;
279
        }
280
        $this->setSessionValues('recaptcha', 'reset');
281
        $errors = [
282
            static::RECAPTCHA_FAILED => __('The reCAPTCHA failed to load, please refresh the page and try again.', 'site-reviews'),
283
            static::RECAPTCHA_INVALID => __('The reCAPTCHA verification failed, please try again.', 'site-reviews'),
284
        ];
285
        $this->setError($errors[$status]);
286
    }
287
288
    /**
289
     * @return array
290
     */
291 1
    protected function validateRequest(array $request)
292
    {
293 1
        if (!$this->isRequestValid($request)) {
294
            $this->error = __('Please fix the submission errors.', 'site-reviews');
295
            return $request;
296
        }
297 1
        return array_merge(glsr(ValidateReviewDefaults::class)->defaults(), $request);
298
    }
299
}
300