1:   2:   3:   4:   5:   6:   7:   8:   9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49:  50:  51:  52:  53:  54:  55:  56:  57:  58:  59:  60:  61:  62:  63:  64:  65:  66:  67:  68:  69:  70:  71:  72:  73:  74:  75:  76:  77:  78:  79:  80:  81:  82:  83:  84:  85:  86:  87:  88:  89:  90:  91:  92:  93:  94:  95:  96:  97:  98:  99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 
<?php
/**
 * Erply Books API PHP client
 *
 * @author Rene Korss <rene@koren.ee>
 * @copyright Copyright (c) 2020 Rene Korss (https://koren.ee)
 */

namespace Koren\ErplyBooks;

use Http\Adapter\Guzzle6\Client as GuzzleClient;
use Http\Client\HttpClient;
use InvalidArgumentException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Erply Books API PHP client Client class
 *
 * @author Rene Korss <rene@koren.ee>
 * @copyright Copyright (c) 2020 Rene Korss (https://koren.ee)
 * @license MIT
 */
class Client
{
    /**
     * API base URL
     * @var string
     */
    const BASE_URL = 'https://accounting.erply.com/api';

    /**
     * API token
     * @var String
     */
    protected $token;

    /**
     * Http Client
     * @var HttpClient
     */
    protected $httpClient;

    /**
     * Additional query params
     * @var array
     */
    protected $query = [];

    /**
     * API client constructor
     *
     * @param string $token API token
     * @param \Http\Client\HttpClient $client HTTP Client used for requests
     */
    public function __construct($token, HttpClient $client = null)
    {
        // Fallback to Guzzle
        if (is_null($client)) {
            $client = new GuzzleClient();
        }

        $this->token = $token;

        $this->setHttpClient($client);
    }


    /**
     * Get API token
     */
    public function getApiToken() : string
    {
        return $this->token;
    }

    /**
     * Set the Http Client used for API requests
     *
     * This allows the default http client to be swapped out for a HTTPlug compatible
     * replacement.
     *
     * @param \Http\Client\HttpClient $client
     *
     * @return \Resolve\Api\Client
     */
    public function setHttpClient(HttpClient $client) : self
    {
        $this->httpClient = $client;
        return $this;
    }

    /**
     * Get the Http Client used for API requests
     *
     * @return \Http\Client\HttpClient
     */
    public function getHttpClient() : HttpClient
    {
        return $this->httpClient;
    }

    /**
     * Adds token to request
     *
     * @param \Psr\Http\Message\RequestInterface $request Request being sent
     *
     * @return \Psr\Http\Message\RequestInterface
     */
    public function authenticate(RequestInterface $request) : RequestInterface
    {
        $credentials = ['token' => $this->token];

        $request = $request->withHeader('Content-Type', 'application/json');
        $body = $request->getBody();
        $body->rewind();
        $content = $body->getContents();
        $params = json_decode($content, true);
        $params = array_merge((array)$params, $credentials);
        $body->rewind();
        $body->write(json_encode($params));

        $request = $request->withHeader('Accept', 'application/json');

        return $request;
    }

    /**
     * Add query param
     *
     * @param array $param Key and value array of param
     *
     * @return \Resolve\Api\Client
     */
    public function withQuery(array $param) : self
    {
        $this->query = array_merge($this->query, $param);
        return $this;
    }

    /**
     * Add query param
     *
     * @param \Psr\Http\Message\RequestInterface $request Request being sent
     *
     * @return \Psr\Http\Message\RequestInterface
     */
    protected function withQueryParams(RequestInterface $request) : RequestInterface
    {
        if (count($this->query) > 0) {
            $query = [];
            parse_str($request->getUri()->getQuery(), $query);
            $query = array_merge($query, $this->query);
            $request = $request->withUri($request->getUri()->withQuery(http_build_query($query)));
        }

        return $request;
    }

    /**
     * Send request
     *
     * @param \Psr\Http\Message\RequestInterface $request Request being sent
     *
     * @return \Psr\Http\Message\ResponseInterface
     */
    public function sendRequest(RequestInterface $request) : ResponseInterface
    {
        // Add apiKey to request
        $request = $this->authenticate($request);

        // Add query params if any
        $request = $this->withQueryParams($request);

        $response = $this->getHttpClient()->sendRequest($request);
        return $response;
    }

    /**
     * Magic method to get resource object
     *
     * @return mixed Resource
     *
     * @throws \InvalidArgumentException if resource is not found
     * @ignore
     * @SuppressWarnings(PHPMD.MissingImport)
     */
    public function __call($name, $args)
    {
        $className = 'Koren\ErplyBooks\Resource\\'.$name;

        if (!class_exists($className)) {
            throw new InvalidArgumentException('Resource '.$name.' not found.');
        }

        $resource = new $className;
        $resource->setClient($this);

        return $resource;
    }
}