1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace jumper423\decaptcha\core; |
4
|
|
|
|
5
|
|
|
use Exception; |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* Class DeCaptchaAbstract. |
9
|
|
|
*/ |
10
|
|
|
abstract class DeCaptchaAbstract implements DeCaptchaInterface |
11
|
|
|
{ |
12
|
|
|
const RESPONSE_TYPE_STRING = 1; |
13
|
|
|
const RESPONSE_TYPE_JSON = 2; |
14
|
|
|
|
15
|
|
|
const ACTION_FIELDS = 1; |
16
|
|
|
const ACTION_URI = 2; |
17
|
|
|
const ACTION_METHOD = 3; |
18
|
|
|
const ACTION_JSON = 4; |
19
|
|
|
|
20
|
|
|
const ACTION_METHOD_POST = 1; |
21
|
|
|
const ACTION_METHOD_GET = 2; |
22
|
|
|
|
23
|
|
|
const DECODE_FORMAT = 1; |
24
|
|
|
const DECODE_ACTION = 2; |
25
|
|
|
const DECODE_SEPARATOR = 3; |
26
|
|
|
const DECODE_PARAMS = 4; |
27
|
|
|
const DECODE_PARAM_SETTING_MARKER = 5; |
28
|
|
|
|
29
|
|
|
const PARAM_FIELD_TYPE_STRING = 1; |
30
|
|
|
const PARAM_FIELD_TYPE_INTEGER = 2; |
31
|
|
|
const PARAM_FIELD_TYPE_MIX = 3; |
32
|
|
|
const PARAM_FIELD_TYPE_OBJECT = 4; |
33
|
|
|
const PARAM_FIELD_TYPE_BOOLEAN = 5; |
34
|
|
|
const PARAM_FIELD_TYPE_FLOAT = 6; |
35
|
|
|
|
36
|
|
|
const PARAM_SLUG_DEFAULT = 1; |
37
|
|
|
const PARAM_SLUG_TYPE = 2; |
38
|
|
|
const PARAM_SLUG_REQUIRE = 3; |
39
|
|
|
const PARAM_SLUG_SPEC = 4; |
40
|
|
|
const PARAM_SLUG_VARIABLE = 5; |
41
|
|
|
const PARAM_SLUG_CODING = 6; |
42
|
|
|
const PARAM_SLUG_NOTWIKI = 7; |
43
|
|
|
const PARAM_SLUG_ENUM = 8; |
44
|
|
|
const PARAM_SLUG_WIKI = 9; |
45
|
|
|
|
46
|
|
|
const PARAM_SLUG_CODING_BASE64 = 1; |
47
|
|
|
|
48
|
|
|
const PARAM_SPEC_API_KEY = -1; |
49
|
|
|
const PARAM_SPEC_FILE = -2; |
50
|
|
|
const PARAM_SPEC_CAPTCHA = -3; |
51
|
|
|
const PARAM_SPEC_CODE = -4; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Сервис на который будем загружать капчу. |
55
|
|
|
* |
56
|
|
|
* @var string |
57
|
|
|
*/ |
58
|
|
|
protected $host; |
59
|
|
|
protected $scheme = 'http'; |
60
|
|
|
protected $errorLang = DeCaptchaErrors::LANG_EN; |
61
|
|
|
protected $lastRunTime = null; |
62
|
|
|
/** @var DeCaptchaErrors */ |
63
|
|
|
protected $errorObject; |
64
|
|
|
protected $causeAnError = false; |
65
|
|
|
|
66
|
|
|
protected $limit = []; |
67
|
|
|
protected $paramsSpec = []; |
68
|
|
|
protected $params = []; |
69
|
|
|
protected $limitSettings = []; |
70
|
|
|
protected $decodeSettings = []; |
71
|
|
|
protected $actions = []; |
72
|
|
|
protected $paramsNames = []; |
73
|
|
|
|
74
|
|
|
protected function resetLimits() |
75
|
|
|
{ |
76
|
|
|
foreach ($this->limitSettings as $action => $value) { |
77
|
|
|
$this->limit[$action] = $value; |
78
|
|
|
} |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* @param $action |
83
|
|
|
* |
84
|
|
|
* @return bool |
85
|
|
|
*/ |
86
|
|
|
protected function limitHasNotYetEnded($action) |
87
|
|
|
{ |
88
|
|
|
return $this->limit[$action]-- > 0; |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
/** |
92
|
3 |
|
* @param $action |
93
|
|
|
* @param $data |
94
|
3 |
|
* |
95
|
1 |
|
* @throws DeCaptchaErrors |
96
|
1 |
|
* |
97
|
3 |
|
* @return array |
98
|
|
|
*/ |
99
|
3 |
|
protected function decodeResponse($action, $data) |
100
|
|
|
{ |
101
|
|
|
if (!array_key_exists($action, $this->decodeSettings[static::DECODE_ACTION])) { |
102
|
|
|
throw new DeCaptchaErrors('нет action'); |
103
|
|
|
} |
104
|
|
|
$decodeSetting = $this->decodeSettings[static::DECODE_ACTION][$action]; |
105
|
|
|
$decodeFormat = array_key_exists(static::DECODE_FORMAT, $decodeSetting) ? |
106
|
|
|
$decodeSetting[static::DECODE_FORMAT] : |
107
|
|
|
$this->decodeSettings[static::DECODE_FORMAT]; |
108
|
|
|
$values = []; |
109
|
|
|
switch ($decodeFormat) { |
110
|
|
|
case static::RESPONSE_TYPE_STRING: |
111
|
|
View Code Duplication |
foreach (explode($decodeSetting[static::DECODE_SEPARATOR], $data) as $key => $value) { |
|
|
|
|
112
|
|
|
foreach ($decodeSetting[static::DECODE_PARAMS] as $param => $paramSetting) { |
113
|
|
|
if ($key === $paramSetting[static::DECODE_PARAM_SETTING_MARKER]) { |
114
|
3 |
|
$values[$param] = $value; |
115
|
3 |
|
} |
116
|
2 |
|
} |
117
|
2 |
|
} |
118
|
1 |
|
break; |
119
|
1 |
|
case static::RESPONSE_TYPE_JSON: |
120
|
1 |
|
foreach (json_decode($data, true) as $key => $value) { |
121
|
1 |
|
foreach ($decodeSetting[static::DECODE_PARAMS] as $param => $paramSetting) { |
122
|
|
|
if (count(explode('.', $paramSetting[static::DECODE_PARAM_SETTING_MARKER])) > 1) { |
123
|
|
|
if ($key === explode('.', $paramSetting[static::DECODE_PARAM_SETTING_MARKER])[0]) { |
124
|
1 |
|
if (array_key_exists(explode('.', $paramSetting[static::DECODE_PARAM_SETTING_MARKER])[1], $value)) { |
125
|
1 |
|
$values[$param] = $value[explode('.', $paramSetting[static::DECODE_PARAM_SETTING_MARKER])[1]]; |
126
|
|
|
} |
127
|
2 |
|
} |
128
|
1 |
|
} |
129
|
|
|
if ($key === $paramSetting[static::DECODE_PARAM_SETTING_MARKER]) { |
130
|
1 |
|
$values[$param] = $value; |
131
|
|
|
} |
132
|
|
|
} |
133
|
|
|
} |
134
|
|
|
break; |
135
|
|
|
} |
136
|
4 |
|
|
137
|
4 |
|
return $values; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* @param $errorLang |
142
|
|
|
*/ |
143
|
|
|
public function setErrorLang($errorLang) |
144
|
2 |
|
{ |
145
|
2 |
|
$this->errorLang = $errorLang; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Узнаём путь до файла |
150
|
|
|
* Если передана ссылка, то скачиваем и кладём во временную директорию. |
151
|
|
|
* |
152
|
1 |
|
* @param string $fileName |
153
|
1 |
|
* |
154
|
|
|
* @throws Exception |
155
|
|
|
* |
156
|
|
|
* @return string |
157
|
|
|
*/ |
158
|
|
|
protected function getFilePath($fileName) |
159
|
1 |
|
{ |
160
|
1 |
|
if (strpos($fileName, 'http://') !== false || strpos($fileName, 'https://') !== false) { |
161
|
|
|
try { |
162
|
|
|
$current = file_get_contents($fileName); |
163
|
|
|
} catch (\Exception $e) { |
164
|
|
|
throw new DeCaptchaErrors(DeCaptchaErrors::ERROR_FILE_IS_NOT_LOADED, $fileName, $this->errorLang); |
165
|
|
|
} |
166
|
|
|
$path = tempnam(sys_get_temp_dir(), 'captcha'); |
167
|
|
|
file_put_contents($path, $current); |
168
|
|
|
|
169
|
2 |
|
return $path; |
170
|
|
|
} |
171
|
2 |
|
if (file_exists($fileName)) { |
172
|
1 |
|
return $fileName; |
173
|
|
|
} |
174
|
1 |
|
|
175
|
|
|
throw new DeCaptchaErrors(DeCaptchaErrors::ERROR_FILE_NOT_FOUND, $fileName, $this->errorLang); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* @param $action |
180
|
|
|
* |
181
|
|
|
* @return string |
182
|
|
|
*/ |
183
|
|
|
protected function getActionUrl($action) |
184
|
|
|
{ |
185
|
1 |
|
return $this->getBaseUrl().$this->actions[$action][static::ACTION_URI]; |
186
|
1 |
|
} |
187
|
1 |
|
|
188
|
1 |
|
/** |
189
|
1 |
|
* @return string |
190
|
1 |
|
*/ |
191
|
1 |
|
protected function getBaseUrl() |
192
|
1 |
|
{ |
193
|
|
|
return "{$this->scheme}://{$this->host}/"; |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* @param $params |
198
|
|
|
*/ |
199
|
|
|
public function setParams($params) |
200
|
|
|
{ |
201
|
|
|
if (is_array($params)) { |
202
|
|
|
foreach ($params as $param => $value) { |
203
|
|
|
$this->params[$param] = $value; |
204
|
|
|
} |
205
|
|
|
} |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* @param $param |
210
|
|
|
* @param $value |
211
|
|
|
*/ |
212
|
|
|
public function setParam($param, $value) |
213
|
|
|
{ |
214
|
|
|
$this->params[$param] = $value; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* @param $param |
219
|
|
|
* @param $spec |
220
|
|
|
* @param $coding |
221
|
|
|
* |
222
|
|
|
* @return \CURLFile|mixed|null|string |
223
|
|
|
*/ |
224
|
|
|
public function getParamSpec($param, $spec = null, $coding = null) |
225
|
|
|
{ |
226
|
|
|
if (is_null($spec)) { |
227
|
|
|
$spec = $param; |
228
|
|
|
} |
229
|
|
|
if (!array_key_exists($param, $this->params) || is_null($this->params[$param])) { |
230
|
|
|
if (!array_key_exists($spec, $this->params) || is_null($this->params[$spec])) { |
231
|
|
|
return null; |
232
|
|
|
} |
233
|
|
|
$param = $spec; |
234
|
|
|
} |
235
|
|
|
switch ($spec) { |
236
|
|
|
case static::PARAM_SPEC_FILE: |
237
|
|
|
switch ($coding) { |
238
|
|
|
case static::PARAM_SLUG_CODING_BASE64: |
239
|
|
|
return base64_encode(file_get_contents($this->params[$param])); |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
return (version_compare(PHP_VERSION, '5.5.0') >= 0) ? new \CURLFile($this->getFilePath($this->params[$param])) : '@'.$this->getFilePath($this->params[$param]); |
243
|
|
|
case static::PARAM_SPEC_API_KEY: |
244
|
|
|
return is_callable($this->params[$param]) ? $this->params[$param]() : $this->params[$param]; |
245
|
|
|
case static::PARAM_SPEC_CAPTCHA: |
246
|
|
|
return (int) $this->params[$param]; |
247
|
|
|
case static::PARAM_SPEC_CODE: |
248
|
|
|
return (string) $this->params[$param]; |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
return null; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* @param $action |
256
|
|
|
* @param $field |
257
|
|
|
* |
258
|
|
|
* @throws DeCaptchaErrors |
259
|
|
|
* |
260
|
|
|
* @return array |
261
|
|
|
*/ |
262
|
|
|
protected function getParams($action, $field = null) |
263
|
|
|
{ |
264
|
|
|
if (empty($this->actions[$action])) { |
265
|
|
|
return []; |
266
|
|
|
} |
267
|
|
|
$fields = $this->actions[$action][static::ACTION_FIELDS]; |
268
|
|
|
if (!is_null($field)) { |
269
|
|
|
$fields = $fields[$field][static::ACTION_FIELDS]; |
270
|
|
|
} |
271
|
|
|
$params = []; |
272
|
|
|
foreach ($fields as $field => $settings) { |
273
|
|
|
$value = null; |
274
|
|
|
if (array_key_exists(self::PARAM_SLUG_DEFAULT, $settings)) { |
275
|
|
|
$value = $settings[self::PARAM_SLUG_DEFAULT]; |
276
|
|
|
} |
277
|
|
|
if (array_key_exists($field, $this->params) && (!array_key_exists(self::PARAM_SLUG_VARIABLE, $settings) ^ (array_key_exists(self::PARAM_SLUG_VARIABLE, $settings) && $settings[self::PARAM_SLUG_VARIABLE] === false))) { |
278
|
|
|
$value = $this->params[$field]; |
279
|
|
|
} |
280
|
|
|
if (array_key_exists(self::PARAM_SLUG_SPEC, $settings)) { |
281
|
|
|
$value = $this->getParamSpec($field, $settings[self::PARAM_SLUG_SPEC], array_key_exists(self::PARAM_SLUG_CODING, $settings) ? $settings[self::PARAM_SLUG_CODING] : null); |
282
|
|
|
} |
283
|
|
|
if (is_null($value)) { |
284
|
|
|
if (array_key_exists(self::PARAM_SLUG_REQUIRE, $settings) && $settings[self::PARAM_SLUG_REQUIRE] === true) { |
285
|
|
|
throw new DeCaptchaErrors(DeCaptchaErrors::ERROR_PARAM_REQUIRE, array_key_exists($field, $this->paramsNames) ? $this->paramsNames[$field] : $field, $this->errorLang); |
286
|
|
|
} |
287
|
|
|
continue; |
288
|
|
|
} |
289
|
|
|
if (array_key_exists($field, $this->paramsNames)) { |
290
|
|
|
switch ($settings[self::PARAM_SLUG_TYPE]) { |
291
|
|
|
case self::PARAM_FIELD_TYPE_FLOAT: |
292
|
|
|
$value = (float) $value; |
293
|
|
|
break; |
294
|
|
|
case self::PARAM_FIELD_TYPE_INTEGER: |
295
|
|
|
$value = (int) $value; |
296
|
|
|
break; |
297
|
|
|
case self::PARAM_FIELD_TYPE_STRING: |
298
|
|
|
$value = (string) $value; |
299
|
|
|
break; |
300
|
|
|
case self::PARAM_FIELD_TYPE_BOOLEAN: |
301
|
|
|
$value = (bool) $value; |
302
|
|
|
break; |
303
|
|
|
case self::PARAM_FIELD_TYPE_OBJECT: |
304
|
|
|
$value = $this->getParams($action, $field); |
305
|
|
|
break; |
306
|
|
|
} |
307
|
|
|
if (array_key_exists(self::PARAM_SLUG_ENUM, $settings) && !in_array($value, $settings[static::PARAM_SLUG_ENUM])) { |
308
|
|
|
throw new DeCaptchaErrors(DeCaptchaErrors::ERROR_PARAM_ENUM, (array_key_exists($field, $this->paramsNames) ? $this->paramsNames[$field] : $field).' = '.$value, $this->errorLang); |
309
|
|
|
} |
310
|
|
|
$params[$this->paramsNames[$field]] = $value; |
311
|
|
|
} |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
return $params; |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* @param string $action |
319
|
|
|
* |
320
|
|
|
* @return string |
321
|
|
|
*/ |
322
|
|
|
protected function getResponse($action) |
323
|
|
|
{ |
324
|
|
|
return $this->curlResponse( |
325
|
|
|
$this->getActionUrl($action), |
326
|
|
|
$this->getParams($action), |
327
|
|
|
array_key_exists(static::ACTION_METHOD, $this->actions[$action]) && $this->actions[$action][static::ACTION_METHOD] === static::ACTION_METHOD_POST, |
328
|
|
|
array_key_exists(static::ACTION_JSON, $this->actions[$action]) && $this->actions[$action][static::ACTION_JSON] === true |
329
|
|
|
); |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
/** |
333
|
|
|
* Задержка выполнения. |
334
|
|
|
* |
335
|
|
|
* @param int $delay Количество секунд |
336
|
|
|
* @param \Closure|null $callback |
337
|
|
|
* |
338
|
|
|
* @return mixed |
339
|
|
|
*/ |
340
|
|
|
protected function executionDelayed($delay = 0, $callback = null) |
341
|
|
|
{ |
342
|
|
|
$time = microtime(true); |
343
|
|
|
$timePassed = $time - $this->lastRunTime; |
344
|
|
|
if ($timePassed < $delay) { |
345
|
|
|
usleep(($delay - $timePassed) * 1000000); |
346
|
|
|
} |
347
|
|
|
$this->lastRunTime = microtime(true); |
348
|
|
|
|
349
|
|
|
return $callback instanceof \Closure ? $callback($this) : $callback; |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
/** |
353
|
|
|
* @param string $url |
354
|
|
|
* @param array $data |
355
|
|
|
* @param bool $isPost |
356
|
|
|
* @param bool $isJson |
357
|
|
|
* |
358
|
|
|
* @throws DeCaptchaErrors |
359
|
|
|
* |
360
|
|
|
* @return string |
361
|
|
|
*/ |
362
|
|
|
protected function curlResponse($url, $data, $isPost = true, $isJson = false) |
363
|
|
|
{ |
364
|
|
|
$curl = curl_init(); |
365
|
|
|
if ($isJson) { |
366
|
|
|
$data = json_encode($data); |
367
|
|
|
} elseif (!$isPost) { |
368
|
|
|
$uri = []; |
369
|
|
|
foreach ($data as $key => $value) { |
370
|
|
|
$uri[] = "$key=$value"; |
371
|
|
|
} |
372
|
|
|
$url .= '?'.implode('&', $uri); |
373
|
|
|
} |
374
|
|
|
curl_setopt($curl, CURLOPT_URL, $url); |
375
|
|
|
if (!$isJson && version_compare(PHP_VERSION, '5.5.0') >= 0 && version_compare(PHP_VERSION, '7.0') < 0 && defined('CURLOPT_SAFE_UPLOAD')) { |
376
|
|
|
curl_setopt($curl, CURLOPT_SAFE_UPLOAD, false); |
377
|
|
|
} |
378
|
|
|
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); |
379
|
|
|
curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate'); |
380
|
|
|
curl_setopt($curl, CURLOPT_TIMEOUT, 30); |
381
|
|
|
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 30); |
382
|
|
|
curl_setopt($curl, CURLOPT_POST, $isPost); |
383
|
|
|
if ($isPost) { |
384
|
|
|
curl_setopt($curl, CURLOPT_POSTFIELDS, $data); |
385
|
|
|
} |
386
|
|
|
if ($isJson) { |
387
|
|
|
curl_setopt($curl, CURLOPT_HTTPHEADER, [ |
388
|
|
|
'Content-Type: application/json; charset=utf-8', |
389
|
|
|
'Accept: application/json', |
390
|
|
|
'Content-Length: '.strlen($data), |
391
|
|
|
]); |
392
|
|
|
} |
393
|
|
|
$result = curl_exec($curl); |
394
|
|
|
if (curl_errno($curl)) { |
395
|
|
|
throw new DeCaptchaErrors(DeCaptchaErrors::ERROR_CURL, curl_error($curl), $this->errorLang); |
396
|
|
|
} |
397
|
|
|
curl_close($curl); |
398
|
|
|
|
399
|
|
|
return $result; |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
abstract public function getCode(); |
403
|
|
|
|
404
|
|
|
abstract public function getError(); |
405
|
|
|
} |
406
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.