jumper423/decaptcha

View on GitHub
src/core/DeCaptchaAbstract.php

Summary

Maintainability
D
2 days
Test Coverage
<?php

namespace jumper423\decaptcha\core;

use Exception;

/**
 * Class DeCaptchaAbstract.
 */
abstract class DeCaptchaAbstract implements DeCaptchaInterface
{
    const RESPONSE_TYPE_STRING = 1;
    const RESPONSE_TYPE_JSON = 2;

    const ACTION_FIELDS = 1;
    const ACTION_URI = 2;
    const ACTION_METHOD = 3;
    const ACTION_JSON = 4;

    const ACTION_METHOD_POST = 1;
    const ACTION_METHOD_GET = 2;

    const DECODE_FORMAT = 1;
    const DECODE_ACTION = 2;
    const DECODE_SEPARATOR = 3;
    const DECODE_PARAMS = 4;
    const DECODE_PARAM_SETTING_MARKER = 5;

    const PARAM_FIELD_TYPE_STRING = 1;
    const PARAM_FIELD_TYPE_INTEGER = 2;
    const PARAM_FIELD_TYPE_MIX = 3;
    const PARAM_FIELD_TYPE_OBJECT = 4;
    const PARAM_FIELD_TYPE_BOOLEAN = 5;
    const PARAM_FIELD_TYPE_FLOAT = 6;

    const PARAM_SLUG_DEFAULT = 1;
    const PARAM_SLUG_TYPE = 2;
    const PARAM_SLUG_REQUIRE = 3;
    const PARAM_SLUG_SPEC = 4;
    const PARAM_SLUG_VARIABLE = 5;
    const PARAM_SLUG_CODING = 6;
    const PARAM_SLUG_NOTWIKI = 7;
    const PARAM_SLUG_ENUM = 8;
    const PARAM_SLUG_WIKI = 9;

    const PARAM_SLUG_CODING_BASE64 = 1;

    const PARAM_SPEC_API_KEY = -1;
    const PARAM_SPEC_FILE = -2;
    const PARAM_SPEC_CAPTCHA = -3;
    const PARAM_SPEC_CODE = -4;

    /**
     * Сервис на который будем загружать капчу.
     *
     * @var string
     */
    protected $host;
    protected $scheme = 'http';
    protected $errorLang = DeCaptchaErrors::LANG_EN;
    protected $lastRunTime = null;
    /** @var DeCaptchaErrors */
    protected $errorObject;
    protected $causeAnError = false;

    protected $limit = [];
    protected $paramsSpec = [];
    protected $params = [];
    protected $limitSettings = [];
    protected $decodeSettings = [];
    protected $actions = [];
    protected $paramsNames = [];

    protected function resetLimits()
    {
        foreach ($this->limitSettings as $action => $value) {
            $this->limit[$action] = $value;
        }
    }

    /**
     * @param $action
     *
     * @return bool
     */
    protected function limitHasNotYetEnded($action)
    {
        return $this->limit[$action]-- > 0;
    }

    /**
     * @param $action
     * @param $data
     *
     * @throws DeCaptchaErrors
     *
     * @return array
     */
    protected function decodeResponse($action, $data)
    {
        if (!array_key_exists($action, $this->decodeSettings[static::DECODE_ACTION])) {
            throw new DeCaptchaErrors('нет action');
        }
        $decodeSetting = $this->decodeSettings[static::DECODE_ACTION][$action];
        $decodeFormat = array_key_exists(static::DECODE_FORMAT, $decodeSetting) ?
            $decodeSetting[static::DECODE_FORMAT] :
            $this->decodeSettings[static::DECODE_FORMAT];
        $values = [];
        switch ($decodeFormat) {
            case static::RESPONSE_TYPE_STRING:
                foreach (explode($decodeSetting[static::DECODE_SEPARATOR], $data) as $key => $value) {
                    foreach ($decodeSetting[static::DECODE_PARAMS] as $param => $paramSetting) {
                        if ($key === $paramSetting[static::DECODE_PARAM_SETTING_MARKER]) {
                            $values[$param] = $value;
                        }
                    }
                }
                break;
            case static::RESPONSE_TYPE_JSON:
                foreach (json_decode($data, true) as $key => $value) {
                    foreach ($decodeSetting[static::DECODE_PARAMS] as $param => $paramSetting) {
                        if (count(explode('.', $paramSetting[static::DECODE_PARAM_SETTING_MARKER])) > 1) {
                            if ($key === explode('.', $paramSetting[static::DECODE_PARAM_SETTING_MARKER])[0]) {
                                if (array_key_exists(explode('.', $paramSetting[static::DECODE_PARAM_SETTING_MARKER])[1], $value)) {
                                    $values[$param] = $value[explode('.', $paramSetting[static::DECODE_PARAM_SETTING_MARKER])[1]];
                                }
                            }
                        }
                        if ($key === $paramSetting[static::DECODE_PARAM_SETTING_MARKER]) {
                            $values[$param] = $value;
                        }
                    }
                }
                break;
        }

        return $values;
    }

