Source of file NetWrapper.php

Size: 22,123 Bytes - Last Modified: 2020-10-25T23:00:04+00:00

/root/gitwork/work/tornelib-php-netcurl-6.1/src/Module/Network/NetWrapper.php

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764
<?php
/**
 * Copyright © Tomas Tornevall / Tornevall Networks. All rights reserved.
 * See LICENSE.md for license details.
 */

namespace TorneLIB\Module\Network;

use Exception;
use ReflectionException;
use TorneLIB\Exception\Constants;
use TorneLIB\Exception\ExceptionHandler;
use TorneLIB\IO\Data\Arrays;
use TorneLIB\IO\Data\Content;
use TorneLIB\Model\Interfaces\WrapperInterface;
use TorneLIB\Model\Type\authType;
use TorneLIB\Model\Type\dataType;
use TorneLIB\Model\Type\requestMethod;
use TorneLIB\Module\Config\WrapperConfig;
use TorneLIB\Module\Config\WrapperDriver;
use TorneLIB\Utils\Generic;
use TorneLIB\Utils\Security;

/**
 * Class NetWrapper
 * Taking over from v6.0 MODULE_CURL.
 *
 * @package TorneLIB\Module\Network
 */
class NetWrapper implements WrapperInterface
{
    /**
     * @var WrapperConfig $CONFIG
     * @since 6.1.0
     */
    private $CONFIG;

    /**
     * Chosen wrapper.
     * @var string $selectedWrapper
     * @since 6.1.0
     */
    private $selectedWrapper;

    /**
     * @var bool
     * @since 6.1.0
     */
    private $isSoapRequest = false;

    /**
     * @var array
     * @since 6.1.0
     */
    private $multiRequest = [];

    /**
     * @var bool
     * @since 6.1.0
     */
    private $allowInternalMulti = false;

    /**
     * @var bool
     * @since 6.1.0
     */
    private $instantCurlMultiErrors = false;

    public function __construct()
    {
        $this->initializeWrappers();
    }

    /**
     * @since 6.1.0
     */
    private function initializeWrappers()
    {
        WrapperDriver::initializeWrappers();
        $this->CONFIG = new WrapperConfig();
        return $this;
    }

    /**
     * Allows strict identification in user-agent header.
     * @param $activation
     * @param $allowPhpRelease
     * @return NetWrapper
     * @since 6.1.0
     */
    public function setIdentifiers($activation, $allowPhpRelease = false)
    {
        $this->CONFIG->setIdentifiers($activation, $allowPhpRelease);

        return $this;
    }

    /**
     * @param string $key
     * @param string $value
     * @param false $static
     * @return NetWrapper
     * @since 6.1.2
     */
    public function setHeader($key = '', $value = '', $static = false)
    {
        $this->CONFIG->setHeader($key, $value, $static);
        return $this;
    }

    /**
     * @return string
     * @throws ExceptionHandler
     * @throws ReflectionException
     * @since 6.1.0
     */
    public function getVersion()
    {
        return isset($this->version) && !empty($this->version) ?
            $this->version : (new Generic())->getVersionByAny(__DIR__, 3, WrapperConfig::class);
    }

    /**
     * @return bool
     * @since 6.1.0
     */
    public function getIsSoap()
    {
        return $this->isSoapRequest;
    }

    /**
     * @var WrapperInterface $instance The instance is normally the wrapperinterface.
     * @since 6.1.0
     */
    private $instance;

    /**
     * Get list of internal wrappers.
     *
     * @return mixed
     * @since 6.1.0
     */
    public function getWrappers()
    {
        return WrapperDriver::getWrappers();
    }

    /**
     * @return bool
     * @since 6.1.0
     */
    public function getAllowInternalMulti()
    {
        return $this->allowInternalMulti;
    }

    /**
     * @param bool $allowInternalMulti
     * @return NetWrapper
     * @since 6.1.0
     */
    public function setAllowInternalMulti($allowInternalMulti = false)
    {
        $this->allowInternalMulti = $allowInternalMulti;

        return $this;
    }

