Passed
Pull Request — master (#8)
by Dmitrii
08:30
created

DeCaptchaAbstract::sendRequest()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 0
cts 0
cp 0
rs 9.7
c 0
b 0
f 0
cc 4
nc 3
nop 4
crap 20
1
<?php
2
3
namespace jumper423\decaptcha\core;
4
5
use Exception;
6
use GuzzleHttp\Client;
7
8
/**
9
 * Class DeCaptchaAbstract.
10
 */
11
abstract class DeCaptchaAbstract implements DeCaptchaInterface
12
{
13
    const RESPONSE_TYPE_STRING = 1;
14
    const RESPONSE_TYPE_JSON = 2;
15
16
    const ACTION_FIELDS = 1;
17
    const ACTION_URI = 2;
18
    const ACTION_METHOD = 3;
19
    const ACTION_JSON = 4;
20
21
    const ACTION_METHOD_POST = 'post';
22
    const ACTION_METHOD_GET = 'get';
23
24
    const DECODE_FORMAT = 1;
25
    const DECODE_ACTION = 2;
26
    const DECODE_SEPARATOR = 3;
27
    const DECODE_PARAMS = 4;
28
    const DECODE_PARAM_SETTING_MARKER = 5;
29
30
    const PARAM_FIELD_TYPE_STRING = 1;
31
    const PARAM_FIELD_TYPE_INTEGER = 2;
32
    const PARAM_FIELD_TYPE_MIX = 3;
33
    const PARAM_FIELD_TYPE_OBJECT = 4;
34
    const PARAM_FIELD_TYPE_BOOLEAN = 5;
35
    const PARAM_FIELD_TYPE_FLOAT = 6;
36
37
    const PARAM_SLUG_DEFAULT = 1;
38
    const PARAM_SLUG_TYPE = 2;
39
    const PARAM_SLUG_REQUIRE = 3;
40
    const PARAM_SLUG_SPEC = 4;
41
    const PARAM_SLUG_VARIABLE = 5;
42
    const PARAM_SLUG_CODING = 6;
43
    const PARAM_SLUG_NOTWIKI = 7;
44
    const PARAM_SLUG_ENUM = 8;
45
    const PARAM_SLUG_WIKI = 9;
46
47
    const PARAM_SLUG_CODING_BASE64 = 1;
48
49
    const PARAM_SPEC_API_KEY = -1;
50
    const PARAM_SPEC_FILE = -2;
51
    const PARAM_SPEC_CAPTCHA = -3;
52
    const PARAM_SPEC_CODE = -4;
53
54
    /**
55
     * Сервис на который будем загружать капчу.
56
     *
57
     * @var string
58
     */
59
    protected $host;
60
    protected $scheme = 'http';
61
    protected $errorLang = DeCaptchaErrors::LANG_EN;
62
    protected $lastRunTime = null;
63
    /** @var DeCaptchaErrors */
64
    protected $errorObject;
65
    protected $causeAnError = false;
66
67
    protected $limit = [];
68
    protected $paramsSpec = [];
69
    protected $params = [];
70
    protected $limitSettings = [];
71
    protected $decodeSettings = [];
72
    protected $actions = [];
73
    protected $paramsNames = [];
74
75
    /**
76
     * @var Client
77
     */
78
    protected $client = null;
79
80
    public function __construct()
81
    {
82
        $this->client = new Client([
83
            'timeout'         => 30,
84
            'connect_timeout' => 30,
85
            'headers'         => [
86
                'Accept-Encoding' => 'gzip,deflate',
87
                'User-Agent'      => 'DeCaptcha',
88
            ],
89
        ]);
90
    }
91
92 3
93
    protected function resetLimits()
94 3
    {
95 1
        foreach ($this->limitSettings as $action => $value) {
96 1
            $this->limit[$action] = $value;
97 3
        }
98
    }
99 3
100
    /**
101
     * @param $action
102
     *
103
     * @return bool
104
     */
105
    protected function limitHasNotYetEnded($action)
106
    {
107
        return $this->limit[$action]-- > 0;
108
    }
109
110
    /**
111
     * @param $action
112
     * @param $data
113
     *
114 3
     * @throws DeCaptchaErrors
115 3
     *
116 2
     * @return array
117 2
     */
118 1
    protected function decodeResponse($action, $data)
119 1
    {
120 1
        if (!array_key_exists($action, $this->decodeSettings[static::DECODE_ACTION])) {
121 1
            throw new DeCaptchaErrors('нет action');
122
        }
123
        $decodeSetting = $this->decodeSettings[static::DECODE_ACTION][$action];
124 1
        $decodeFormat = array_key_exists(static::DECODE_FORMAT, $decodeSetting) ?
125 1
            $decodeSetting[static::DECODE_FORMAT] :
126
            $this->decodeSettings[static::DECODE_FORMAT];
127 2
        $values = [];
128 1
        switch ($decodeFormat) {
129
            case static::RESPONSE_TYPE_STRING:
130 1 View Code Duplication
                foreach (explode($decodeSetting[static::DECODE_SEPARATOR], $data) as $key => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
131
                    foreach ($decodeSetting[static::DECODE_PARAMS] as $param => $paramSetting) {
132
                        if ($key === $paramSetting[static::DECODE_PARAM_SETTING_MARKER]) {
133
                            $values[$param] = $value;
134
                        }
135
                    }
136 4
                }
137 4
                break;
138
            case static::RESPONSE_TYPE_JSON:
139
                foreach (json_decode($data, true) as $key => $value) {
140
                    foreach ($decodeSetting[static::DECODE_PARAMS] as $param => $paramSetting) {
141
                        if (count(explode('.', $paramSetting[static::DECODE_PARAM_SETTING_MARKER])) > 1) {
142
                            if ($key === explode('.', $paramSetting[static::DECODE_PARAM_SETTING_MARKER])[0]) {
143
                                if (array_key_exists(explode('.', $paramSetting[static::DECODE_PARAM_SETTING_MARKER])[1], $value)) {
144 2
                                    $values[$param] = $value[explode('.', $paramSetting[static::DECODE_PARAM_SETTING_MARKER])[1]];
145 2
                                }
146
                            }
147
                        }
148
                        if ($key === $paramSetting[static::DECODE_PARAM_SETTING_MARKER]) {
149
                            $values[$param] = $value;
150
                        }
151
                    }
152 1
                }
153 1
                break;
154
        }
155
156
        return $values;
157
    }
158
159 1
    /**
160 1
     * @param $errorLang
161
     */
162
    public function setErrorLang($errorLang)
163
    {
164
        $this->errorLang = $errorLang;
165
    }
166
167
    /**
168
     * Узнаём путь до файла
169 2
     * Если передана ссылка, то скачиваем и кладём во временную директорию.
170
     *
171 2
     * @param string $fileName
172 1
     *
173
     * @throws Exception
174 1
     *
175
     * @return string
176
     */
177
    protected function getFilePath($fileName)
178
    {
179
        if (strpos($fileName, 'http://') !== false || strpos($fileName, 'https://') !== false) {
180
            try {
181
                $current = file_get_contents($fileName);
182
            } catch (\Exception $e) {
183
                throw new DeCaptchaErrors(DeCaptchaErrors::ERROR_FILE_IS_NOT_LOADED, $fileName, $this->errorLang);
184
            }
185 1
            $path = tempnam(sys_get_temp_dir(), 'captcha');
186 1
            file_put_contents($path, $current);
187 1
188 1
            return $path;
189 1
        }
190 1
        if (file_exists($fileName)) {
191 1
            return $fileName;
192 1
        }
193
194
        throw new DeCaptchaErrors(DeCaptchaErrors::ERROR_FILE_NOT_FOUND, $fileName, $this->errorLang);
195
    }
196
197
    /**
198
     * @param $action
199
     *
200
     * @return string
201
     */
202
    protected function getActionUrl($action)
203
    {
204
        return $this->getBaseUrl().$this->actions[$action][static::ACTION_URI];
205
    }
206
207
    /**
208
     * @return string
209
     */
210
    protected function getBaseUrl()
211
    {
212
        return "{$this->scheme}://{$this->host}/";
213
    }
214
215
    /**
216
     * @param $params
217
     */
218
    public function setParams($params)
219
    {
220
        if (is_array($params)) {
221
            foreach ($params as $param => $value) {
222
                $this->params[$param] = $value;
223
            }
224
        }
225
    }
226
227
    /**
228
     * @param $param
229
     * @param $value
230
     */
231
    public function setParam($param, $value)
232
    {
233
        $this->params[$param] = $value;
234
    }
235
236
    /**
237
     * @param $param
238
     * @param $spec
239
     * @param $coding
240
     *
241
     * @return mixed|null|string
242
     */
243
    public function getParamSpec($param, $spec = null, $coding = null)
244
    {
245
        if (is_null($spec)) {
246
            $spec = $param;
247
        }
248
        if (!array_key_exists($param, $this->params) || is_null($this->params[$param])) {
249
            if (!array_key_exists($spec, $this->params) || is_null($this->params[$spec])) {
250
                return null;
251
            }
252
            $param = $spec;
253
        }
254
        switch ($spec) {
255
            case static::PARAM_SPEC_FILE:
256
                switch ($coding) {
257
                    case static::PARAM_SLUG_CODING_BASE64:
258
                        return base64_encode(file_get_contents($this->params[$param]));
259
                }
260
261
                return '@'.$this->getFilePath($this->params[$param]);
262
            case static::PARAM_SPEC_API_KEY:
263
                return is_callable($this->params[$param]) ? $this->params[$param]() : $this->params[$param];
264
            case static::PARAM_SPEC_CAPTCHA:
265
                return (int) $this->params[$param];
266
            case static::PARAM_SPEC_CODE:
267
                return (string) $this->params[$param];
268
        }
269
270
        return null;
271
    }
272
273
    /**
274
     * @param $action
275
     * @param $field
276
     *
277
     * @throws DeCaptchaErrors
278
     *
279
     * @return array
280
     */
281
    protected function getParams($action, $field = null)
282
    {
283
        if (empty($this->actions[$action])) {
284
            return [];
285
        }
286
        $fields = $this->actions[$action][static::ACTION_FIELDS];
287
        if (!is_null($field)) {
288
            $fields = $fields[$field][static::ACTION_FIELDS];
289
        }
290
        $params = [];
291
        foreach ($fields as $field => $settings) {
292
            $value = null;
293
            if (array_key_exists(self::PARAM_SLUG_DEFAULT, $settings)) {
294
                $value = $settings[self::PARAM_SLUG_DEFAULT];
295
            }
296
            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))) {
297
                $value = $this->params[$field];
298
            }
299
            if (array_key_exists(self::PARAM_SLUG_SPEC, $settings)) {
300
                $value = $this->getParamSpec($field, $settings[self::PARAM_SLUG_SPEC], array_key_exists(self::PARAM_SLUG_CODING, $settings) ? $settings[self::PARAM_SLUG_CODING] : null);
301
            }
302
            if (is_null($value)) {
303
                if (array_key_exists(self::PARAM_SLUG_REQUIRE, $settings) && $settings[self::PARAM_SLUG_REQUIRE] === true) {
304
                    throw new DeCaptchaErrors(DeCaptchaErrors::ERROR_PARAM_REQUIRE, array_key_exists($field, $this->paramsNames) ? $this->paramsNames[$field] : $field, $this->errorLang);
305
                }
306
                continue;
307
            }
308
            if (array_key_exists($field, $this->paramsNames)) {
309
                switch ($settings[self::PARAM_SLUG_TYPE]) {
310
                    case self::PARAM_FIELD_TYPE_FLOAT:
311
                        $value = (float) $value;
312
                        break;
313
                    case self::PARAM_FIELD_TYPE_INTEGER:
314
                        $value = (int) $value;
315
                        break;
316
                    case self::PARAM_FIELD_TYPE_STRING:
317
                        $value = (string) $value;
318
                        break;
319
                    case self::PARAM_FIELD_TYPE_BOOLEAN:
320
                        $value = (bool) $value;
321
                        break;
322
                    case self::PARAM_FIELD_TYPE_OBJECT:
323
                        $value = $this->getParams($action, $field);
324
                        break;
325
                }
326
                if (array_key_exists(self::PARAM_SLUG_ENUM, $settings) && !in_array($value, $settings[static::PARAM_SLUG_ENUM])) {
327
                    throw new DeCaptchaErrors(DeCaptchaErrors::ERROR_PARAM_ENUM, (array_key_exists($field, $this->paramsNames) ? $this->paramsNames[$field] : $field).' = '.$value, $this->errorLang);
328
                }
329
                $params[$this->paramsNames[$field]] = $value;
330
            }
331
        }