    /**
     * @param $errorLang
     */
    public function setErrorLang($errorLang)
    {
        $this->errorLang = $errorLang;
    }

    /**
     * Узнаём путь до файла
     * Если передана ссылка, то скачиваем и кладём во временную директорию.
     *
     * @param string $fileName
     *
     * @throws Exception
     *
     * @return string
     */
    protected function getFilePath($fileName)
    {
        if (strpos($fileName, 'http://') !== false || strpos($fileName, 'https://') !== false) {
            try {
                $current = file_get_contents($fileName);
            } catch (\Exception $e) {
                throw new DeCaptchaErrors(DeCaptchaErrors::ERROR_FILE_IS_NOT_LOADED, $fileName, $this->errorLang);
            }
            $path = tempnam(sys_get_temp_dir(), 'captcha');
            file_put_contents($path, $current);

            return $path;
        }
        if (file_exists($fileName)) {
            return $fileName;
        }

        throw new DeCaptchaErrors(DeCaptchaErrors::ERROR_FILE_NOT_FOUND, $fileName, $this->errorLang);
    }

    /**
     * @param $action
     *
     * @return string
     */
    protected function getActionUrl($action)
    {
        return $this->getBaseUrl().$this->actions[$action][static::ACTION_URI];
    }

    /**
     * @return string
     */
    protected function getBaseUrl()
    {
        return "{$this->scheme}://{$this->host}/";
    }

    /**
     * @param $params
     */
    public function setParams($params)
    {
        if (is_array($params)) {
            foreach ($params as $param => $value) {
                $this->params[$param] = $value;
            }
        }
    }

    /**
     * @param $param
     * @param $value
     */
    public function setParam($param, $value)
    {
        $this->params[$param] = $value;
    }

    /**
     * @param $param
     * @param $spec
     * @param $coding
     *
     * @return \CURLFile|mixed|null|string
     */
    public function getParamSpec($param, $spec = null, $coding = null)
    {
        if (is_null($spec)) {
            $spec = $param;
        }
        if (!array_key_exists($param, $this->params) || is_null($this->params[$param])) {
            if (!array_key_exists($spec, $this->params) || is_null($this->params[$spec])) {
                return null;
            }
            $param = $spec;
        }
        switch ($spec) {
            case static::PARAM_SPEC_FILE:
                switch ($coding) {
                    case static::PARAM_SLUG_CODING_BASE64:
                        return base64_encode(file_get_contents($this->params[$param]));
                }

                return (version_compare(PHP_VERSION, '5.5.0') >= 0) ? new \CURLFile($this->getFilePath($this->params[$param])) : '@'.$this->getFilePath($this->params[$param]);
            case static::PARAM_SPEC_API_KEY:
                return is_callable($this->params[$param]) ? $this->params[$param]() : $this->params[$param];
            case static::PARAM_SPEC_CAPTCHA:
                return (int) $this->params[$param];
            case static::PARAM_SPEC_CODE:
                return (string) $this->params[$param];
        }

        return null;
    }

