Completed
Push — master ( 7c2bcc...cd5c4f )
by ARCANEDEV
11s
created

NoCaptcha   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 449
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 449
c 1
b 0
f 0
wmc 35
lcom 1
cbo 6
ccs 115
cts 115
cp 1
rs 9

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A setSecret() 0 8 1
A setSiteKey() 0 8 1
A setLang() 0 6 1
A getScriptSrc() 0 15 4
A setRequestClient() 0 7 1
A setAttributes() 0 7 1
A display() 0 9 1
A image() 0 6 1
A audio() 0 6 1
A button() 0 8 1
A verify() 0 12 3
A verifyRequest() 0 15 3
A script() 0 11 2
A scriptWithCallback() 0 16 3
A renderCaptchas() 0 6 1
A hasLang() 0 4 1
A hasCallbackName() 0 4 2
A checkKey() 0 8 1
A checkIsString() 0 8 2
A checkIsNotEmpty() 0 6 2
A sendVerifyRequest() 0 8 1
1
<?php namespace Arcanedev\NoCaptcha;
2
3
use Arcanedev\NoCaptcha\Utilities\Attributes;
4
use Arcanedev\NoCaptcha\Utilities\Request;
5
use Psr\Http\Message\ServerRequestInterface;
6
7
/**
8
 * Class     NoCaptcha
9
 *
10
 * @package  Arcanedev\NoCaptcha
11
 * @author   ARCANEDEV <[email protected]>
12
 */