    /**
     * @param WrapperConfig $config
     * @return NetWrapper
     * @since 6.1.0
     */
    public function setConfig($config)
    {
        /** @var WrapperConfig CONFIG */
        $this->CONFIG = $config;

        return $this;
    }

    /**
     * @return WrapperConfig
     * @since 6.1.0
     */
    public function getConfig()
    {
        return $this->CONFIG;
    }

    /**
     * @param $username
     * @param $password
     * @param int $authType
     * @return NetWrapper
     * @since 6.1.0
     */
    public function setAuthentication($username, $password, $authType = authType::BASIC)
    {
        $this->CONFIG->setAuthentication($username, $password, $authType);

        return $this;
    }

    /**
     * @return array
     * @since 6.1.0
     */
    public function getAuthentication()
    {
        return $this->CONFIG->getAuthentication();
    }

    /**
     * Register an external wrapper/module/communicator.
     *
     * @param $wrapperClass
     * @param bool $tryFirst
     * @return string
     * @throws ExceptionHandler
     * @since 6.1.0
     */
    public function register($wrapperClass, $tryFirst = false)
    {
        return WrapperDriver::register($wrapperClass, $tryFirst);
    }

    /**
     * Return body of the request.
     *
     * @param string $url
     * @return mixed
     * @throws ExceptionHandler
     * @since 6.1.0
     */
    public function getBody($url = '')
    {
        if (($mInstance = $this->getMultiInstance($url)) &&
            !empty($mInstance) &&
            method_exists($mInstance, __FUNCTION__)
        ) {
            return $mInstance->{__FUNCTION__}();
        }

        if (!empty($this->instance) && method_exists($this->instance, __FUNCTION__)) {
            return $this->instance->{__FUNCTION__}();
        }

        throw new ExceptionHandler(
            sprintf(
                '%s instance %s does not support %s.',
                __CLASS__,
                WrapperDriver::getInstanceClass(),
                __FUNCTION__
            )
        );
    }

    /**
     * @param string $url
     * @return mixed
     * @throws ExceptionHandler
     * @since 6.1.0
     */
    public function getParsed($url = '')
    {
        if (($mInstance = $this->getMultiInstance($url)) &&
            !empty($mInstance) &&
            method_exists($mInstance, 'getParsed')
        ) {
            return $mInstance->getParsed();
        }

        if (!empty($this->instance) && method_exists($this->instance, 'getBody')) {
            return $this->instance->getParsed();
        }

        throw new ExceptionHandler(
            sprintf(
                '%s instance %s does not support %s.',
                __CLASS__,
                WrapperDriver::getInstanceClass(),
                __FUNCTION__
            )
        );
    }

    /**
     * @param string $url
     * @return mixed|null
     * @since 6.1.0
     */
    private function getMultiInstance($url = '')
    {
        $return = null;
        if (isset($this->multiRequest[$url])) {
            $return = $this->multiRequest[$url];
        } elseif (is_object($this->instance)) {
            $return = $this->instance;
        }

        return $return;
    }

    /**
     * @param string $url
     * @return mixed
     * @throws ExceptionHandler
     * @since 6.1.0
     * @noinspection DuplicatedCode
     */
    public function getCode($url = '')
    {
        if (($mInstance = $this->getMultiInstance($url)) &&
            !empty($mInstance) &&
            method_exists($mInstance, __FUNCTION__)
        ) {
            return $mInstance->{__FUNCTION__}();
        }

        if (!empty($this->instance) &&
            method_exists($this->instance, __FUNCTION__)
        ) {
            return $this->instance->{__FUNCTION__}();
        }

        throw new ExceptionHandler(
            sprintf(
                '%s instance %s does not support %s.',
                __CLASS__,
                WrapperDriver::getInstanceClass(),
                __FUNCTION__
            )
        );
    }

    /**
     * @param $url
     * @return WrapperInterface
     * @since 6.1.0
     */
    public function getWrapper($url)
    {
        return $this->getMultiInstance($url);
    }

