Completed
Pull Request — master (#23)
by Roberto
08:58
created

ReCaptchaBuilder::htmlScriptTagJsApiV3()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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
15
class ReCaptchaBuilder {
16
17
	/**
18
	 * The Site key
19
	 * please visit https://developers.google.com/recaptcha/docs/start
20
	 * @var string
21
	 */
22
	protected $api_site_key;
23
24
	/**
25
	 * The Secret key
26
	 * please visit https://developers.google.com/recaptcha/docs/start
27
	 * @var string
28
	 */
29
	protected $api_secret_key;
30
31
	/**
32
	 * The chosen ReCAPTCHA version
33
	 * please visit https://developers.google.com/recaptcha/docs/start
34
	 * @var string
35
	 */
36
	protected $version;
37
38
	/**
39
	 * The curl timeout
40
	 * please visit https://curl.haxx.se/libcurl/c/CURLOPT_TIMEOUT.html
41
	 * @var int
42
	 */
43
	protected $curl_timeout;
44
45
	/**
46
	 * Whether is true the ReCAPTCHA is inactive
47
	 * @var boolean
48
	 */
49
	protected $skip_by_ip = false;
50
51
	/**
52
	 * The API request URI
53
	 */
54
	protected $api_url = 'https://www.google.com/recaptcha/api/siteverify';
55
56 15
	public function __construct($api_site_key, $api_secret_key, $version = 'v2', $curl_timeout = 3) {
57
58 15
		$this->setApiSiteKey($api_site_key);
59 15
		$this->setApiSecretKey($api_secret_key);
60 15
		$this->setVersion($version);
61 15
		$this->setCurlTimeout($curl_timeout);
62 15
		$this->setSkipByIp($this->skipByIp());
63 15
	}
64
65
	/**
66
	 * @param string $api_site_key
67
	 *
68
	 * @return ReCaptchaBuilder
69
	 */
70 15
	public function setApiSiteKey(string $api_site_key): ReCaptchaBuilder {
71
72 15
		$this->api_site_key = $api_site_key;
73
74 15
		return $this;
75
	}
76
77
	/**
78
	 * @param string $api_secret_key
79
	 *
80
	 * @return ReCaptchaBuilder
81
	 */
82 15
	public function setApiSecretKey(string $api_secret_key): ReCaptchaBuilder {
83
84 15
		$this->api_secret_key = $api_secret_key;
85
86 15
		return $this;
87
	}
88
89
	/**
90
	 * @param int $curl_timeout
91
	 * @return ReCaptchaBuilder
92
	 */
93 15
	public function setCurlTimeout(int $curl_timeout): ReCaptchaBuilder {
94
95 15
		$this->curl_timeout = $curl_timeout;
96
97 15
		return $this;
98
	}
99
100
	/**
101
	 * @return int
102
	 */
103 1
	public function getCurlTimeout(): int {
104
105 1
		return $this->curl_timeout;
106
	}
107
108
	/**
109
	 * @param string $version
110
	 *
111
	 * @return ReCaptchaBuilder
112
	 */
113 15
	public function setVersion(string $version): ReCaptchaBuilder {
114
115 15
		$this->version = $version;
116
117 15
		return $this;
118
	}
119
120
	/**
121
	 * @return string
122
	 */
123 1
	public function getVersion(): string {
124
125 1
		return $this->version;
126
	}
127
128
	/**
129
	 * @param bool $skip_by_ip
130
	 *
131
	 * @return ReCaptchaBuilder
132
	 */
133 15
	public function setSkipByIp(bool $skip_by_ip): ReCaptchaBuilder {
134
135 15
		$this->skip_by_ip = $skip_by_ip;
136
137 15
		return $this;
138
	}
139
140
	/**
141
	 * @return array|mixed
142
	 */
143 15
	public function getIpWhitelist() {
144 15
		$whitelist = config('recaptcha.skip_ip', []);
145
146 15
		if(!is_array($whitelist)) {
147 2
			$whitelist = explode(',', $whitelist);
148
		}
149
150 15
		return $whitelist;
151
	}
152
153
	/**
154
	 * Checks whether the user IP address is among IPs "to be skipped"
155
	 *
156
	 * @return boolean
157
	 */
158 15
	public function skipByIp(): bool {
159
160 15
		return (in_array(request()->ip(), $this->getIpWhitelist()));
161
	}
162
163
	/**
164
	 * Write script HTML tag in you HTML code
165
	 * Insert before </head> tag
166
	 *
167
	 * @param string|null $formId
168
	 * @param array|null  $configuration
169
	 *
170
	 * @return string
171
	 * @throws Exception
172
	 */
173 6
	public function htmlScriptTagJsApi(?string $formId = '', ?array $configuration = []): string {
174
175 6
		if ($this->skip_by_ip) {
176
			return '';
177
		}
178
179 6
		switch ($this->version) {
180 6
			case 'v3':
181 5
				$html = "<script src=\"https://www.google.com/recaptcha/api.js?render={$this->api_site_key}\"></script>";
182 5
				break;
183
			default:
184 1
				$html = "<script src=\"https://www.google.com/recaptcha/api.js\" async defer></script>";
185
		}
186
187 6
		if ($this->version == 'invisible') {
188
			if (!$formId) {
189
				throw new Exception("formId required", 1);
190
			}
191
			$html .= '<script>
192
		       function biscolabLaravelReCaptcha(token) {
193
		         document.getElementById("' . $formId . '").submit();
194
		       }
195
		     </script>';
196
		}
197 6
		elseif ($this->version == 'v3') {
198
199 5
			$action = array_get($configuration, 'action', 'homepage');
0 ignored issues
show
Deprecated Code introduced by
The function array_get() has been deprecated: Arr::get() should be used directly instead. Will be removed in Laravel 5.9. ( Ignorable by Annotation )

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

199
			$action = /** @scrutinizer ignore-deprecated */ array_get($configuration, 'action', 'homepage');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
200
201 5
			$js_custom_validation = array_get($configuration, 'custom_validation', '');
0 ignored issues
show
Deprecated Code introduced by
The function array_get() has been deprecated: Arr::get() should be used directly instead. Will be removed in Laravel 5.9. ( Ignorable by Annotation )

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

201
			$js_custom_validation = /** @scrutinizer ignore-deprecated */ array_get($configuration, 'custom_validation', '');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
202
203
			// Check if set custom_validation. That function will override default fetch validation function
204 5
			if ($js_custom_validation) {
205
206 1
				$validate_function = ($js_custom_validation) ? "{$js_custom_validation}(token);" : '';
207
			}
208
			else {
209
210 4
				$js_then_callback = array_get($configuration, 'callback_then', '');
0 ignored issues
show
Deprecated Code introduced by
The function array_get() has been deprecated: Arr::get() should be used directly instead. Will be removed in Laravel 5.9. ( Ignorable by Annotation )

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

210
				$js_then_callback = /** @scrutinizer ignore-deprecated */ array_get($configuration, 'callback_then', '');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
211 4
				$js_callback_catch = array_get($configuration, 'callback_catch', '');
0 ignored issues
show
Deprecated Code introduced by
The function array_get() has been deprecated: Arr::get() should be used directly instead. Will be removed in Laravel 5.9. ( Ignorable by Annotation )

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

211
				$js_callback_catch = /** @scrutinizer ignore-deprecated */ array_get($configuration, 'callback_catch', '');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
212
213 4
				$js_then_callback = ($js_then_callback) ? "{$js_then_callback}(response)" : '';
214 4
				$js_callback_catch = ($js_callback_catch) ? "{$js_callback_catch}(err)" : '';
215
216
				$validate_function = "
217 4
                fetch('/" . config('recaptcha.default_validation_route', 'biscolab-recaptcha/validate') . "?" . config('recaptcha.default_token_parameter_name', 'token') . "=' + token, {
218
                    headers: {
219
                        \"X-Requested-With\": \"XMLHttpRequest\",
220
                        \"X-CSRF-TOKEN\": csrfToken.content
221
                    }
222
                })
223
                .then(function(response) {
224 4
                   	{$js_then_callback}
225
                })
226
                .catch(function(err) {
227 4
                    {$js_callback_catch}
228
                });";
229
			}
230
231
			$html .= "<script>
232
                    var csrfToken = document.head.querySelector('meta[name=\"csrf-token\"]');
233
                  grecaptcha.ready(function() {
234 5
                      grecaptcha.execute('{$this->api_site_key}', {action: '{$action}'}).then(function(token) {
235 5
                        {$validate_function}
236
                      });
237
                  });
238
		     </script>";
239
		}
240
241 6
		return $html;
242
	}
243
244
	/**
245
	 * @param array|null $configuration
246
	 *
247
	 * @return string
248
	 */
249 5
	public function htmlScriptTagJsApiV3(?array $configuration = []): string {
250
251 5
		return $this->htmlScriptTagJsApi('', $configuration);
252
	}
253
254
	/**
255
	 * Call out to reCAPTCHA and process the response
256
	 *
257
	 * @param string $response
258
	 *
259
	 * @return boolean|array
260
	 */
261 1
	public function validate($response) {
262
263 1
		if ($this->skip_by_ip) {
264
			if ($this->returnArray()) {
265
				// Add 'skip_by_ip' field to response
266
				return [
267
					'skip_by_ip' => true,
268
					'score'      => 0.9,
269
					'success'    => true
270
				];
271
			}
272
273
			return true;
274
		}
275
276 1
		$params = http_build_query([
277 1
			'secret'   => $this->api_secret_key,
278 1
			'remoteip' => request()->getClientIp(),
279 1
			'response' => $response,
280
		]);
281
282 1
		$url = $this->api_url . '?' . $params;
283
284 1
		if (function_exists('curl_version')) {
285 1
			$curl = curl_init($url);
286 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

286
			curl_setopt(/** @scrutinizer ignore-type */ $curl, CURLOPT_HEADER, false);
Loading history...
287 1
			curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
288 1
			curl_setopt($curl, CURLOPT_TIMEOUT, $this->curl_timeout);
289 1
			curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
290 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

290
			$curl_response = curl_exec(/** @scrutinizer ignore-type */ $curl);
Loading history...
291
		}
292
		else {
293
			$curl_response = file_get_contents($url);
294
		}
295
296 1
		if (is_null($curl_response) || empty($curl_response)) {
297
			if ($this->returnArray()) {
298
				// Add 'error' field to response
299
				return [
300
					'error'   => 'cURL response empty',
301
					'score'   => 0.1,
302
					'success' => false
303
				];
304
			}
305
306
			return false;
307
		}
308 1
		$response = json_decode(trim($curl_response), true);
309
310 1
		if ($this->returnArray()) {
311 1
			return $response;
312
		}
313
314
		return $response['success'];
315
316
	}
317
318
	/**
319
	 * @return bool
320
	 */
321 1
	protected function returnArray(): bool {
322
323 1
		return ($this->version == 'v3');
324
	}
325
}