13
class NoCaptcha implements Contracts\NoCaptcha
14
{
15
    /* ------------------------------------------------------------------------------------------------
16
     |  Constants
17
     | ------------------------------------------------------------------------------------------------
18
     */
19
    const CLIENT_URL   = 'https://www.google.com/recaptcha/api.js';
20
    const VERIFY_URL   = 'https://www.google.com/recaptcha/api/siteverify';
21
    const CAPTCHA_NAME = 'g-recaptcha-response';
22
23
    /* ------------------------------------------------------------------------------------------------
24
     |  Properties
25
     | ------------------------------------------------------------------------------------------------
26
     */
27
    /**
28
     * The shared key between your site and ReCAPTCHA
29
     *
30
     * @var string
31
     */
32
    private $secret;
33
34
    /**
35
     * Your site key
36
     *
37
     * @var string
38
     */
39
    private $siteKey;
40
41
    /**
42
     * Forces the widget to render in a specific language.
43
     * Auto-detects the user's language if unspecified.
44
     *
45
     * @var string
46
     */
47
    protected $lang;
48
49
    /**
50
     * Decides if we've already loaded the script file or not.
51
     *
52
     * @param bool
53
     */
54
    protected $scriptLoaded = false;
55
56
    /**
57
     * HTTP Request Client
58
     *
59
     * @var \Arcanedev\NoCaptcha\Contracts\Utilities\RequestInterface
60
     */
61
    protected $request;
62
63
    /**
64
     * noCaptcha Attributes
65
     *
66
     * @var \Arcanedev\NoCaptcha\Contracts\Utilities\AttributesInterface
67
     */
68
    protected $attributes;
69
70
    /* ------------------------------------------------------------------------------------------------
71
     |  Constructor
72
     | ------------------------------------------------------------------------------------------------
73
     */
74
    /**
75
     * NoCaptcha constructor.
76
     *
77
     * @param  string       $secret
78
     * @param  string       $siteKey
79
     * @param  string|null  $lang
80
     * @param  array        $attributes
81
     */
82 84
    public function __construct($secret, $siteKey, $lang = null, array $attributes = [])
83
    {
84 84
        $this->setSecret($secret);
85 84
        $this->setSiteKey($siteKey);
86 84
        $this->setLang($lang);
87
88 84
        $this->setRequestClient(new Request);
89 84
        $this->setAttributes(new Attributes($attributes));
90 84
    }
91
92
    /* ------------------------------------------------------------------------------------------------
93
     |  Getters & Setters
94
     | ------------------------------------------------------------------------------------------------
95
     */
96
    /**
97
     * Set the secret key.
98
     *
99
     * @param  string  $secret
100
     *
101
     * @return self
102
     */
103 84
    protected function setSecret($secret)
104
    {
105 84
        $this->checkKey('secret key', $secret);
106
107 84
        $this->secret = $secret;
108
109 84
        return $this;
110
    }
111
112
    /**
113
     * Set Site key.
114
     *
115
     * @param  string  $siteKey
116
     *
117
     * @return self
118
     */
119 84
    protected function setSiteKey($siteKey)
120
    {
121 84
        $this->checkKey('site key', $siteKey);
122
123 84
        $this->siteKey = $siteKey;
124
125 84
        return $this;
126
    }
127
128
    /**
129
     * Set language code.
130
     *
131
     * @param  string  $lang
132
     *
133
     * @return self
134
     */
135 84
    public function setLang($lang)
136
    {
137 84
        $this->lang = $lang;
138
139 84
        return $this;
140
    }
141
142
    /**
143
     * Get script source link.
144
     *
145
     * @param  string|null  $callbackName
146
     *
147
     * @return string
148
     */
149 18
    private function getScriptSrc($callbackName = null)
150
    {
151 18
        $queries = [];
152
153 18
        if ($this->hasLang()) {
154 9
            array_set($queries, 'hl', $this->lang);
155 3
        }
156
157 18
        if ($this->hasCallbackName($callbackName)) {
158 3
            array_set($queries, 'onload', $callbackName);
159 3
            array_set($queries, 'render', 'explicit');
160 1
        }
161
162 18
        return static::CLIENT_URL . (count($queries) ? '?' . http_build_query($queries) : '');
163
    }
164
165
    /**
166
     * Set HTTP Request Client.
167
     *
168
     * @param  \Arcanedev\NoCaptcha\Contracts\Utilities\RequestInterface  $request
169
     *
170
     * @return self
171
     */
172 84
    public function setRequestClient(
173
        Contracts\Utilities\RequestInterface $request
174
    ) {
175 84
        $this->request = $request;
176
177 84
        return $this;
178
    }
179
180
    /**
181
     * Set noCaptcha Attributes.
182
     *
183
     * @param  \Arcanedev\NoCaptcha\Contracts\Utilities\AttributesInterface  $attributes
184
     *
185
     * @return self
186
     */
187 84
    public function setAttributes(
188
        Contracts\Utilities\AttributesInterface $attributes
189
    ) {
190 84
        $this->attributes = $attributes;
191
192 84
        return $this;
193
    }
194
195
    /* ------------------------------------------------------------------------------------------------
196
     |  Main Functions
197
     | ------------------------------------------------------------------------------------------------
198
     */
199
    /**
200
     * Display Captcha.
201
     *
202
     * @param  string|null  $name
203
     * @param  array        $attributes
204
     *
205
     * @return string
206
     */
207 33
    public function display($name = null, array $attributes = [])
208
    {
209 33
        $attributes = $this->attributes->build($this->siteKey, array_merge(
210 33
            $this->attributes->prepareNameAttribute($name),
211
            $attributes
212 8
        ));
213
214 24
        return '<div '.$attributes.'></div>';
215
    }
216
217
    /**
218
     * Display image Captcha.
219
     *
220
     * @param  string|null  $name
221
     * @param  array        $attributes
222
     *
223
     * @return string
224
     */
225 9
    public function image($name = null, array $attributes = [])
226
    {
227 9
        return $this->display(
228 9
            $name, array_merge($attributes, $this->attributes->getImageAttribute())
229 3
        );
230
    }
231
232
    /**
233
     * Display audio Captcha.
234
     *
235
     * @param  string|null  $name
236
     * @param  array        $attributes
237
     *
238
     * @return string
239
     */
240 9
    public function audio($name = null, array $attributes = [])
241
    {
242 9
        return $this->display(
243 9
            $name, array_merge($attributes, $this->attributes->getAudioAttribute())
244 3
        );
245
    }
246
247
    /**
248
     * Display an invisible Captcha (bind the challenge to a button).
249
     *
250
     * @param  string  $value
251
     * @param  array   $attributes
252
     *
253
     * @return string
254
     */
255 3
    public function button($value, array $attributes = [])
256
    {
257 3
        $attributes = $this->attributes->build($this->siteKey, array_merge([
258 3
            'data-callback' => 'onSubmit',
259 1
        ], $attributes));
260
261 3
        return '<button '.$attributes.'>'.$value.'</button>';
262
    }
263
264
    /**
265
     * Verify Response.
266
     *
267
     * @param  string  $response
268
     * @param  string  $clientIp
269
     *
270
     * @return bool
271
     */
272 18
    public function verify($response, $clientIp = null)
273
    {
274 18
        if (empty($response)) return false;
275
276 18
        $response = $this->sendVerifyRequest([
277 18
            'secret'   => $this->secret,
278 18
            'response' => $response,
279 12
            'remoteip' => $clientIp
280 6
        ]);
281
282 18
        return isset($response['success']) && $response['success'] === true;
283
    }
284
285
    /**
286
     * Calls the reCAPTCHA siteverify API to verify whether the user passes CAPTCHA
287
     * test using a PSR-7 ServerRequest object.
288
     *
289
     * @param  \Psr\Http\Message\ServerRequestInterface  $request
290
     *
291
     * @return bool
292
     */
293 3
    public function verifyRequest(ServerRequestInterface $request)
294
    {
295 3
        $body   = $request->getParsedBody();
296 3
        $server = $request->getServerParams();
297
298 3
        $response = isset($body[self::CAPTCHA_NAME])
299 3
            ? $body[self::CAPTCHA_NAME]
300 3
            : '';
301
302 3
        $remoteIp = isset($server['REMOTE_ADDR'])
303 3
            ? $server['REMOTE_ADDR']
304 3
            : null;
305
306 3
        return $this->verify($response, $remoteIp);
307
    }
308
309
    /**
310
     * Get script tag.
311
     *
312
     * @param  string|null  $callbackName
313
     *
314
     * @return string
315
     */
316 18
    public function script($callbackName = null)
317
    {
318 18
        $script = '';
319
320 18
        if ( ! $this->scriptLoaded) {
321 18
            $script = '<script src="'.$this->getScriptSrc($callbackName).'" async defer></script>';
322 18
            $this->scriptLoaded = true;
323 6
        }
324
325 18
        return $script;
326
    }
327
328
    /**
329
     * Get script tag with a callback function.
330
     *
331
     * @param  array   $captchas
332
     * @param  string  $callbackName
333
     *
334
     * @return string
335
     */
336 3
    public function scriptWithCallback(array $captchas, $callbackName = 'captchaRenderCallback')
337
    {
338 3
        $script = $this->script($callbackName);
339
340 3
        if (empty($script) || empty($captchas)) {
341 3
            return $script;
342
        }
343
344 3
        return implode(PHP_EOL, [implode(PHP_EOL, [
345 3
            '<script>',
346 3
                "var $callbackName = function() {",
347 3
                    $this->renderCaptchas($captchas),
348 3
                '};',
349 2
            '</script>'
350 3
        ]), $script]);
351
    }
352
353
    /**
354
     * Rendering captchas with callback function.
355
     *
356
     * @param  array  $captchas
357
     *
358
     * @return string
359
     */
360
    private function renderCaptchas(array $captchas)
361
    {
362 3
        return implode(PHP_EOL, array_map(function($captcha) {
363 3
            return "if (document.getElementById('$captcha')) { grecaptcha.render('$captcha', {'sitekey' : '{$this->siteKey}'}); }";
364 3
        }, $captchas));
365
    }
366
367
    /* ------------------------------------------------------------------------------------------------
368
     |  Check Functions
369
     | ------------------------------------------------------------------------------------------------
370
     */
371
    /**
372
     * Check if has lang.
373
     *
374
     * @return bool
375
     */
376 18
    private function hasLang()
377
    {
378 18
        return ! empty($this->lang);
379
    }
380
381
    /**
382
     * Check if callback is not empty.
383
     *
384
     * @param  string|null  $callbackName
385
     *
386
     * @return bool
387
     */
388 18
    private function hasCallbackName($callbackName)
389
    {
390 18
        return ! (is_null($callbackName) || trim($callbackName) === '');
391
    }
392
393
    /**
394
     * Check key.
395
     *
396
     * @param  string  $name
397
     * @param  string  $value
398
     *
399
     * @throws \Arcanedev\NoCaptcha\Exceptions\ApiException
400
     */
401 84
    private function checkKey($name, &$value)
402
    {
403 84
        $this->checkIsString($name, $value);
404
405 84
        $value = trim($value);
406
407 84
        $this->checkIsNotEmpty($name, $value);
408 84
    }
409
410
    /**
411
     * Check if the value is a string value.
412
     *
413
     * @param  string  $name
414
     * @param  string  $value
415
     *
416
     * @throws \Arcanedev\NoCaptcha\Exceptions\ApiException
417
     */
418 84
    private function checkIsString($name, $value)
419
    {
420 84
        if ( ! is_string($value)) {
421 6
            throw new Exceptions\ApiException(
422 6
                "The {$name} must be a string value, ".gettype($value).' given.'
423 2
            );
424
        }
425 84
    }
426
427
    /**
428
     * Check if the value is not empty.
429
     *
430
     * @param string  $name
431
     * @param string  $value
432
     *
433
     * @throws \Arcanedev\NoCaptcha\Exceptions\ApiException
434
     */
435 84
    private function checkIsNotEmpty($name, $value)
436
    {
437 84
        if (empty($value)) {
438 6
            throw new Exceptions\ApiException("The {$name} must not be empty");
439
        }
440 84
    }
441
442
    /* ------------------------------------------------------------------------------------------------
443
     |  Other functions
444
     | ------------------------------------------------------------------------------------------------
445
     */
446
    /**
447
     * Send verify request to API and get response.
448
     *
449
     * @param  array  $query
450
     *
451
     * @return array
452
     */
453 18
    private function sendVerifyRequest(array $query = [])
454
    {
455 18
        $query    = array_filter($query);
456 18
        $url      = static::VERIFY_URL . '?' . http_build_query($query);
457 18
        $response = $this->request->send($url);
458
459 18
        return $response;
460
    }
461
}
462