Completed
Push — master ( b76060...cb204c )
by ARCANEDEV
9s
created

NoCaptcha::getNameAttribute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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