Completed
Pull Request — master (#60)
by ARCANEDEV
05:11 queued 03:57
created

NoCaptcha::button()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 10
ccs 6
cts 6
cp 1
rs 9.4285
cc 1
eloc 6
nc 1
nop 2
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 54
    public function __construct($secret, $siteKey, $lang = null, array $attributes = [])
87
    {
88 54
        $this->setSecret($secret);
89 54
        $this->setSiteKey($siteKey);
90 54
        $this->setLang($lang);
91
92 54
        $this->setRequestClient(new Request);
93 54
        $this->setAttributes(new Attributes($attributes));
94 54
    }
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 54
    protected function setSecret($secret)
109
    {
110 54
        $this->checkKey('secret key', $secret);
111
112 54
        $this->secret = $secret;
113
114 54
        return $this;
115
    }
116
117
    /**
118
     * Set Site key.
119
     *
120
     * @param  string  $siteKey
121
     *
122
     * @return self
123
     */
124 54
    protected function setSiteKey($siteKey)
125
    {
126 54
        $this->checkKey('site key', $siteKey);
127
128 54
        $this->siteKey = $siteKey;
129
130 54
        return $this;
131
    }
132
133
    /**
134
     * Set language code.
135
     *
136
     * @param  string  $lang
137
     *
138
     * @return self
139
     */
140 54
    public function setLang($lang)
141
    {
142 54
        $this->lang = $lang;
143
144 54
        return $this;
145
    }
146
147
    /**
148
     * Get script source link.
149
     *
150
     * @param  string|null  $callbackName
151
     *
152
     * @return string
153
     */
154 12
    private function getScriptSrc($callbackName = null)
155
    {
156 12
        $queries = [];
157
158 12
        if ($this->hasLang())
159 6
            array_set($queries, 'hl', $this->lang);
160
161 12
        if ($this->hasCallbackName($callbackName)) {
162 2
            array_set($queries, 'onload', $callbackName);
163 2
            array_set($queries, 'render', 'explicit');
164
        }
165
166 12
        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 54
    public function setRequestClient(Contracts\Utilities\RequestInterface $request)
177
    {
178 54
        $this->request = $request;
179
180 54
        return $this;
181
    }
182
183
    /**
184
     * Set noCaptcha Attributes.
185
     *
186
     * @param  \Arcanedev\NoCaptcha\Contracts\Utilities\AttributesInterface  $attributes
187
     *
188
     * @return self
189
     */
190 54
    public function setAttributes(Contracts\Utilities\AttributesInterface $attributes)
191
    {
192 54
        $this->attributes = $attributes;
193
194 54
        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 22
    public function display($name = null, array $attributes = [])
211
    {
212 22
        $attributes = $this->attributes->build($this->siteKey, array_merge(
213 22
            $this->attributes->prepareNameAttribute($name),
214 16
            $attributes
215
        ));
216
217 16
        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 6
    public function image($name = null, array $attributes = [])
229
    {
230 6
        return $this->display(
231 6
            $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 6
    public function audio($name = null, array $attributes = [])
244
    {
245 6
        return $this->display(
246 6
            $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 2
    public function button($value, array $attributes = [])
259
    {
260 2
        $attributes = $this->attributes->build($this->siteKey, array_merge([
261 2
            'data-callback' => 'onSubmit',
262 2
        ], $attributes));
263
264 2
        return $this->toHtmlString(
265 2
            '<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 10
    public function verify($response, $clientIp = null)
278
    {
279 10
        if (empty($response)) return false;
280
281 10
        $response = $this->sendVerifyRequest([
282 10
            'secret'   => $this->secret,
283 10
            'response' => $response,
284 10
            'remoteip' => $clientIp
285
        ]);
286
287 10
        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 2
    public function verifyRequest(ServerRequestInterface $request)
299
    {
300 2
        $body   = $request->getParsedBody();
301 2
        $server = $request->getServerParams();
302
303 2
        return $this->verify(
304 2
            $body[self::CAPTCHA_NAME] ?? '',
305 2
            $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 12
    public function script($callbackName = null)
317
    {
318 12
        $script = '';
319
320 12
        if ( ! $this->scriptLoaded) {
321 12
            $script = '<script src="'.$this->getScriptSrc($callbackName).'" async defer></script>';
322 12
            $this->scriptLoaded = true;
323
        }
324
325 12
        return $this->toHtmlString($script);
326
    }
327
328
    /**
329
     * Get script tag with a callback function.
330
     *
331
     * @param  array   $captchas
332
     * @param  string  $callbackName
333
     *
334
     * @return \Illuminate\Support\HtmlString
335
     */
336 2
    public function scriptWithCallback(array $captchas, $callbackName = 'captchaRenderCallback')
337
    {
338 2
        $script = $this->script($callbackName)->toHtml();
339
340 2
        if ( ! empty($script) && ! empty($captchas)) {
341 2
            $script = implode(PHP_EOL, [implode(PHP_EOL, [
342 2
                '<script>',
343 2
                    "var $callbackName = function() {",
344 2
                        $this->renderCaptchas($captchas),
345 2
                    '};',
346 2
                '</script>'
347 2
            ]), $script]);
348
        }
349
350 2
        return $this->toHtmlString($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 2
        return implode(PHP_EOL, array_map(function($captcha) {
363 2
            return "if (document.getElementById('$captcha')) { grecaptcha.render('$captcha', {'sitekey' : '{$this->siteKey}'}); }";
364 2
        }, $captchas));
365
    }
366
367
    /* -----------------------------------------------------------------
368
     |  Check Methods
369
     | -----------------------------------------------------------------
370
     */
371
372
    /**
373
     * Check if has lang.
374
     *
375
     * @return bool
376
     */
377 12
    private function hasLang()
378
    {
379 12
        return ! empty($this->lang);
380
    }
381
382
    /**
383
     * Check if callback is not empty.
384
     *
385
     * @param  string|null  $callbackName
386
     *
387
     * @return bool
388
     */
389 12
    private function hasCallbackName($callbackName)
390
    {
391 12
        return ! (is_null($callbackName) || trim($callbackName) === '');
392
    }
393
394
    /**
395
     * Check key.
396
     *
397
     * @param  string  $name
398
     * @param  string  $value
399
     *
400
     * @throws \Arcanedev\NoCaptcha\Exceptions\ApiException
401
     */
402 54
    private function checkKey($name, &$value)
403
    {
404 54
        $this->checkIsString($name, $value);
405
406 54
        $value = trim($value);
407
408 54
        $this->checkIsNotEmpty($name, $value);
409 54
    }
410
411
    /**
412
     * Check if the value is a string value.
413
     *
414
     * @param  string  $name
415
     * @param  string  $value
416
     *
417
     * @throws \Arcanedev\NoCaptcha\Exceptions\ApiException
418
     */
419 54
    private function checkIsString($name, $value)
420
    {
421 54
        if ( ! is_string($value)) {
422 4
            throw new Exceptions\ApiException(
423 4
                "The {$name} must be a string value, ".gettype($value).' given.'
424
            );
425
        }
426 54
    }
427
428
    /**
429
     * Check if the value is not empty.
430
     *
431
     * @param string  $name
432
     * @param string  $value
433
     *
434
     * @throws \Arcanedev\NoCaptcha\Exceptions\ApiException
435
     */
436 54
    private function checkIsNotEmpty($name, $value)
437
    {
438 54
        if (empty($value)) {
439 4
            throw new Exceptions\ApiException("The {$name} must not be empty");
440
        }
441 54
    }
442
443
    /* ------------------------------------------------------------------------------------------------
444
     |  Other Methods
445
     | ------------------------------------------------------------------------------------------------
446
     */
447
448
    /**
449
     * Send verify request to API and get response.
450
     *
451
     * @param  array  $query
452
     *
453
     * @return array
454
     */
455 10
    private function sendVerifyRequest(array $query = [])
456
    {
457 10
        $query    = array_filter($query);
458 10
        $url      = static::VERIFY_URL . '?' . http_build_query($query);
459 10
        $response = $this->request->send($url);
460
461 10
        return $response;
462
    }
463
464
    /**
465
     * Transform the string to an Html serializable object
466
     *
467
     * @param  string  $html
468
     *
469
     * @return \Illuminate\Support\HtmlString
470
     */
471 30
    protected function toHtmlString($html)
472
    {
473 30
        return new HtmlString($html);
474
    }
475
}
476