Completed
Pull Request — master (#8)
by Dmitrii
04:27 queued 01:56
created

DeCaptchaAbstract   F

Complexity

Total Complexity 74

Size/Duplication

Total Lines 378
Duplicated Lines 0 %

Coupling/Cohesion

Components 4
Dependencies 3

Test Coverage

Coverage 70.9%

Importance

Changes 0
Metric Value
wmc 74
lcom 4
cbo 3
dl 0
loc 378
ccs 39
cts 55
cp 0.709
rs 2.3809
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A executionDelayed() 0 11 3
A resetLimits() 0 6 2
A limitHasNotYetEnded() 0 4 1
C decodeResponse() 0 40 14
A setErrorLang() 0 4 1
B getFilePath() 0 18 5
A getActionUrl() 0 4 1
A getBaseUrl() 0 4 1
A setParams() 0 8 3
A setParam() 0 4 1
C getParamSpec() 0 29 13
D getParams() 0 51 23
A getResponse() 0 9 2
B sendRequest() 0 26 4
getCode() 0 1 ?
getError() 0 1 ?

How to fix   Complexity   

Complex Class

Complex classes like DeCaptchaAbstract often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DeCaptchaAbstract, and based on these observations, apply Extract Interface, too.

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
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
                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
        throw new DeCaptchaErrors(DeCaptchaErrors::ERROR_FILE_NOT_FOUND, $fileName, $this->errorLang);
175
    }
176
177
    /**
178
     * @param $action
179
     *
180
     * @return string
181
     */
182
    protected function getActionUrl($action)
183
    {
184
        return $this->getBaseUrl().$this->actions[$action][static::ACTION_URI];
185 1
    }
186 1
187 1
    /**
188 1
     * @return string
189 1
     */
190 1
    protected function getBaseUrl()
191 1
    {
192 1
        return "{$this->scheme}://{$this->host}/";
193
    }
194
195
    /**
196
     * @param $params
197
     */
198
    public function setParams($params)
199
    {
200
        if (is_array($params)) {
201
            foreach ($params as $param => $value) {
202
                $this->params[$param] = $value;
203
            }
204
        }
205
    }
206
207
    /**
208
     * @param $param
209
     * @param $value
210
     */
211
    public function setParam($param, $value)
212
    {
213
        $this->params[$param] = $value;
214
    }
215
216
    /**
217
     * @param $param
218
     * @param $spec
219
     * @param $coding
220
     *
221
     * @return \CURLFile|mixed|null|string
222
     */
223
    public function getParamSpec($param, $spec = null, $coding = null)
224
    {
225
        if (is_null($spec)) {
226
            $spec = $param;
227
        }
228
        if (!array_key_exists($param, $this->params) || is_null($this->params[$param])) {
229
            if (!array_key_exists($spec, $this->params) || is_null($this->params[$spec])) {
230
                return null;
231
            }
232
            $param = $spec;
233
        }
234
        switch ($spec) {
235
            case static::PARAM_SPEC_FILE:
236
                switch ($coding) {
237
                    case static::PARAM_SLUG_CODING_BASE64:
238
                        return base64_encode(file_get_contents($this->params[$param]));
239
                }
240
241
                return (version_compare(PHP_VERSION, '5.5.0') >= 0) ? new \CURLFile($this->getFilePath($this->params[$param])) : '@'.$this->getFilePath($this->params[$param]);
242
            case static::PARAM_SPEC_API_KEY:
243
                return is_callable($this->params[$param]) ? $this->params[$param]() : $this->params[$param];
244
            case static::PARAM_SPEC_CAPTCHA:
245
                return (int) $this->params[$param];
246
            case static::PARAM_SPEC_CODE:
247
                return (string) $this->params[$param];
248
        }
249
250
        return null;
251
    }
252
253
    /**
254
     * @param $action
255
     * @param $field
256
     *
257
     * @throws DeCaptchaErrors
258
     *
259
     * @return array
260
     */
261
    protected function getParams($action, $field = null)
262
    {
263
        if (empty($this->actions[$action])) {
264
            return [];
265
        }
266
        $fields = $this->actions[$action][static::ACTION_FIELDS];
267
        if (!is_null($field)) {
268
            $fields = $fields[$field][static::ACTION_FIELDS];
269
        }
270
        $params = [];
271
        foreach ($fields as $field => $settings) {
272
            $value = null;
273
            if (array_key_exists(self::PARAM_SLUG_DEFAULT, $settings)) {
274
                $value = $settings[self::PARAM_SLUG_DEFAULT];
275
            }
276
            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))) {
277
                $value = $this->params[$field];
278
            }