    /**
     * @param $requestArray
     * @return array
     * @throws ExceptionHandler
     * @since 6.1.0
     */
    private function handleMultiUrl($requestArray)
    {
        $return = [];
        $isAssocList = (new Arrays())->isAssoc($requestArray);
        foreach ($requestArray as $requestUrl => $requestData) {
            $currentRequestUrl = $requestUrl;
            if (!$isAssocList) {
                $currentRequestUrl = $requestData;
            }
            if (isset($requestData[3]) &&
                is_object($requestData[3]) &&
                $requestData[3] instanceof WrapperConfig
            ) {
                $this->CONFIG = $requestData[3];
            }
            $return[$currentRequestUrl] = $this->request(
                $currentRequestUrl,
                isset($requestData[0]) ? $requestData[0] : [],
                isset($requestData[1]) ? $requestData[1] : requestMethod::METHOD_GET,
                isset($requestData[2]) ? $requestData[2] : dataType::NORMAL
            );
        }
        return $return;
    }

    /**
     * @param $url
     * @return NetWrapper
     * @throws ExceptionHandler
     * @since 6.1.0
     */
    private function getMultiInternalRequest($url)
    {
        $this->multiRequest = $this->handleMultiUrl($url);
        return $this;
    }

    /**
     * @inheritDoc
     * @throws ExceptionHandler
     * @since 6.1.0
     */
    public function request($url, $data = [], $method = requestMethod::METHOD_GET, $dataType = dataType::NORMAL)
    {
        if (is_array($url)) {
            // If url list an associative array or allowed to run arrays that is not associative, run this
            // kind of request instead of the default. The regular indexed arraylist will be passed over to
            // curl_multi requests instead of collecting the requests. Besides error handling in non-assoc
            // is not collected, any exception will be thrown immediately.
            if ($this->getAllowInternalMulti() || (new Arrays())->isAssoc($url)) {
                // As those requests are arrayed differently, the regular parameters above do not apply here.
                // If they are not associative, they will also lose all attributes which is why we want them
                // associative or not at all.
                return $this->getMultiInternalRequest($url);
            }
        }

        $this->CONFIG->setNetWrapper(true);
        $return = null;
        $requestexternalExecute = null;

        $externalWrapperList = WrapperDriver::getExternalWrappers();
        if (WrapperDriver::getRegisteredWrappersFirst() && count($externalWrapperList)) {
            try {
                $returnable = $this->requestExternalExecute($url, $data, $method, $dataType);
                if (!is_null($returnable)) {
                    return $returnable;
                }
            } catch (ExceptionHandler $requestexternalExecute) {
            }
        }

        // Run internal wrappers.
        if ($hasReturnedRequest = $this->getResultFromInternals(
            $url,
            $data,
            $method,
            $dataType
        )) {
            $return = $hasReturnedRequest;
        }

        $externalWrapperList = WrapperDriver::getExternalWrappers();
        // Internal handles are usually throwing execptions before landing here.
        if (is_null($return) &&
            !WrapperDriver::getRegisteredWrappersFirst() &&
            count($externalWrapperList)
        ) {
            // Last execution should render errors thrown from external executes.
            $returnable = $this->requestExternalExecute($url, $data, $method, $dataType);
            if (!is_null($returnable)) {
                return $returnable;
            }
        }

        $this->getInstantiationException($return, __CLASS__, __FILE__, $requestexternalExecute);

        return $return;
    }

