Completed
Push — master ( 7b9b82...f4954a )
by Roberto
07:53 queued 11s
created

ReCaptchaBuilder::validate()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 55
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 8.4551

Importance

Changes 6
Bugs 0 Features 1
Metric Value
cc 8
eloc 32
c 6
b 0
f 1
nc 10
nop 1
dl 0
loc 55
rs 8.1635
ccs 21
cts 26
cp 0.8077
crap 8.4551

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 Exception;
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
	 * The Site key
35
	 * please visit https://developers.google.com/recaptcha/docs/start
36
	 * @var string
37
	 */
38
	protected $api_site_key;
39
40
	/**
41
	 * The Secret key
42
	 * please visit https://developers.google.com/recaptcha/docs/start
43
	 * @var string
44
	 */
45
	protected $api_secret_key;
46
47
	/**
48
	 * The chosen ReCAPTCHA version
49
	 * please visit https://developers.google.com/recaptcha/docs/start
50
	 * @var string
51
	 */
52
	protected $version;
53
54
	/**
55
	 * Whether is true the ReCAPTCHA is inactive
56
	 * @var boolean
57
	 */
58
	protected $skip_by_ip = false;
59
60
	/**
61
	 * The API request URI
62
	 */
63
	protected $api_url = 'https://www.google.com/recaptcha/api/siteverify';
64
65
	/**
66
	 * ReCaptchaBuilder constructor.
67
	 *
68
	 * @param string      $api_site_key
69
	 * @param string      $api_secret_key
70
	 * @param null|string $version
71
	 */
72 31
	public function __construct(
73
		string $api_site_key,
74
		string $api_secret_key,
75
		?string $version = self::DEFAULT_API_VERSION
76
	) {
77
78 31
		$this->setApiSiteKey($api_site_key);
79 31
		$this->setApiSecretKey($api_secret_key);
80 31
		$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

80
		$this->setVersion(/** @scrutinizer ignore-type */ $version);
Loading history...
81 31
		$this->setSkipByIp($this->skipByIp());
82 31
	}
83
84
	/**
85
	 * @param string $api_site_key
86
	 *
87
	 * @return ReCaptchaBuilder
88
	 */
89 31
	public function setApiSiteKey(string $api_site_key): ReCaptchaBuilder
90
	{
91
92 31
		$this->api_site_key = $api_site_key;
93
94 31
		return $this;
95
	}
96
97
	/**
98
	 * @param string $api_secret_key
99
	 *
100
	 * @return ReCaptchaBuilder
101
	 */
102 31
	public function setApiSecretKey(string $api_secret_key): ReCaptchaBuilder
103
	{
104
105 31
		$this->api_secret_key = $api_secret_key;
106
107 31
		return $this;
108
	}
109
110
	/**
111
	 * @return int
112
	 */
113 4
	public function getCurlTimeout(): int
114
	{
115
116 4
		return config('recaptcha.curl_timeout', self::DEFAULT_CURL_TIMEOUT);
117
	}
118
119
	/**
120
	 * @param string $version
121
	 *
122
	 * @return ReCaptchaBuilder
123
	 */
124 31
	public function setVersion(string $version): ReCaptchaBuilder
125
	{
126
127 31
		$this->version = $version;
128
129 31
		return $this;
130
	}
131
132
	/**
133
	 * @return string
134
	 */
135 1
	public function getVersion(): string
136
	{
137
138 1
		return $this->version;
139
	}
140
141
	/**
142
	 * @param bool $skip_by_ip
143
	 *
144
	 * @return ReCaptchaBuilder
145
	 */
146 31
	public function setSkipByIp(bool $skip_by_ip): ReCaptchaBuilder
147
	{
148
149 31
		$this->skip_by_ip = $skip_by_ip;
150
151 31
		return $this;
152
	}
153
154
	/**
155
	 * @return array|mixed
156
	 */
157 31
	public function getIpWhitelist()
158
	{
159
160 31
		$whitelist = config('recaptcha.skip_ip', []);
161
162 31
		if (!is_array($whitelist)) {
163 4
			$whitelist = explode(',', $whitelist);
164
		}
165
166 31
		return $whitelist;
167
	}
168
169
	/**
170
	 * Checks whether the user IP address is among IPs "to be skipped"
171
	 *
172
	 * @return boolean
173
	 */
174 31
	public function skipByIp(): bool
175
	{
176
177 31
		return (in_array(request()->ip(), $this->getIpWhitelist()));
178
	}
179
180
	/**
181
	 * Write script HTML tag in you HTML code
182
	 * Insert before </head> tag
183
	 *
184
	 * @param string|null $formId
185
	 * @param array|null  $configuration
186
	 *
187
	 * @return string
188
	 * @throws Exception
189
	 */
190 8
	public function htmlScriptTagJsApi(?string $formId = '', ?array $configuration = []): string
