Source of file SoapClientWrapper.php

Size: 23,580 Bytes - Last Modified: 2020-10-25T23:00:04+00:00

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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884
<?php /** @noinspection PhpComposerExtensionStubsInspection */

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

namespace TorneLIB\Module\Network\Wrappers;

use Exception;
use ReflectionException;
use SoapClient;
use SoapFault;
use TorneLIB\Exception\Constants;
use TorneLIB\Exception\ExceptionHandler;
use TorneLIB\Helpers\GenericParser;
use TorneLIB\IO\Data\Strings;
use TorneLIB\Model\Interfaces\WrapperInterface;
use TorneLIB\Model\Type\authSource;
use TorneLIB\Model\Type\authType;
use TorneLIB\Model\Type\dataType;
use TorneLIB\Model\Type\requestMethod;
use TorneLIB\Module\Config\WrapperConfig;
use TorneLIB\Utils\Generic;
use TorneLIB\Utils\Security;

/**
 * Class SoapClientWrapper
 *
 * @package TorneLIB\Module\Network\Wrappers
 */
class SoapClientWrapper implements WrapperInterface
{
    /**
     * @var WrapperConfig $CONFIG
     * @since 6.1.0
     */
    private $CONFIG;

    /**
     * @var SoapClient $soapClient
     * @since 6.1.0
     */
    private $soapClient;

    /**
     * @var $soapClientResponse
     * @since 6.1.0
     */
    private $soapClientResponse;

    /**
     * @var array $soapClientContent
     * @since 6.1.0
     */
    private $soapClientContent = [
        'lastRequest' => null,
        'lastRequestHeaders' => null,
        'lastResponse' => null,
        'lastResponseHeaders' => null,
        'functions' => null,
    ];

    /**
     * @var string
     * @since 6.1.0
     */
    private $soapProxyHost = '';

    /**
     * @var int
     * @since 6.1.0
     */
    private $soapProxyPort = 80;

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

    /**
     * The header that the soapResponse are returning, converted to an array.
     *
     * @var array $responseHeaderArray
     * @since 6.1.0
     */
    private $responseHeaderArray = [];

    /**
     * @var array $soapWarningException
     * @since 6.1.0
     */
    private $soapWarningException = ['code' => 0, 'string' => null];

    /**
     * Reuse soapClient session if this is true. By means, it will be reinitialized on each call otherwise.
     * @var bool
     * @since 6.1.0
     */
    private $reuseSoapClient = false;

    /**
     * @var
     */
    private $currentErrorHandler;

    /**
     * SoapClientWrapper constructor.
     * @throws ExceptionHandler
     * @throws Exception
     */
    public function __construct()
    {
        Security::getCurrentClassState('SoapClient');

        $this->CONFIG = new WrapperConfig();
        $this->CONFIG->setSoapRequest(true);
        $this->CONFIG->setCurrentWrapper(__CLASS__);
        $this->getPriorCompatibilityArguments(func_get_args());
    }

    /**
     * If staging is false, we're considering production mode.
     * @param bool $staging
     * @return WrapperConfig
     * @since 6.1.0
     */
    public function setStaging($staging = true)
    {
        return $this->CONFIG->setStaging($staging);
    }

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

    /**
     * @param bool $production
     * @return WrapperConfig
     * @since 6.1.0
     */
    public function setProduction($production = true)
    {
        return $this->CONFIG->setProduction($production);
    }

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

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

    /**
     * Reverse compatibility with v6.0 - returns true if any of the settings here are touched.
     * Main function as it is duplicated is moved into WrapprConfig->getCompatibilityArguments()
     *
     * @param array $funcArgs
     * @return bool
     * @throws Exception
     * @since 6.1.0
     */
    private function getPriorCompatibilityArguments($funcArgs = [])
    {
        return $this->CONFIG->getCompatibilityArguments($funcArgs);
    }

    /**
     * @return mixed
     * @since 6.1.0
     */
    public function getLastRequest()
    {
        return (string)$this->soapClientContent['lastRequest'];
    }