    /**
     * Make request from built in wrappers (the internally supported).
     *
     * @param $url
     * @param array $data
     * @param int $method
     * @param int $dataType
     * @return mixed|null
     * @throws ExceptionHandler
     * @since 6.1.0
     */
    private function getResultFromInternals(
        $url,
        $data = [],
        $method = requestMethod::METHOD_GET,
        $dataType = dataType::NORMAL
    ) {
        $return = null;

        // Header setup is only supported by internal requests. If external requests are used,
        // the developer is on her/his own.
        $headerArray = $this->CONFIG->getHeader();

        if (!is_array($url) && (bool)preg_match('/\?wsdl|&wsdl/i', $url)) {
            try {
                Security::getCurrentClassState('SoapClient');
                $dataType = dataType::SOAP;
            } catch (ExceptionHandler $e) {
                $method = requestMethod::METHOD_POST;
                $dataType = dataType::XML;
                /** @noinspection CallableParameterUseCaseInTypeContextInspection */
                if (!is_string($data) && !empty($data)) {
                    $data = (new Content())->getXmlFromArray($data);
                }
            }
        }

        // Example from tornelib-php-drivertest.
        // This allows us to add internal supported drivers without including them in this specific package.
        //$testWrapper = WrapperDriver::getWrapperAllowed('myNameSpace\myDriver');

        if ($dataType === dataType::SOAP && ($this->getProperInstanceWrapper('SoapClientWrapper'))) {
            $this->isSoapRequest = true;
            $this->instance->setConfig($this->getConfig());
            $return = $this->instance->request($url, $data, $method, $dataType);
        } elseif ($dataType === dataType::RSS_XML && $this->getProperInstanceWrapper('RssWrapper')) {
            $this->instance->setConfig($this->getConfig());
            $return = $this->instance->request($url, $data, $method, $dataType);
        } elseif ($this->getProperInstanceWrapper('CurlWrapper')) {
            $this->instance->setHeader($headerArray, null, false);
            $this->instance->setConfig($this->getConfig());
            /** @noinspection PhpPossiblePolymorphicInvocationInspection */
            // No inspection since assuming this is always a curl-based call.
            $this->instance->setCurlMultiInstantException($this->instantCurlMultiErrors);
            $return = $this->instance->request($url, $data, $method, $dataType);
        } elseif ($this->getProperInstanceWrapper('SimpleStreamWrapper')) {
            $this->instance->setHeader($headerArray, null, false);
            $currentConfig = $this->getConfig();
            // Check if auth is properly set, in case default setup is used.
            $currentConfig->setAuthStream();
            $this->instance->setConfig($currentConfig);
            $return = $this->instance->request($url, $data, $method, $dataType);
        }

        return $return;
    }

    /**
     * Set up proxy.
     *
     * @param string $proxyAddress Normal usage is address:post.
     * @param int $proxyType Default: 0 = HTTP
     * @return $this
     * @since 6.1.0
     */
    public function setProxy($proxyAddress, $proxyType = 0)
    {
        $this->CONFIG->setProxy($proxyAddress, $proxyType);

        return $this;
    }

    /**
     * Set up so that curlwrapper makes instant exceptions on curl_multi requests, if they occur. Mirrormethod.
     *
     * @param bool $throwInstant
     * @return $this
     * @since 6.1.0
     */
    public function setCurlMultiInstantException($throwInstant = true)
    {
        $this->instantCurlMultiErrors = $throwInstant;
        return $this;
    }

    /**
     * Get proper instance.
     *
     * @param $wrapperName
     * @return mixed|WrapperInterface
     * @throws ExceptionHandler
     * @since 6.1.0
     */
    private function getProperInstanceWrapper($wrapperName)
    {
        $this->instance = WrapperDriver::getWrapperAllowed($wrapperName, true);

        if (!is_null($this->instance)) {
            $this->selectedWrapper = get_class($this->instance);
        }

        return $this->instance;
    }

    /**
     * Entry point.
     *
     * @return string
     * @since 6.1.1
     */
    public function getSelectedWrapper()
    {
        return $this->selectedWrapper;
    }

    /**
     * Get current used wrapper class name (short or with full namespace).
     * @param bool $short
     * @return string
     * @since 6.1.0
     */
    public function getCurrentWrapperClass($short = false)
    {
        return $this->CONFIG->getCurrentWrapperClass($short);
    }

    /**
     * Check if return value is null and if, do thrown an exception. This is done if no instances has been successfully
     * created during request.
     *
     * @param $nullValue
     * @param $className
     * @param $functionName
     * @param $requestexternalExecute
     * @throws ExceptionHandler
     * @since 6.1.0
     */
    private function getInstantiationException($nullValue, $className, $functionName, $requestexternalExecute)
    {
        if (is_null($nullValue)) {
            throw new ExceptionHandler(
                sprintf(
                    '%s instantiation failure: No communication wrappers currently available in function/class %s.',
                    $className,
                    $functionName
                ),
                Constants::LIB_NETCURL_NETWRAPPER_NO_DRIVER_FOUND,
                $requestexternalExecute
            );
        }
    }