332
333
        return $params;
334
    }
335
336
    /**
337
     * @param string $action
338
     *
339
     * @return string
340
     */
341
    protected function getResponse($action)
342
    {
343
        return $this->sendRequest(
344
            $this->actions[$action][static::ACTION_METHOD],
345
            $this->getActionUrl($action),
346
            $this->getParams($action),
347
            array_key_exists(static::ACTION_JSON, $this->actions[$action]) && $this->actions[$action][static::ACTION_JSON] === true
348
        );
349
    }
350
351
    /**
352
     * Задержка выполнения.
353
     *
354
     * @param int           $delay    Количество секунд
355
     * @param \Closure|null $callback
356
     *
357
     * @return mixed
358
     */
359
    protected function executionDelayed($delay = 0, $callback = null)
360
    {
361
        $time = microtime(true);
362
        $timePassed = $time - $this->lastRunTime;
363
        if ($timePassed < $delay) {
364
            usleep(($delay - $timePassed) * 1000000);
365
        }
366
        $this->lastRunTime = microtime(true);
367
368
        return $callback instanceof \Closure ? $callback($this) : $callback;
369
    }
370
371
    /**
372
     * @param string       $method Request method
373
     * @param string       $url    Request URL
374
     * @param array|string $data   Request payload
375
     * @param bool         $isJson Whether request payload should be serialized as JSON
376
     *
377
     * @throws \InvalidArgumentException Invalid arguments combination
378
     *
379
     * @return string
380
     */
381
    protected function sendRequest($method, $url, $data, $isJson = false)
382
    {
383
        if ($method === static::ACTION_METHOD_GET && $isJson) {
384
            throw new \InvalidArgumentException('JSON encoding with GET requests is not supported.');
385
        }
386
387
        $options = [];
388
389
        $encType = $isJson ? 'json' : 'query';
390
        $options[$encType] = $data;
391
392
        $response = $this->client->request($method, $url, $options);
393
394
        $body = $response->getBody();
395
396
        return (string) $body;
397
    }
398
399
    abstract public function getCode();
400
401
    abstract public function getError();
402
}
403