    /**
     * @param $action
     * @param $field
     *
     * @throws DeCaptchaErrors
     *
     * @return array
     */
    protected function getParams($action, $field = null)
    {
        if (empty($this->actions[$action])) {
            return [];
        }
        $fields = $this->actions[$action][static::ACTION_FIELDS];
        if (!is_null($field)) {
            $fields = $fields[$field][static::ACTION_FIELDS];
        }
        $params = [];
        foreach ($fields as $field => $settings) {
            $value = null;
            if (array_key_exists(self::PARAM_SLUG_DEFAULT, $settings)) {
                $value = $settings[self::PARAM_SLUG_DEFAULT];
            }
            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))) {
                $value = $this->params[$field];
            }
            if (array_key_exists(self::PARAM_SLUG_SPEC, $settings)) {
                $value = $this->getParamSpec($field, $settings[self::PARAM_SLUG_SPEC], array_key_exists(self::PARAM_SLUG_CODING, $settings) ? $settings[self::PARAM_SLUG_CODING] : null);
            }
            if (is_null($value)) {
                if (array_key_exists(self::PARAM_SLUG_REQUIRE, $settings) && $settings[self::PARAM_SLUG_REQUIRE] === true) {
                    throw new DeCaptchaErrors(DeCaptchaErrors::ERROR_PARAM_REQUIRE, array_key_exists($field, $this->paramsNames) ? $this->paramsNames[$field] : $field, $this->errorLang);
                }
                continue;
            }
            if (array_key_exists($field, $this->paramsNames)) {
                switch ($settings[self::PARAM_SLUG_TYPE]) {
                    case self::PARAM_FIELD_TYPE_FLOAT:
                        $value = (float) $value;
                        break;
                    case self::PARAM_FIELD_TYPE_INTEGER:
                        $value = (int) $value;
                        break;
                    case self::PARAM_FIELD_TYPE_STRING:
                        $value = (string) $value;
                        break;
                    case self::PARAM_FIELD_TYPE_BOOLEAN:
                        $value = (bool) $value;
                        break;
                    case self::PARAM_FIELD_TYPE_OBJECT:
                        $value = $this->getParams($action, $field);
                        break;
                }
                if (array_key_exists(self::PARAM_SLUG_ENUM, $settings) && !in_array($value, $settings[static::PARAM_SLUG_ENUM])) {
                    throw new DeCaptchaErrors(DeCaptchaErrors::ERROR_PARAM_ENUM, (array_key_exists($field, $this->paramsNames) ? $this->paramsNames[$field] : $field).' = '.$value, $this->errorLang);
                }
                $params[$this->paramsNames[$field]] = $value;
            }
        }

        return $params;
    }

    /**
     * @param string $action
     *
     * @return string
     */
    protected function getResponse($action)
    {
        return $this->curlResponse(
            $this->getActionUrl($action),
            $this->getParams($action),
            array_key_exists(static::ACTION_METHOD, $this->actions[$action]) && $this->actions[$action][static::ACTION_METHOD] === static::ACTION_METHOD_POST,
            array_key_exists(static::ACTION_JSON, $this->actions[$action]) && $this->actions[$action][static::ACTION_JSON] === true
        );
    }

    /**
     * Задержка выполнения.
     *
     * @param int           $delay    Количество секунд
     * @param \Closure|null $callback
     *
     * @return mixed
     */
    protected function executionDelayed($delay = 0, $callback = null)
    {
        $time = microtime(true);
        $timePassed = $time - $this->lastRunTime;
        if ($timePassed < $delay) {
            usleep(($delay - $timePassed) * 1000000);
        }
        $this->lastRunTime = microtime(true);

        return $callback instanceof \Closure ? $callback($this) : $callback;
    }

    /**
     * @param string $url
     * @param array  $data
     * @param bool   $isPost
     * @param bool   $isJson
     *
     * @throws DeCaptchaErrors
     *
     * @return string
     */
    protected function curlResponse($url, $data, $isPost = true, $isJson = false)
    {
        $curl = curl_init();
        if ($isJson) {
            $data = json_encode($data);
        } elseif (!$isPost) {
            $uri = [];
            foreach ($data as $key => $value) {
                $uri[] = "$key=$value";
            }
            $url .= '?'.implode('&', $uri);
        }
        curl_setopt($curl, CURLOPT_URL, $url);
        if (!$isJson && version_compare(PHP_VERSION, '5.5.0') >= 0 && version_compare(PHP_VERSION, '7.0') < 0 && defined('CURLOPT_SAFE_UPLOAD')) {
            curl_setopt($curl, CURLOPT_SAFE_UPLOAD, false);
        }
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate');
        curl_setopt($curl, CURLOPT_TIMEOUT, 30);
        curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 30);
        curl_setopt($curl, CURLOPT_POST, $isPost);
        if ($isPost) {
            curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        }
        if ($isJson) {
            curl_setopt($curl, CURLOPT_HTTPHEADER, [
                'Content-Type: application/json; charset=utf-8',
                'Accept: application/json',
                'Content-Length: '.strlen($data),
            ]);
        }
        $result = curl_exec($curl);
        if (curl_errno($curl)) {
            throw new DeCaptchaErrors(DeCaptchaErrors::ERROR_CURL, curl_error($curl), $this->errorLang);
        }
        curl_close($curl);

        return $result;
    }

    abstract public function getCode();

    abstract public function getError();
}