Passed
Push — master ( 2009cc...a406f9 )
by Paul
04:50 queued 21s
created

ValidateReview::validate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 2.0017

Importance

Changes 0
Metric Value
cc 2
eloc 12
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 15
ccs 12
cts 13
cp 0.9231
crap 2.0017
rs 9.8666
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\Helpers\Arr;
9
use GeminiLabs\SiteReviews\Modules\Akismet;
10
use GeminiLabs\SiteReviews\Modules\Blacklist;
11
use GeminiLabs\SiteReviews\Modules\ReviewLimits;
12
use GeminiLabs\SiteReviews\Modules\Validator;
13
14
class ValidateReview
15
{
16
    const RECAPTCHA_ENDPOINT = 'https://www.google.com/recaptcha/api/siteverify';
17
18
    const RECAPTCHA_DISABLED = 0;
19
    const RECAPTCHA_EMPTY = 1;
20
    const RECAPTCHA_FAILED = 2;
21
    const RECAPTCHA_INVALID = 3;
22
    const RECAPTCHA_VALID = 4;
23
24
    const VALIDATION_RULES = [
25
        'content' => 'required',
26
        'email' => 'required|email',
27
        'name' => 'required',
28
        'rating' => 'required|number|between:1,5',
29
        'terms' => 'accepted',
30
        'title' => 'required',
31
    ];
32
33
    /**
34
     * @var string|void
35
     */
36
    public $error;
37
38
    /**
39
     * @var string
40
     */
41
    public $form_id;
42
43
    /**
44
     * @var bool
45
     */
46
    public $recaptchaIsUnset = false;
47
48
    /**
49
     * @var array
50
     */
51
    public $request;
52
53
    /**
54
     * @var array
55
     */
56
    protected $options;
57
58
    /**
59
     * @return static
60
     */
61 1
    public function validate(array $request)
62
    {
63 1
        $this->form_id = $request['form_id'];
64 1
        $this->options = glsr(OptionManager::class)->all();
65 1
        $this->request = $this->validateRequest($request);
66 1
        $this->validateCustom();
67 1
        $this->validateHoneyPot();
68 1
        $this->validateReviewLimits();
69 1
        $this->validateBlacklist();
70 1
        $this->validateAkismet();
71 1
        $this->validateRecaptcha();
72 1
        if (!empty($this->error)) {
73
            $this->setSessionValues('message', $this->error);
74
        }
75 1
        return $this;
76
    }
77
78
    /**
79
     * @param string $path
80
     * @param mixed $fallback
81
     * @return mixed
82
     */
83 1
    protected function getOption($path, $fallback = '')
84
    {
85 1
        return Arr::get($this->options, $path, $fallback);
86
    }
87
88
    /**
89
     * @return int
90
     */
91 1
    protected function getRecaptchaStatus()
92
    {
93 1
        if (!glsr(OptionManager::class)->isRecaptchaEnabled()) {
94 1
            return static::RECAPTCHA_DISABLED;
95
        }
96
        if (empty($this->request['_recaptcha-token'])) {
97
            return $this->request['_counter'] < intval(apply_filters('site-reviews/recaptcha/timeout', 5))
98
                ? static::RECAPTCHA_EMPTY
99
                : static::RECAPTCHA_FAILED;
100
        }
101
        return $this->getRecaptchaTokenStatus();
102
    }
103
104
    /**
105
     * @return int
106
     */
107
    protected function getRecaptchaTokenStatus()
108
    {
109
        $endpoint = add_query_arg([
110
            'remoteip' => Helper::getIpAddress(),
111
            'response' => $this->request['_recaptcha-token'],
112
            'secret' => $this->getOption('settings.submissions.recaptcha.secret'),
113
        ], static::RECAPTCHA_ENDPOINT);
114
        if (is_wp_error($response = wp_remote_get($endpoint))) {
115
            glsr_log()->error($response->get_error_message());
116
            return static::RECAPTCHA_FAILED;
117
        }
118
        $response = json_decode(wp_remote_retrieve_body($response));
119
        if (!empty($response->success)) {
120
            return boolval($response->success)
121
                ? static::RECAPTCHA_VALID
122
                : static::RECAPTCHA_INVALID;
123
        }
124
        foreach ($response->{'error-codes'} as $error) {
125
            glsr_log()->error('reCAPTCHA error: '.$error);
126
        }
127
        return static::RECAPTCHA_INVALID;
128
    }
129
130
    /**
131
     * @return array
132
     */
133 1
    protected function getValidationRules(array $request)
134
    {
135 1
        $rules = array_intersect_key(
136 1
            apply_filters('site-reviews/validation/rules', static::VALIDATION_RULES, $request),
137 1
            array_flip($this->getOption('settings.submissions.required', []))
138
        );
139 1
        $excluded = explode(',', Arr::get($request, 'excluded'));
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->error = __('Please fix the submission errors.', 'site-reviews');
154
        $this->setSessionValues('errors', $errors);
155
        $this->setSessionValues('values', $request);
156
        return false;
157
    }
158
159
    protected function setError($message, $loggedMessage = '')
160
    {
161
        $this->setSessionValues('errors', [], $loggedMessage);
162
        $this->error = $message;
163
    }
164
165
    /**
166
     * @param string $type
167
     * @param mixed $value
168
     * @param string $loggedMessage
169
     * @return void
170
     */
171
    protected function setSessionValues($type, $value, $loggedMessage = '')
172
    {
173
        glsr()->sessionSet($this->form_id.$type, $value);
174
        if (!empty($loggedMessage)) {
175
            glsr_log()->warning($loggedMessage)->debug($this->request);
176
        }
177
    }
178
179
    /**
180
     * @return void
181
     */
182 1
    protected function validateAkismet()
183
    {
184 1
        if (!empty($this->error)) {
185
            return;
186
        }
187 1
        if (glsr(Akismet::class)->isSpam($this->request)) {
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 1
    }
193
194
    /**
195
     * @return void
196
     */
197 1
    protected function validateBlacklist()
198
    {
199 1
        if (!empty($this->error)) {
200
            return;
201
        }
202 1
        if (!glsr(Blacklist::class)->isBlacklisted($this->request)) {
203 1
            return;
204
        }
205
        $blacklistAction = $this->getOption('settings.submissions.blacklist.action');
206
        if ('reject' != $blacklistAction) {
207
            $this->request['blacklisted'] = true;
208
            return;
209
        }
210
        $this->setError(__('Your review cannot be submitted at this time.', 'site-reviews'),
211
            'Blacklisted submission detected:'
212
        );
213
    }
214
215
    /**
216
     * @return void
217
     */
218 1
    protected function validateCustom()
219
    {
220 1
        if (!empty($this->error)) {
221
            return;
222
        }
223 1
        $validated = apply_filters('site-reviews/validate/custom', true, $this->request);
224 1
        if (true === $validated) {
225 1
            return;
226
        }
227
        $errorMessage = is_string($validated)
228
            ? $validated
229
            : __('The review submission failed. Please notify the site administrator.', 'site-reviews');
230
        $this->setError($errorMessage);
231
        $this->setSessionValues('values', $this->request);
232
    }
233
234
    /**
235
     * @return void
236
     */
237 1
    protected function validateHoneyPot()
238
    {
239 1
        if (!empty($this->error)) {
240
            return;
241
        }
242 1
        if (!empty($this->request['gotcha'])) {
243
            $this->setError(__('The review submission failed. Please notify the site administrator.', 'site-reviews'),
244
                'The Honeypot caught a bad submission:'
245
            );
246
        }
247 1
    }
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
            $this->setError(__('You have already submitted a review.', 'site-reviews'));
259
        }
260 1
    }
