Completed
Push — master ( b91c46...cf06f8 )
by ARCANEDEV
7s
created

NoCaptcha::setSiteKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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