Passed
Push — master ( 34ba2b...b9b7cf )
by Paul
08:22 queued 04:02
created

ValidateReview::validatePermission()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 5.024

Importance

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