191
	{
192
193 8
		if ($this->skip_by_ip) {
194
			return '';
195
		}
196
197 8
		switch ($this->version) {
198 8
			case 'v3':
199 5
				$html = "<script src=\"https://www.google.com/recaptcha/api.js?render={$this->api_site_key}\"></script>";
200 5
				break;
201
			default:
202 3
				$language = config('recaptcha.default_language', null);
203 3
				$query = ($language) ? "?hl=" . $language : "";
204 3
				$html = "<script src=\"https://www.google.com/recaptcha/api.js" . $query . "\" async defer></script>";
205
		}
206
207 8
		if ($this->version == 'invisible') {
208 1
			if (!$formId) {
209 1
				throw new Exception("formId required", 1);
210
			}
211
			$html .= '<script>
212
		       function biscolabLaravelReCaptcha(token) {
213
		         document.getElementById("' . $formId . '").submit();
214
		       }
215
		     </script>';
216 7
		} elseif ($this->version == 'v3') {
217
218 5
			$action = Arr::get($configuration, 'action', 'homepage');
219
220 5
			$js_custom_validation = Arr::get($configuration, 'custom_validation', '');
221
222
			// Check if set custom_validation. That function will override default fetch validation function
223 5
			if ($js_custom_validation) {
224
225 1
				$validate_function = ($js_custom_validation) ? "{$js_custom_validation}(token);" : '';
226
			} else {
227
228 4
				$js_then_callback = Arr::get($configuration, 'callback_then', '');
229 4
				$js_callback_catch = Arr::get($configuration, 'callback_catch', '');
230
231 4
				$js_then_callback = ($js_then_callback) ? "{$js_then_callback}(response)" : '';
232 4
				$js_callback_catch = ($js_callback_catch) ? "{$js_callback_catch}(err)" : '';
233
234
				$validate_function = "
235 4
                fetch('/" . config('recaptcha.default_validation_route',
236 4
						'biscolab-recaptcha/validate') . "?" . config('recaptcha.default_token_parameter_name',
237 4
						'token') . "=' + token, {
238
                    headers: {
239
                        \"X-Requested-With\": \"XMLHttpRequest\",
240
                        \"X-CSRF-TOKEN\": csrfToken.content
241
                    }
242
                })
243
                .then(function(response) {
244 4
                   	{$js_then_callback}
245
                })
246
                .catch(function(err) {
247 4
                    {$js_callback_catch}
248
                });";
249
			}
250
251
			$html .= "<script>
252
                    var csrfToken = document.head.querySelector('meta[name=\"csrf-token\"]');
253
                  grecaptcha.ready(function() {
254 5
                      grecaptcha.execute('{$this->api_site_key}', {action: '{$action}'}).then(function(token) {
255 5
                        {$validate_function}
256
                      });
257
                  });
258
		     </script>";
259
		}
260
261 7
		return $html;
262
	}
263
264
	/**
265
	 * @param array|null $configuration
266
	 *
267
	 * @return string
268
	 */
269 5
	public function htmlScriptTagJsApiV3(?array $configuration = []): string
270
	{
271
272 5
		return $this->htmlScriptTagJsApi('', $configuration);
273
	}
274
275
	/**
276
	 * Call out to reCAPTCHA and process the response
277
	 *
278
	 * @param string $response
279
	 *
280
	 * @return boolean|array
281
	 */
282 3
	public function validate($response)
283
	{
284
285 3
		if ($this->skip_by_ip) {
286 2
			if ($this->returnArray()) {
287
				// Add 'skip_by_ip' field to response
288
				return [
289 1
					'skip_by_ip' => true,
290
					'score'      => 0.9,
291
					'success'    => true
292
				];
293
			}
294
295 1
			return true;
296
		}
297
298 1
		$params = http_build_query([
299 1
			'secret'   => $this->api_secret_key,
300 1
			'remoteip' => request()->getClientIp(),
301 1
			'response' => $response,
302
		]);
303
304 1
		$url = $this->api_url . '?' . $params;
305
306 1
		if (function_exists('curl_version')) {
307
308 1
			$curl = curl_init($url);
309 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

309
			curl_setopt(/** @scrutinizer ignore-type */ $curl, CURLOPT_HEADER, false);
Loading history...
310 1
			curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
311 1
			curl_setopt($curl, CURLOPT_TIMEOUT, $this->getCurlTimeout());
312 1
			curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
313 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

313
			$curl_response = curl_exec(/** @scrutinizer ignore-type */ $curl);
Loading history...
314
		} else {
315
			$curl_response = file_get_contents($url);
316
		}
317
318 1
		if (is_null($curl_response) || empty($curl_response)) {
319
			if ($this->returnArray()) {
320
				// Add 'error' field to response
321
				return [
322
					'error'   => 'cURL response empty',
323
					'score'   => 0.1,
324
					'success' => false
325
				];
326
			}
327
328
			return false;
329
		}
330 1
		$response = json_decode(trim($curl_response), true);
331
332 1
		if ($this->returnArray()) {
333 1
			return $response;
334
		}
335
336
		return $response['success'];
337
338
	}
339
340
	/**
341
	 * @return string
342
	 */
343 1
	public function getApiSiteKey(): string
344
	{
345
346 1
		return $this->api_site_key;
347
	}
348
349
	/**
350
	 * @return string
351
	 */
352 1
	public function getApiSecretKey(): string
353
	{
354
355 1
		return $this->api_secret_key;
356
	}
357
358
	/**
359
	 * @return bool
360
	 */
361 3
	protected function returnArray(): bool
362
	{
363
364 3
		return ($this->version == 'v3');
365
	}
366
}