Passed
Branch master (47c941)
by Roberto
05:04
created

ReCaptchaBuilder::setApiUrls()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 7
rs 10
ccs 4
cts 4
cp 1
crap 1
1
<?php
2
3
/**
4
 * Copyright (c) 2017 - present
5
 * LaravelGoogleRecaptcha - ReCaptchaBuilder.php
6
 * author: Roberto Belotti - [email protected]
7
 * web : robertobelotti.com, github.com/biscolab
8
 * Initial version created on: 12/9/2018
9
 * MIT license: https://github.com/biscolab/laravel-recaptcha/blob/master/LICENSE
10
 */
11
12
namespace Biscolab\ReCaptcha;
13
14
use Illuminate\Support\Arr;
15
16
/**
17
 * Class ReCaptchaBuilder
18
 * @package Biscolab\ReCaptcha
19
 */
20
class ReCaptchaBuilder
21
{
22
23
    /**
24
     * @var string
25
     */
26
    const DEFAULT_API_VERSION = 'v2';
27
28
    /**
29
     * @var int
30
     */
31
    const DEFAULT_CURL_TIMEOUT = 10;
32
33
    /**
34
     * @var string
35
     */
36
    const DEFAULT_ONLOAD_JS_FUNCTION = 'biscolabOnloadCallback';
37
38
    /**
39
     * @var string
40
     */
41
    const DEFAULT_RECAPTCHA_RULE_NAME = 'recaptcha';
42
43
    /**
44
     * @var string
45
     */
46
    const DEFAULT_RECAPTCHA_FIELD_NAME = 'g-recaptcha-response';
47
48
    /**
49
     * @var string
50
     */
51
    const DEFAULT_RECAPTCHA_API_DOMAIN = 'www.google.com';
52
53
    /**
54
     * The Site key
55
     * please visit https://developers.google.com/recaptcha/docs/start
56
     * @var string
57
     */
58
    protected $api_site_key;
59
60
    /**
61
     * The Secret key
62
     * please visit https://developers.google.com/recaptcha/docs/start
63
     * @var string
64
     */
65
    protected $api_secret_key;
66
67
    /**
68
     * The chosen ReCAPTCHA version
69
     * please visit https://developers.google.com/recaptcha/docs/start
70
     * @var string
71
     */
72
    protected $version;
73
74
    /**
75
     * Whether is true the ReCAPTCHA is inactive
76
     * @var boolean
77
     */
78
    protected $skip_by_ip = false;
79
80
    /**
81
     * The API domain (default: retrieved from config file)
82
     * @var string
83
     */
84
    protected $api_domain = '';
85
86
    /**
87
     * The API request URI
88
     * @var string
89
     */
90
    protected $api_url = '';
91
92
    /**
93
     * The URI of the API Javascript file to embed in you pages
94
     * @var string
95
     */
96
    protected $api_js_url = '';
97
98
    /**
99
     * ReCaptchaBuilder constructor.
100
     *
101
     * @param string      $api_site_key
102
     * @param string      $api_secret_key
103
     * @param null|string $version
104
     */
105 46
    public function __construct(
106
        string $api_site_key,
107
        string $api_secret_key,
108
        ?string $version = self::DEFAULT_API_VERSION
109
    ) {
110
111 46
        $this->setApiSiteKey($api_site_key);
112 46
        $this->setApiSecretKey($api_secret_key);
113 46
        $this->setVersion($version);
0 ignored issues
show
Bug introduced by
It seems like $version can also be of type null; however, parameter $version of Biscolab\ReCaptcha\ReCaptchaBuilder::setVersion() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

113
        $this->setVersion(/** @scrutinizer ignore-type */ $version);
Loading history...
114 46
        $this->setSkipByIp($this->skipByIp());
115 46
        $this->setApiDomain();
116 46
        $this->setApiUrls();
117 46
    }
118
119
    /**
120
     * @param string $api_site_key
121
     *
122
     * @return ReCaptchaBuilder
123
     */
124 46
    public function setApiSiteKey(string $api_site_key): ReCaptchaBuilder
125
    {
126
127 46
        $this->api_site_key = $api_site_key;
128
129 46
        return $this;
130
    }
131
132
    /**
133
     * @param string $api_secret_key
134
     *
135
     * @return ReCaptchaBuilder
136
     */
137 46
    public function setApiSecretKey(string $api_secret_key): ReCaptchaBuilder
138
    {
139
140 46
        $this->api_secret_key = $api_secret_key;
141
142 46
        return $this;
143
    }
144
145
    /**
146
     * @return int
147
     */
148 4
    public function getCurlTimeout(): int
149
    {
150
151 4
        return config('recaptcha.curl_timeout', self::DEFAULT_CURL_TIMEOUT);
152
    }
153
154
    /**
155
     * @param string $version
156
     *
157
     * @return ReCaptchaBuilder
158
     */
159 46
    public function setVersion(string $version): ReCaptchaBuilder
160
    {
161
162 46
        $this->version = $version;
163
164 46
        return $this;
165
    }
166
167
    /**
168
     * @return string
169
     */
170 1
    public function getVersion(): string
171
    {
172
173 1
        return $this->version;
174
    }
175
176
    /**
177
     * @param bool $skip_by_ip
178
     *
179
     * @return ReCaptchaBuilder
180
     */
181 46
    public function setSkipByIp(bool $skip_by_ip): ReCaptchaBuilder
182
    {
183
184 46
        $this->skip_by_ip = $skip_by_ip;
185
186 46
        return $this;
187
    }
188
189
    /**
190
     * @param null|string $api_domain
191
     *
192
     * @return ReCaptchaBuilder
193
     */
194 46
    public function setApiDomain(?string $api_domain = null): ReCaptchaBuilder
195
    {
196
197 46
        $this->api_domain = $api_domain ?? config('recaptcha.api_domain', self::DEFAULT_RECAPTCHA_API_DOMAIN);
198
199 46
        return $this;
200
    }
201
202
    /**
203
     * @return string
204
     */
205 2
    public function getApiDomain(): string
206
    {
207
208 2
        return $this->api_domain;
209
    }
210
211
    /**
212
     * @return ReCaptchaBuilder
213
     */
214 46
    public function setApiUrls(): ReCaptchaBuilder
215
    {
216
217 46
        $this->api_url = 'https://' . $this->api_domain . '/recaptcha/api/siteverify';
218 46
        $this->api_js_url = 'https://' . $this->api_domain . '/recaptcha/api.js';
219
220 46
        return $this;
221
    }
222
223
    /**
224
     * @return array|mixed
225
     */
226 46
    public function getIpWhitelist()
227
    {
228
229 46
        $whitelist = config('recaptcha.skip_ip', []);
230
231 46
        if (!is_array($whitelist)) {
232 4
            $whitelist = explode(',', $whitelist);
233
        }
234
235
        $whitelist = array_map(function ($item) {
236
237 4
            return trim($item);
238 46
        }, $whitelist);
239
240 46
        return $whitelist;
241
    }
242
243
    /**
244
     * Checks whether the user IP address is among IPs "to be skipped"
245
     *
246
     * @return boolean
247
     */
248 46
    public function skipByIp(): bool
249
    {
250
251 46
        return (in_array(request()->ip(), $this->getIpWhitelist()));
252
    }
253
254
    /**
255
     * Write script HTML tag in you HTML code
256
     * Insert before </head> tag
257
     *
258
     * @param array|null $configuration
259
     *
260
     * @return string
261
     * @throws \Exception
262
     */
263 6
    public function htmlScriptTagJsApi(?array $configuration = []): string
264
    {
265
266 6
        $query = [];
267 6
        $html = '';
268
269
        // Language: "hl" parameter
270
        // resources $configuration parameter overrides default language
271 6
        $language = Arr::get($configuration, 'lang');
272 6
        if (!$language) {
273 5
            $language = config('recaptcha.default_language', null);
274
        }
275 6
        if ($language) {
276 2
            Arr::set($query, 'hl', $language);
277
        }
278
279
        // Onload JS callback function: "onload" parameter
280
        // "render" parameter set to "explicit"
281 6
        if (config('recaptcha.explicit', null) && $this->version === 'v2') {
282 2
            Arr::set($query, 'render', 'explicit');
283 2
            Arr::set($query, 'onload', self::DEFAULT_ONLOAD_JS_FUNCTION);
284
285
            /** @scrutinizer ignore-call */
286 2
            $html = $this->getOnLoadCallback();
287
        }
288
289
        // Create query string
290 5
        $query = ($query) ? '?' . http_build_query($query) : "";
291 5
        $html .= "<script src=\"" . $this->api_js_url .  $query . "\" async defer></script>";
292
293 5
        return $html;
294
    }
295
296
    /**
297
     * Call out to reCAPTCHA and process the response
298
     *
299
     * @param string $response
300
     *
301
     * @return boolean|array
302
     */
303 3
    public function validate($response)
304
    {
305
306 3
        if ($this->skip_by_ip) {
307 2
            if ($this->returnArray()) {
308
                // Add 'skip_by_ip' field to response
309
                return [
310 1
                    'skip_by_ip' => true,
311
                    'score'      => 0.9,
312
                    'success'    => true
313
                ];
314
            }
315
316 1
            return true;
317
        }
318
319 1
        $params = http_build_query([
320 1
            'secret'   => $this->api_secret_key,
321 1
            'remoteip' => request()->getClientIp(),
322 1
            'response' => $response,
323
        ]);
324
325 1
        $url = $this->api_url . '?' . $params;
326
327 1
        if (function_exists('curl_version')) {
328
329 1
            $curl = curl_init($url);
330 1
            curl_setopt($curl, CURLOPT_HEADER, false);
0 ignored issues
show
Bug introduced by
It seems like $curl can also be of type false; however, parameter $ch of curl_setopt() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

330
            curl_setopt(/** @scrutinizer ignore-type */ $curl, CURLOPT_HEADER, false);
Loading history...
331 1
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
332 1
            curl_setopt($curl, CURLOPT_TIMEOUT, $this->getCurlTimeout());
333 1
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
334 1
            $curl_response = curl_exec($curl);
0 ignored issues
show
Bug introduced by
It seems like $curl can also be of type false; however, parameter $ch of curl_exec() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

334
            $curl_response = curl_exec(/** @scrutinizer ignore-type */ $curl);
Loading history...
335
        } else {
336
            $curl_response = file_get_contents($url);
337
        }
338
339 1
        if (is_null($curl_response) || empty($curl_response)) {
340
            if ($this->returnArray()) {
341
                // Add 'error' field to response
342
                return [
343
                    'error'   => 'cURL response empty',
344
                    'score'   => 0.1,
345
                    'success' => false
346
                ];
347
            }
348
349
            return false;
350
        }
351 1
        $response = json_decode(trim($curl_response), true);
352
353 1
        if ($this->returnArray()) {
354 1
            return $response;
355
        }
356
357
        return $response['success'];
358
    }
359
360
    /**
361
     * @return string
362
     */
363 1
    public function getApiSiteKey(): string
364
    {
365
366 1
        return $this->api_site_key;
367
    }
368
369
    /**
370
     * @return string
371
     */
372 1
    public function getApiSecretKey(): string
373
    {
374
375 1
        return $this->api_secret_key;
376
    }
377
378
    /**
379
     * @return bool
380
     */
381 3
    protected function returnArray(): bool
382
    {
383
384 3
        return ($this->version == 'v3');
385
    }
386
}
387