Completed
Pull Request — master (#29)
by
unknown
08:35
created

NoCaptcha::renderCaptchas()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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