261
262
    /**
263
     * @return void
264
     */
265 1
    protected function validateRecaptcha()
266
    {
267 1
        if (!empty($this->error)) {
268
            return;
269
        }
270 1
        $status = $this->getRecaptchaStatus();
271 1
        if (in_array($status, [static::RECAPTCHA_DISABLED, static::RECAPTCHA_VALID])) {
272 1
            return;
273
        }
274
        if (static::RECAPTCHA_EMPTY === $status) {
275
            $this->setSessionValues('recaptcha', 'unset');
276
            $this->recaptchaIsUnset = true;
277
            return;
278
        }
279
        $this->setSessionValues('recaptcha', 'reset');
280
        $errors = [
281
            static::RECAPTCHA_FAILED => __('The reCAPTCHA failed to load, please refresh the page and try again.', 'site-reviews'),
282
            static::RECAPTCHA_INVALID => __('The reCAPTCHA verification failed, please try again.', 'site-reviews'),
283
        ];
284
        $this->setError($errors[$status]);
285
    }
286
287
    /**
288
     * @return array
289
     */
290 1
    protected function validateRequest(array $request)
291
    {
292 1
        if ($this->isRequestValid($request)) {
293 1
            return array_merge(glsr(ValidateReviewDefaults::class)->defaults(), $request);
294
        }
295
        return $request;
296
    }
297
}
298