Completed
Push — master ( 535418...acf734 )
by ARCANEDEV
11s
created

NoCaptcha::getApiScript()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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