    /**
     * @return mixed
     * @since 6.1.0
     */
    public function getLastRequestHeaders()
    {
        return (string)$this->soapClientContent['lastRequestHeaders'];
    }

    /**
     * @return bool
     * @since 6.1.0;
     */
    public function getLastResponse()
    {
        return (string)$this->soapClientContent['lastResponse'];
    }

    /**
     * @return string
     * @since 6.1.0
     */
    public function getLastResponseHeaders()
    {
        return (string)$this->soapClientContent['lastResponseHeaders'];
    }

    /**
     * Returns an array of function from the soapcall.
     *
     * @return array
     * @since 6.1.0
     */
    public function getFunctions()
    {
        return (array)$this->soapClientContent['functions'];
    }

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

        return $this;
    }

    /**
     * @param $config
     * @return mixed
     * @since 6.1.0
     */
    private function getInheritedConfig($config)
    {
        $config->setCurrentWrapper($this->CONFIG->getCurrentWrapper());

        return $config;
    }

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

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

        return $this;
    }

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

    /**
     * @throws ExceptionHandler
     * @throws SoapFault
     * @since 6.1.0
     */
    private function getSoapClient()
    {
        $this->CONFIG->getOptions();
        $this->getSoapInitErrorHandler();
        $streamOpt = $this->getPreparedProxyOptions($this->getConfig()->getStreamOptions());
        // version_compare(PHP_VERSION, '7.1.0', '>=')
        if (PHP_VERSION_ID >= 70100) {
            $this->soapClient = new SoapClient(
                $this->getConfig()->getRequestUrl(),
                $streamOpt
            );
        } else {
            // Suppress fatals in older releases.
            $this->soapClient = @(new SoapClient(
                $this->getConfig()->getRequestUrl(),
                $streamOpt
            ));
        }
    }

    /**
     * @param $streamOptions
     * @return mixed
     * @since 6.1.0
     */
    private function getPreparedProxyOptions($streamOptions)
    {
        if (!empty($this->soapProxyHost)) {
            $streamOptions['proxy_host'] = $this->soapProxyHost;
            $streamOptions['proxy_port'] = $this->soapProxyPort;
        }

        if (is_array($this->soapProxyOptions)) {
            /** @noinspection AdditionOperationOnArraysInspection */
            // + is intended.
            $streamOptions += $this->soapProxyOptions;
        }

        return $streamOptions;
    }

    /**
     * SOAP initializer.
     * Formerly known as a simpleSoap getSoap() variant.
     *
     * @return $this
     * @throws ExceptionHandler
     * @throws Exception
     * @since 6.1.0
     */
    private function getSoapInit()
    {
        try {
            $this->getSoapClient();
        } catch (Exception $soapException) {
            if ((int)$soapException->getCode()) {
                throw $soapException;
            }

            // Trying to prevent dual requests during a soap-transfer. In v6.0, there was dual initializations of
            // soapclient when potential authfail errors occurred.
            if ((int)$this->soapWarningException['code']) {
                $code = $this->getHttpHead($this->soapWarningException['string']);
                $message = $this->getHttpHead($this->soapWarningException['string'], 'message');

                $this->CONFIG->getHttpException(
                    (int)$code > 0 && !empty($message) ? $message : $this->soapWarningException['string'],
                    (int)$code > 0 ? $code : $this->soapWarningException['code'],
                    null,
                    $this,
                    true
                );
            }
        }
        // Restore the errorhandler immediately after soaprequest if no exceptions are detected during first request.
        restore_error_handler();

        return $this;
    }

    /**
     * @param $string
     * @param string $returnData
     * @return int|string
     * @since 6.1.0
     */
    private function getHttpHead($string, $returnData = 'code')
    {
        return GenericParser::getHttpHead($string, $returnData);
    }

    /**
     * Initialize SoapExceptions for special occasions.
     *
     * @return $this
     * @since 6.1.0
     * @noinspection SuspiciousAssignmentsInspection
     */
    private function getSoapInitErrorHandler()
    {
        // No inspections on this, it is handled properly handled despite the immediate overrider.
        // The overrider is present as it has to be nulled out after each use.
        if (!is_null($this->currentErrorHandler)) {
            restore_error_handler();
            $this->currentErrorHandler = null;
            $this->soapWarningException['code'] = null;
            $this->soapWarningException['string'] = null;
        }
        $this->currentErrorHandler = set_error_handler(function ($errNo, $errStr) {
            if (empty($this->soapWarningException['string'])) {
                $this->soapWarningException['code'] = $errNo;
                $this->soapWarningException['string'] = $errStr;
            }
            restore_error_handler();
            return false;
        }, E_WARNING);

        return $this;
    }

    /**
     * @return mixed
     * @throws ExceptionHandler
     * @since 6.1.0
     */
    public function getStreamContext()
    {
        $currentStreamContext = $this->getConfig()->getStreamContext();
        if (is_null($currentStreamContext)) {
            // Not properly initialized yet.
            $this->getConfig()->getStreamOptions();
            $currentStreamContext = $this->getConfig()->getStreamContext();
        }

        if (is_resource($currentStreamContext)) {
            $currentStreamContext = stream_context_get_options($currentStreamContext);
        }

        return $currentStreamContext;
    }

    /**
     * Interface Request Method. Barely not in used in this service.
     *
     * @param $url
     * @param array $data
     * @param int $method Not in use.
     * @param int $dataType Not in use, as we are located in the world of SOAP.
     * @return $this|mixed
     * @since 6.1.0
     */
    public function request($url, $data = [], $method = requestMethod::METHOD_GET, $dataType = dataType::SOAP)
    {
        if (!empty($url)) {
            $this->CONFIG->setRequestUrl($url);
        }
        if (is_array($data) && count($data)) {
            $this->CONFIG->setRequestData($data);
        }

        if ($this->CONFIG->getRequestDataType() !== $dataType) {
            $this->CONFIG->setRequestDataType($dataType);
        }

        return $this;
    }

    /**
     * @return SoapClientWrapper
     * @since 6.1.0
     */
    private function setMergedSoapResponse()
    {
        foreach ($this->soapClientContent as $soapMethod => $value) {
            $methodName = sprintf(
                '__get%s',
                ucfirst($soapMethod)
            );
            $this->soapClientContent[$soapMethod] = $this->getFromSoap($methodName);
        }

        return $this;
    }

    /**
     * @param $methodName
     * @return mixed|null
     * @since 6.1.0
     */
    private function getFromSoap($methodName)
    {
        $return = null;

        if (!empty($this->soapClient) && method_exists($this->soapClient, $methodName)) {
            $return = call_user_func_array([$this->soapClient, $methodName], []);
        }

        return $return;
    }

    /**
     * @param $userAgentString
     * @return WrapperConfig
     * @since 6.1.0
     * @noinspection PhpUnusedPrivateMethodInspection
     */
    private function setUserAgent($userAgentString)
    {
        return $this->CONFIG->setUserAgent($userAgentString);
    }

    /**
     * @return mixed
     * @throws ExceptionHandler
     * @since 6.1.0
     * @noinspection PhpUnusedPrivateMethodInspection
     */
    private function getUserAgent()
    {
        return $this->CONFIG->getUserAgent();
    }

    /**
     * @param $methodContent
     * @param $name
     * @param $arguments
     * @return mixed
     * @since 6.1.0
     */
    private function getMagicGettableCall($methodContent, $name, $arguments)
    {
        $return = null;

        if (isset($this->soapClientContent[$methodContent])) {
            $return = $this->soapClientContent[$methodContent];
        } elseif (method_exists($this, $name)) {
            $return = call_user_func_array(
                [
                    $this,
                    $name,
                ],
                $arguments
            );
        }

        return $return;
    }

    /**
     * @param $name
     * @param $arguments
     * @return $this
     * @since 6.1.0
     */
    private function getMagicSettableCall($name, $arguments)
    {
        $return = null;

        if (method_exists($this, $name)) {
            call_user_func_array(
                [
                    $this,
                    $name,
                ],
                $arguments
            );

            $return = $this;
        } elseif (method_exists($this->CONFIG, $name)) {
            call_user_func_array(
                [
                    $this->CONFIG,
                    $name,
                ],
                $arguments
            );

            $return = $this;
        }

        return $return;
    }

    /**
     * @param $name
     * @param $arguments
     * @return mixed
     * @throws Exception
     * @since 6.1.0
     */
    private function execSoap($name, $arguments)
    {
        $return = null;

        /*$soapMethods = [];
        if (method_exists($this->soapClient, '__getFunctions')) {
            $soapMethods = $this->soapClient->__getFunctions();
        }*/

        try {
            // Giving the soapcall a more natural touch with call_user_func_array. Besides, this also means
            // we don't have to check for arguments.
            if (!empty($this->soapClient)) {
                $return = call_user_func_array([$this->soapClient, $name], $arguments);
            } else {
                throw new ExceptionHandler(
                    'SoapClient instance was never initialized.',
                    Constants::LIB_NETCURL_SOAPINSTANCE_MISSING
                );
            }
        } catch (Exception $soapFault) {
            // Public note: Those exceptions may be thrown by the soap-api or when the wsdl is cache and there is
            // for example authorization problems. This is why the soapResponse is fetched and analyzed before
            // giving up.

            // Initialize a merged soapResponse of what's left in this exception - and to see if it was a real
            // api request or a local one.
            $this->setMergedSoapResponse();

            if (!is_null($this->soapClientContent['lastResponseHeaders'])) {
                // Pick up the http-head response from the soapResponseHeader.
                $httpHeader = $this->getHeader('http');

                // Check if it is time to throw something specific.
                $this->CONFIG->getHttpException(
                    $this->getHttpHead($httpHeader, 'message'),
                    $this->getHttpHead($httpHeader),
                    $soapFault,
                    $this
                );
            }

            // Continue throw the soapFault as it.
            throw $soapFault;
        }

        return $return;
    }

    /**
     * Dynamically fetch responses from a soapClientResponse.
     * @param $soapClientResponse
     * @return mixed
     * @since 6.1.0
     */
    private function getSoapResponse($soapClientResponse)
    {
        if (isset($soapClientResponse->return)) {
            $return = $soapClientResponse->return;
        } else {
            $return = $soapClientResponse;
        }

        return $return;
    }

    /**
     * @return int
     * @since 6.1.0
     */
    public function getCode()
    {
        return (int)$this->getHttpHead($this->getHeader('http'));
    }

    /**
     * @param $name
     * @param $arguments
     * @return $this|mixed|null
     * @since 6.1.0
     */
    private function getInternalMagics($name, $arguments)
    {
        $return = null;

        $method = substr($name, 0, 3);
        $methodContent = (new Strings())->getCamelCase(substr($name, 3));

        switch (strtolower($method)) {
            case 'get':
                $getResponse = $this->getMagicGettableCall($methodContent, $name, $arguments);
                if (!is_null($getResponse)) {
                    $return = $getResponse;
                }
                break;
            case 'set':
                $getResponse = $this->getMagicSettableCall($name, $arguments);
                if (!is_null($getResponse)) {
                    $return = $getResponse;
                }
                break;
            default:
                break;
        }

        return $return;
    }

    /**
     * @return mixed
     * @since 6.1.0
     */
    public function getParsed()
    {
        return $this->getSoapResponse($this->soapClientResponse);
    }

    /**
     * @return mixed
     * @since 6.1.0
     */
    public function getBody()
    {
        return $this->getLastResponse();
    }

    /**
     * @param bool $asArray
     * @param bool $lCase
     * @return mixed
     * @since 6.1.0
     */
    public function getHeaders($asArray = false, $lCase = false)
    {
        $return = $this->getLastResponseHeaders();

        if ($asArray) {
            $return = $this->getHeaderArray($this->getLastResponseHeaders(), $lCase);
        }

        return $return;
    }

    /**
     * @param $header
     * @param bool $lCase
     * @return array
     * @since 6.1.0
     */
    private function getHeaderArray($header, $lCase = false)
    {
        $this->responseHeaderArray = [];

        if (is_string($header)) {
            $headerSplit = explode("\n", $header);
            if (is_array($headerSplit)) {
                foreach ($headerSplit as $headerRow) {
                    $this->getHeaderRow($headerRow, $lCase);
                }
            }
        }

        return $this->responseHeaderArray;
    }

    /**
     * @param $header
     * @param bool $lCase
     * @return int
     * @since 6.1.0
     */
    private function getHeaderRow($header, $lCase = false)
    {
        $headSplit = explode(':', $header, 2);
        $spacedSplit = explode(' ', $header, 2);

        if (count($headSplit) < 2) {
            if (count($spacedSplit) > 1) {
                $splitName = !$lCase ? $spacedSplit[0] : strtolower($spacedSplit[0]);

                if ((bool)preg_match('/^http\/(.*?)$/i', $splitName)) {
                    $httpSplitName = explode("/", $splitName, 2);
                    $realSplitName = !$lCase ? $httpSplitName[0] : strtolower($httpSplitName[0]);

                    if (!isset($this->responseHeaderArray[$realSplitName])) {
                        $this->responseHeaderArray[$realSplitName] = trim($spacedSplit[1]);
                    } else {
                        $this->responseHeaderArray[$realSplitName][] = trim($spacedSplit[1]);
                    }
                }

                $this->responseHeaderArray[$splitName][] = trim($spacedSplit[1]);
            }
            return strlen($header);
        }

        $splitName = !$lCase ? $headSplit[0] : strtolower($headSplit[0]);
        $this->responseHeaderArray[$splitName][] = trim($headSplit[1]);
        return strlen($header);
    }

    /**
     * @param $proxyAddress
     * @param array $proxyOptions
     * @return $this
     * @since 6.1.0
     */
    public function setProxy($proxyAddress, $proxyOptions = [])
    {
        $this->CONFIG->setCurrentWrapper(__CLASS__);

        // Add user specific proxy options here as they are not pushed into any stream_context.
        // This is used for proxy authentications.
        $this->soapProxyOptions = $proxyOptions;

        $proxyData = explode(':', $proxyAddress);
        if (isset($proxyData[1])) {
            // SoapClient does not accept stream_context setups, so we'll split it up here and pushing
            // the proxy in from another direction.
            $this->soapProxyHost = $proxyData[0];
            $this->soapProxyPort = $proxyData[1];
        }

        return $this;
    }

    /**
     * @param null $key
     * @return mixed
     * @since 6.1.0
     */
    private function getHeader($key = null)
    {
        if (is_null($key)) {
            $return = $this->getHeaders();
        } else {
            $return = $this->getHeaders(true, true);
        }

        if (isset($return[strtolower($key)])) {
            $return = $return[strtolower($key)];
        }

        return $return;
    }

    /**
     * @param bool $reuseEnabled
     * @return SoapClientWrapper
     * @since 6.1.0
     */
    public function setReuseSoapClient($reuseEnabled = false)
    {
        $this->reuseSoapClient = $reuseEnabled;

        return $this;
    }

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

    /**
     * Dynamic SOAP-requests passing through.
     *
     * @param $name
     * @param $arguments
     * @return SoapClientWrapper
     * @throws ExceptionHandler
     * @throws Exception
     * @since 6.1.0
     */
    public function __call($name, $arguments)
    {
        if (null !== ($internalResponse = $this->getInternalMagics($name, $arguments))) {
            return $internalResponse;
        }

        // Making sure we initialize the soapclient if not already done and only when asked to reuse old sessions.
        // Set higher priority for internal requests and configuration. If there is no reuse setting active
        // it has to be reinitialized as there may be several sessions after each other with different credentials
        // etc.
        if (is_null($this->soapClient) || !$this->getReuseSoapClient()) {
            $this->getSoapInit();
        }

        $this->soapClientResponse = $this->execSoap($name, $arguments);
        $this->setMergedSoapResponse();

        // Return as the last version, if return exists as a response point, we use this part primarily.
        return $this->getSoapResponse($this->soapClientResponse);
    }
}