Passed
Pull Request — master (#8)
by Dmitrii
06:54 queued 04:41
created

DeCaptchaAbstract::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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