Passed
Push — master ( b01395...c75806 )
by Roberto
05:23 queued 13s
created

ReCaptchaBuilder::htmlScriptTagJsApi()   A

Complexity

Conditions 6
Paths 16

Size

Total Lines 30
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6

Importance

Changes 6
Bugs 0 Features 1
Metric Value
cc 6
eloc 14
nc 16
nop 1
dl 0
loc 30
rs 9.2222
c 6
b 0
f 1
ccs 15
cts 15
cp 1
crap 6
1
<?php
2
/**
3
 * Copyright (c) 2017 - present
4
 * LaravelGoogleRecaptcha - ReCaptchaBuilder.php
5
 * author: Roberto Belotti - [email protected]
6
 * web : robertobelotti.com, github.com/biscolab
7
 * Initial version created on: 12/9/2018
8
 * MIT license: https://github.com/biscolab/laravel-recaptcha/blob/master/LICENSE
9
 */
10
11
namespace Biscolab\ReCaptcha;
12
13
use Illuminate\Support\Arr;
14
15
/**
16
 * Class ReCaptchaBuilder
17
 * @package Biscolab\ReCaptcha
18
 */
19
class ReCaptchaBuilder
20
{
21
22
    /**
23
     * @var string
24
     */
25
    const DEFAULT_API_VERSION = 'v2';
26
27
    /**
28
     * @var int
29
     */
30
    const DEFAULT_CURL_TIMEOUT = 10;
31
32
    /**
33
     * @var string
34
     */
35
    const DEFAULT_ONLOAD_JS_FUNCTION = 'biscolabOnloadCallback';
36
37
    /**
38
     * @var string
39
     */
40
    const DEFAULT_RECAPTCHA_RULE_NAME = 'recaptcha';
41
42
    /**
43
     * @var string
44
     */
45
    const DEFAULT_RECAPTCHA_FIELD_NAME = 'g-recaptcha-response';
46
47
    /**
48
     * The Site key
49
     * please visit https://developers.google.com/recaptcha/docs/start
50
     * @var string
51
     */
52
    protected $api_site_key;
53
54
    /**
55
     * The Secret key
56
     * please visit https://developers.google.com/recaptcha/docs/start
57
     * @var string
58
     */
59
    protected $api_secret_key;
60
61
    /**
62
     * The chosen ReCAPTCHA version
63
     * please visit https://developers.google.com/recaptcha/docs/start
64
     * @var string
65
     */
66
    protected $version;
67
68
    /**
69
     * Whether is true the ReCAPTCHA is inactive
70
     * @var boolean
71
     */
72
    protected $skip_by_ip = false;
73
74
    /**
75
     * The API request URI
76
     */
77
    protected $api_url = 'https://www.google.com/recaptcha/api/siteverify';
78
79
    /**
80
     * ReCaptchaBuilder constructor.
81
     *
82
     * @param string      $api_site_key
83
     * @param string      $api_secret_key
84
     * @param null|string $version
85
     */
86 42
    public function __construct(
87
        string $api_site_key,
88
        string $api_secret_key,
89
        ?string $version = self::DEFAULT_API_VERSION
90
    ) {
91
92 42
        $this->setApiSiteKey($api_site_key);
93 42
        $this->setApiSecretKey($api_secret_key);
94 42
        $this->setVersion($version);
0 ignored issues
show
Bug introduced by Roberto
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

94
        $this->setVersion(/** @scrutinizer ignore-type */ $version);
Loading history...
95 42
        $this->setSkipByIp($this->skipByIp());
96 42
    }
97
98
    /**
99
     * @param string $api_site_key
100
     *
101
     * @return ReCaptchaBuilder
102
     */
103 42
    public function setApiSiteKey(string $api_site_key): ReCaptchaBuilder
104
    {
105
106 42
        $this->api_site_key = $api_site_key;
107
108 42
        return $this;
109
    }
110
111
    /**
112
     * @param string $api_secret_key
113
     *
114
     * @return ReCaptchaBuilder
115
     */
116 42
    public function setApiSecretKey(string $api_secret_key): ReCaptchaBuilder
117
    {
118
119 42
        $this->api_secret_key = $api_secret_key;
120
121 42
        return $this;
122
    }
123
124
    /**
125
     * @return int
126
     */
127 4
    public function getCurlTimeout(): int
128
    {
129
130 4
        return config('recaptcha.curl_timeout', self::DEFAULT_CURL_TIMEOUT);
131
    }
132
133
    /**
134
     * @param string $version
135
     *
136
     * @return ReCaptchaBuilder
137
     */
138 42
    public function setVersion(string $version): ReCaptchaBuilder
139
    {
140
141 42
        $this->version = $version;
142
143 42
        return $this;
144
    }
145
146
    /**
147
     * @return string
148
     */
149 1
    public function getVersion(): string
150
    {
151
152 1
        return $this->version;
153
    }
154
155
    /**
156
     * @param bool $skip_by_ip
157
     *
158
     * @return ReCaptchaBuilder
159
     */
160 42
    public function setSkipByIp(bool $skip_by_ip): ReCaptchaBuilder
161
    {
162
163 42
        $this->skip_by_ip = $skip_by_ip;
164
165 42
        return $this;
166
    }
167
168
    /**
169
     * @return array|mixed
170
     */
171 42
    public function getIpWhitelist()
172
    {
173
174 42
        $whitelist = config('recaptcha.skip_ip', []);
175
176 42
        if (!is_array($whitelist)) {
177 4
            $whitelist = explode(',', $whitelist);
178
        }
179
180
        $whitelist = array_map(function ($item) {
181
182 4
            return trim($item);
183 42
        }, $whitelist);
184
185 42
        return $whitelist;
186
    }
187
188
    /**
189
     * Checks whether the user IP address is among IPs "to be skipped"
190
     *
191
     * @return boolean
192
     */
193 42
    public function skipByIp(): bool
194
    {
195
196 42
        return (in_array(request()->ip(), $this->getIpWhitelist()));
197
    }
198
199
    /**
200
     * Write script HTML tag in you HTML code
201
     * Insert before </head> tag
202
     *
203
     * @param array|null $configuration
204
     *
205
     * @return string
206
     * @throws \Exception
207
     */
208 4
    public function htmlScriptTagJsApi(?array $configuration = []): string
209
    {
210
211 4
        $query = [];
212 4
        $html = '';
213
214
        // Language: "hl" parameter
215
        // resources $configuration parameter overrides default language
216 4
        $language = Arr::get($configuration, 'resources');
217 4
        if (!$language) {
218 4
            $language = config('recaptcha.default_language', null);
219
        }
220 4
        if ($language) {
221 1
            Arr::set($query, 'hl', $language);
222
        }
223
224
        // Onload JS callback function: "onload" parameter
225
        // "render" parameter set to "explicit"
226 4
        if (config('recaptcha.explicit', null) && $this->version === 'v2') {
227 2
            Arr::set($query, 'render', 'explicit');
228 2
            Arr::set($query, 'onload', self::DEFAULT_ONLOAD_JS_FUNCTION);
229
230 2
            $html = $this->getOnLoadCallback();
0 ignored issues
show
Bug introduced by Roberto
The method getOnLoadCallback() does not exist on Biscolab\ReCaptcha\ReCaptchaBuilder. It seems like you code against a sub-type of Biscolab\ReCaptcha\ReCaptchaBuilder such as Biscolab\ReCaptcha\ReCaptchaBuilderV2. ( Ignorable by Annotation )

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

230
            /** @scrutinizer ignore-call */ 
231
            $html = $this->getOnLoadCallback();
Loading history...
231
        }
232
233
        // Create query string
234 3
        $query = ($query) ? '?' . http_build_query($query) : "";
235 3
        $html .= "<script src=\"https://www.google.com/recaptcha/api.js" . $query . "\" async defer></script>";
236
237 3
        return $html;
238
    }
239
240
    /**
241
     * Call out to reCAPTCHA and process the response
242
     *
243
     * @param string $response
244
     *
245
     * @return boolean|array
246
     */
247 3
    public function validate($response)
248
    {
249
250 3
        if ($this->skip_by_ip) {
251 2
            if ($this->returnArray()) {
252
                // Add 'skip_by_ip' field to response
253
                return [
254 1
                    'skip_by_ip' => true,
255
                    'score'      => 0.9,
256
                    'success'    => true
257
                ];
258
            }
259
260 1
            return true;
261
        }
262
263 1
        $params = http_build_query([
264 1
            'secret'   => $this->api_secret_key,
265 1
            'remoteip' => request()->getClientIp(),
266 1
            'response' => $response,
267
        ]);
268
269 1
        $url = $this->api_url . '?' . $params;
270
271 1
        if (function_exists('curl_version')) {
272
273 1
            $curl = curl_init($url);
274 1
            curl_setopt($curl, CURLOPT_HEADER, false);
0 ignored issues
show
Bug introduced by Roberto
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

274
            curl_setopt(/** @scrutinizer ignore-type */ $curl, CURLOPT_HEADER, false);
Loading history...
275 1
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
276 1
            curl_setopt($curl, CURLOPT_TIMEOUT, $this->getCurlTimeout());
277 1
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
278 1
            $curl_response = curl_exec($curl);
0 ignored issues
show
Bug introduced by Roberto
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

278
            $curl_response = curl_exec(/** @scrutinizer ignore-type */ $curl);
Loading history...
279
        } else {
280
            $curl_response = file_get_contents($url);
281
        }
282
283 1
        if (is_null($curl_response) || empty($curl_response)) {
284
            if ($this->returnArray()) {
285
                // Add 'error' field to response
286
                return [
287
                    'error'   => 'cURL response empty',
288
                    'score'   => 0.1,
289
                    'success' => false
290
                ];
291
            }
292
293
            return false;
294
        }
295 1
        $response = json_decode(trim($curl_response), true);
296
297 1
        if ($this->returnArray()) {
298 1
            return $response;
299
        }
300
301
        return $response['success'];
302
303
    }
304
305
    /**
306
     * @return string
307
     */
308 1
    public function getApiSiteKey(): string
309
    {
310
311 1
        return $this->api_site_key;
312
    }
313
314
    /**
315
     * @return string
316
     */
317 1
    public function getApiSecretKey(): string
318
    {
319
320 1
        return $this->api_secret_key;
321
    }
322
323
    /**
324
     * @return bool
325
     */
326 3
    protected function returnArray(): bool
327
    {
328
329 3
        return ($this->version == 'v3');
330
    }
331
}