    /**
     * Initiate an external wrapper request. This actually initiates a "wrapper loop" that runs through
     * each registered wrapper and uses the first that responds correctly. Method is collected here as it
     * runs both in the top of request (if prioritized like that) and in the bottom if developers primarily
     * prefers to use internal classes before their own.
     *
     * @param $url
     * @param array $data
     * @param int $method
     * @param int $dataType
     * @return mixed|null
     * @throws ExceptionHandler
     * @since 6.1.0
     */
    private function requestExternalExecute(
        $url,
        $data = [],
        $method = requestMethod::METHOD_GET,
        $dataType = dataType::NORMAL
    ) {
        $this->CONFIG->getStreamHeader();
        $externalHasErrors = false;
        $externalRequestException = null;
        $returnable = null;
        try {
            $returnable = $this->requestExternal(
                $url,
                $data,
                $method,
                $dataType
            );
        } catch (ExceptionHandler $externalRequestException) {
            // Ignore errors here as we have more to go.
            $externalHasErrors = true;
        }
        if (!$externalHasErrors) {
            return $returnable;
        }

        throw new ExceptionHandler(
            sprintf(
                'Internal %s error.',
                __FUNCTION__
            ),
            Constants::LIB_UNHANDLED,
            $externalRequestException
        );
    }

    /**
     * Make a request with help from external wrappers.
     *
     * @param $url
     * @param array $data
     * @param int $method
     * @param int $dataType
     * @return mixed|null
     * @throws ExceptionHandler
     * @since 6.1.0
     */
    private function requestExternal(
        $url,
        $data = [],
        $method = requestMethod::METHOD_GET,
        $dataType = dataType::NORMAL
    ) {
        $return = null;
        $hasInternalSuccess = false;
        $externalWrapperList = WrapperDriver::getExternalWrappers();
        // Walk through external wrappers.
        foreach ($externalWrapperList as $wrapperClass) {
            $returnable = null;
            try {
                // Assuming request is always available via registered implementations, we don't need
                // to use call_user_func_array at all.
                if (method_exists($wrapperClass, 'request')) {
                    $this->CONFIG->setCurrentWrapper(get_class($wrapperClass));
                    $returnable = $wrapperClass->request($url, $data, $method, $dataType);
                }
            } catch (Exception $externalException) {
            }
            // Break on first success.
            if (!is_null($returnable)) {
                $hasInternalSuccess = true;
                $return = $returnable;
                break;
            }
        }

        if (!$hasInternalSuccess) {
            throw new ExceptionHandler(
                sprintf(
                    'An error occurred when configured external wrappers tried to communicate with %s.',
                    $url
                ),
                isset($externalException) ? $externalException->getCode() : Constants::LIB_UNHANDLED,
                isset($externalException) ? $externalException : null
            );
        }

        return $return;
    }

    /**
     * @param $name
     * @param $arguments
     * @return mixed
     * @throws ExceptionHandler
     * @since 6.1.0
     */
    public function __call($name, $arguments)
    {
        $compatibilityMethods = $this->CONFIG->getCompatibilityMethods();
        if (isset($compatibilityMethods[$name])) {
            $name = $compatibilityMethods[$name];
        }

        if ($name === 'setAuth') {
            // Abbreviation for setAuthentication.
            return call_user_func_array([$this, 'setAuthentication'], $arguments);
        }

        // From PHP 8.0 just checking instance content will fail if it is null here.
        if (!empty($this->instance) && method_exists($this->instance, $name)) {
            if ($instanceRequest = call_user_func_array([$this->instance, $name], $arguments)) {
                return $instanceRequest;
            }
        } elseif (method_exists($this->CONFIG, $name)) {
            call_user_func_array(
                [
                    $this->CONFIG,
                    $name,
                ],
                $arguments
            );
        } else {
            throw new ExceptionHandler(
                sprintf('Undefined function: %s', $name),
                Constants::LIB_METHOD_OR_LIBRARY_UNAVAILABLE,
                null,
                null,
                $name
            );
        }

        return $this;
    }
}