279
            if (array_key_exists(self::PARAM_SLUG_SPEC, $settings) && array_key_exists($settings[self::PARAM_SLUG_SPEC], $this->params)) {
280
                $value = $this->getParamSpec($field, $settings[self::PARAM_SLUG_SPEC], array_key_exists(self::PARAM_SLUG_CODING, $settings) ? $settings[self::PARAM_SLUG_CODING] : null);
281
            }
282
            if (is_null($value)) {
283
                if (array_key_exists(self::PARAM_SLUG_REQUIRE, $settings) && $settings[self::PARAM_SLUG_REQUIRE] === true) {
284
                    throw new DeCaptchaErrors(DeCaptchaErrors::ERROR_PARAM_REQUIRE, array_key_exists($field, $this->paramsNames) ? $this->paramsNames[$field] : $field, $this->errorLang);
285
                }
286
                continue;
287
            }
288
            if (array_key_exists($field, $this->paramsNames)) {
289
                switch ($settings[self::PARAM_SLUG_TYPE]) {
290
                    case self::PARAM_FIELD_TYPE_INTEGER:
291
                        $value = (int) $value;
292
                        break;
293
                    case self::PARAM_FIELD_TYPE_STRING:
294
                        $value = (string) $value;
295
                        break;
296
                    case self::PARAM_FIELD_TYPE_BOOLEAN:
297
                        $value = (bool) $value;
298
                        break;
299
                    case self::PARAM_FIELD_TYPE_OBJECT:
300
                        $value = $this->getParams($action, $field);
301
                        break;
302
                }
303
                if (array_key_exists(self::PARAM_SLUG_ENUM, $settings) && !in_array($value, $settings[static::PARAM_SLUG_ENUM])) {
304
                    throw new DeCaptchaErrors(DeCaptchaErrors::ERROR_PARAM_ENUM, (array_key_exists($field, $this->paramsNames) ? $this->paramsNames[$field] : $field).' = '.$value, $this->errorLang);
305
                }
306
                $params[$this->paramsNames[$field]] = $value;
307
            }
308
        }
309
310
        return $params;
311
    }
312
313
    /**
314
     * @param string $action
315
     *
316
     * @return string
317
     */
318
    protected function getResponse($action)
319
    {
320
        return $this->sendRequest(
321
            $this->actions[$action][static::ACTION_METHOD],
322
            $this->getActionUrl($action),
323
            $this->getParams($action),
324
            array_key_exists(static::ACTION_JSON, $this->actions[$action]) && $this->actions[$action][static::ACTION_JSON] === true
325
        );
326
    }
327
328
    /**
329
     * Задержка выполнения.
330
     *
331
     * @param int           $delay    Количество секунд
332
     * @param \Closure|null $callback
333
     *
334
     * @return mixed
335
     */
336
    protected function executionDelayed($delay = 0, $callback = null)
337
    {
338
        $time = microtime(true);
339
        $timePassed = $time - $this->lastRunTime;
340
        if ($timePassed < $delay) {
341
            usleep(($delay - $timePassed) * 1000000);
342
        }
343
        $this->lastRunTime = microtime(true);
344
345
        return $callback instanceof \Closure ? $callback($this) : $callback;
346
    }
347
348
    /**
349
     * @param string       $method Request method
350
     * @param string       $url    Request URL
351
     * @param array|string $data   Request payload
352
     * @param bool         $isJson Whether request payload should be serialized as JSON
353
     *
354
     * @throws \InvalidArgumentException Invalid arguments combination
355
     *
356
     * @return string
357
     */
358
    protected function sendRequest($method, $url, $data, $isJson = false)
359
    {
360
        $client = new Client([
361
            'timeout'         => 30,
362
            'connect_timeout' => 30,
363
            'headers'         => [
364
                'Accept-Encoding' => 'gzip,deflate',
365
                'User-Agent'      => 'DeCaptcha',
366
            ],
367
        ]);
368
369
        if ($method === static::ACTION_METHOD_GET && $isJson) {
370
            throw new \InvalidArgumentException('JSON encoding with GET requests is not supported.');
371
        }
372
373
        $options = [];
374
375
        $encType = $isJson ? 'json' : 'query';
376
        $options[$encType] = $data;
377
378
        $response = $client->request($method, $url, $options);
379
380
        $body = $response->getBody();
381
382
        return (string) $body;
383
    }
384
385
    abstract public function getCode();
386
387
    abstract public function getError();
388
}
389