<?php
namespace DTS\eBaySDK\HalfFinding\Services;

/**
 * Base class for the HalfFinding service.
 */
class HalfFindingBaseService extends \DTS\eBaySDK\Services\BaseService
{
    /**
     * HTTP header constant. The API version your application supports.
     */
    const HDR_API_VERSION = 'X-EBAY-SOA-SERVICE-VERSION';

    /**
     * HTTP header constant. Your application ID.
     */
    const HDR_APP_ID = 'X-EBAY-SOA-SECURITY-APPNAME';

    /**
     * HTTP header constant. The global ID of the eBay site the request is for.
     */
    const HDR_GLOBAL_ID = 'X-EBAY-SOA-GLOBAL-ID';

    /**
     * HTTP header constant. The name of the operation you are calling.
     */
    const HDR_OPERATION_NAME = 'X-EBAY-SOA-OPERATION-NAME';

    /**
     * @param array $config Configuration option values.
     */
    public function __construct(array $config)
    {
        parent::__construct('https://svcs.ebay.com/services/half/HalfFindingService/v1', 'https://svcs.ebay.com/services/half/HalfFindingService/v1', $config);
    }

    /**
     * Returns definitions for each configuration option that is supported.
     *
     * @return array An associative array of configuration definitions.
     */
    public static function getConfigDefinitions()
    {
        $definitions = parent::getConfigDefinitions();

        return $definitions + [
            'apiVersion' => [
                'valid' => ['string'],
                'default' => \DTS\eBaySDK\HalfFinding\Services\HalfFindingService::API_VERSION
            ],
            'globalId' => [
                'valid' => ['string']
            ]
        ];
    }

    /**
     * Builds the needed eBay HTTP headers.
     *
     * @param string $operationName The name of the operation been called.
     *
     * @return array An associative array of eBay http headers.
     */
    protected function getEbayHeaders($operationName)
    {
        $headers = [];

        // Add required headers first.
        $headers[self::HDR_APP_ID] = $this->getConfig('credentials')->getAppId();
        $headers[self::HDR_OPERATION_NAME] = $operationName;

        // Add optional headers.
        if ($this->getConfig('apiVersion')) {
            $headers[self::HDR_API_VERSION] = $this->getConfig('apiVersion');
        }

        if ($this->getConfig('globalId')) {
            $headers[self::HDR_GLOBAL_ID] = $this->getConfig('globalId');
        }

        return $headers;
    }
}


<?php
/**
 * Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
namespace DTS\eBaySDK\JmesPath;

class Utils
{
    static $typeMap = [
        'boolean' => 'boolean',
        'string'  => 'string',
        'NULL'    => 'null',
        'double'  => 'number',
        'float'   => 'number',
        'integer' => 'number'
    ];

    /**
     * Returns true if the value is truthy
     *
     * @param mixed $value Value to check
     *
     * @return bool
     */
    public static function isTruthy($value)
    {
        if (!$value) {
            return $value === 0 || $value === '0';
        } elseif ($value instanceof \stdClass) {
            return (bool) get_object_vars($value);
        } elseif ($value instanceof JmesPathableArrayInterface) {
            return Utils::isTruthy(iterator_to_array($value));
        } elseif ($value instanceof JmesPathableObjectInterface) {
            return (bool) $value->toArray();
        } else {
            return true;
        }
    }

    /**
     * Gets the JMESPath type equivalent of a PHP variable.
     *
     * @param mixed $arg PHP variable
     * @return string Returns the JSON data type
     * @throws \InvalidArgumentException when an unknown type is given.
     */
    public static function type($arg)
    {
        $type = gettype($arg);
        if (isset(self::$typeMap[$type])) {
            return self::$typeMap[$type];
        } elseif ($type === 'array') {
            if (empty($arg)) {
                return 'array';
            }
            reset($arg);
            return key($arg) === 0 ? 'array' : 'object';
        } elseif ($arg instanceof \stdClass) {
            return 'object';
        } elseif ($arg instanceof JmesPathableObjectInterface) {
            return 'object';
        } elseif ($arg instanceof \Closure) {
            return 'expression';
        } elseif ($arg instanceof \ArrayAccess
            && $arg instanceof \Countable
        ) {
            return count($arg) == 0 || $arg->offsetExists(0)
                ? 'array'
                : 'object';
        } elseif (method_exists($arg, '__toString')) {
            return 'string';
        }

        throw new \InvalidArgumentException(
            'Unable to determine JMESPath type from ' . get_class($arg)
        );
    }

    /**
     * Determine if the provided value is a JMESPath compatible object.
     *
     * @param mixed $value
     *
     * @return bool
     */
    public static function isObject($value)
    {
        if (is_array($value)) {
            return !$value || array_keys($value)[0] !== 0;
        }

        // Handle array-like values. Must be empty or offset 0 does not exist
        return $value instanceof \Countable && $value instanceof \ArrayAccess
            ? count($value) == 0 || !$value->offsetExists(0)
            : $value instanceof \stdClass || $value instanceof JmesPathableObjectInterface;
    }

    /**
     * Determine if the provided value is a JMESPath compatible array.
     *
     * @param mixed $value
     *
     * @return bool
     */
    public static function isArray($value)
    {
        if (is_array($value)) {
            return !$value || array_keys($value)[0] === 0;
        }

        // Handle array-like values. Must be empty or offset 0 exists.
        return $value instanceof \Countable && $value instanceof \ArrayAccess
            ? count($value) == 0 || $value->offsetExists(0)
            : $value instanceof JmesPathableArrayInterface;
    }

    public static function toArray($value)
    {
        if ($value instanceof JmesPathableArrayInterface) {
            return iterator_to_array($value);
        } elseif ($value instanceof JmesPathableObjectInterface) {
            return $value->toArray();
        } else {
            return $value;
        }
    }

    /**
     * JSON aware value comparison function.
     *
     * @param mixed $a First value to compare
     * @param mixed $b Second value to compare
     *
     * @return bool
     */
    public static function isEqual($a, $b)
    {
        if ($a === $b) {
            return true;
        } elseif ($a instanceof \stdClass) {
            return self::isEqual((array) $a, $b);
        } elseif ($b instanceof \stdClass) {
            return self::isEqual($a, (array) $b);
        } elseif ($a instanceof JmesPathableArrayInterface) {
            return Utils::isEqual(iterator_to_array($a), $b);
        } elseif ($b instanceof JmesPathableArrayInterface) {
            return Utils::isEqual($a, iterator_to_array($b));
        } elseif ($a instanceof JmesPathableObjectInterface) {
            return Utils::isEqual($a->toArray(), $b);
        } elseif ($b instanceof JmesPathableObjectInterface) {
            return Utils::isEqual($a, $b->toArray());
        } else {
            return false;
        }
    }

    /**
     * JMESPath requires a stable sorting algorithm, so here we'll implement
     * a simple Schwartzian transform that uses array index positions as tie
     * breakers.
     *
     * @param array    $data   List or map of data to sort
     * @param callable $sortFn Callable used to sort values
     *
     * @return array Returns the sorted array
     * @link http://en.wikipedia.org/wiki/Schwartzian_transform
     */
    public static function stableSort(array $data, callable $sortFn)
    {
        // Decorate each item by creating an array of [value, index]
        array_walk($data, function (&$v, $k) { $v = [$v, $k]; });
        // Sort by the sort function and use the index as a tie-breaker
        uasort($data, function ($a, $b) use ($sortFn) {
            return $sortFn($a[0], $b[0]) ?: ($a[1] < $b[1] ? -1 : 1);
        });

        // Undecorate each item and return the resulting sorted array
        return array_map(function ($v) { return $v[0]; }, array_values($data));
    }

    /**
     * Creates a Python-style slice of a string or array.
     *
     * @param array|string $value Value to slice
     * @param int|null     $start Starting position
     * @param int|null     $stop  Stop position
     * @param int          $step  Step (1, 2, -1, -2, etc.)
     *
     * @return array|string
     * @throws \InvalidArgumentException
     */
    public static function slice($value, $start = null, $stop = null, $step = 1)
    {
        if (!Utils::isArray($value) && !is_string($value)) {
            throw new \InvalidArgumentException('Expects string or array');
        }

        return self::sliceIndices($value, $start, $stop, $step);
    }

    private static function adjustEndpoint($length, $endpoint, $step)
    {
        if ($endpoint < 0) {
            $endpoint += $length;
            if ($endpoint < 0) {
                $endpoint = $step < 0 ? -1 : 0;
            }
        } elseif ($endpoint >= $length) {
            $endpoint = $step < 0 ? $length - 1 : $length;
        }

        return $endpoint;
    }

    private static function adjustSlice($length, $start, $stop, $step)
    {
        if ($step === null) {
            $step = 1;
        } elseif ($step === 0) {
            throw new \RuntimeException('step cannot be 0');
        }

        if ($start === null) {
            $start = $step < 0 ? $length - 1 : 0;
        } else {
            $start = self::adjustEndpoint($length, $start, $step);
        }

        if ($stop === null) {
            $stop = $step < 0 ? -1 : $length;
        } else {
            $stop = self::adjustEndpoint($length, $stop, $step);
        }

        return [$start, $stop, $step];
    }

    private static function sliceIndices($subject, $start, $stop, $step)
    {
        $type = gettype($subject);
        $len = $type == 'string' ? strlen($subject) : count($subject);
        list($start, $stop, $step) = self::adjustSlice($len, $start, $stop, $step);

        $result = [];
        if ($step > 0) {
            for ($i = $start; $i < $stop; $i += $step) {
                $result[] = $subject[$i];
            }
        } else {
            for ($i = $start; $i > $stop; $i += $step) {
                $result[] = $subject[$i];
            }
        }

        return $type == 'string' ? implode($result, '') : $result;
    }
}


<?php
namespace DTS\eBaySDK\FileTransfer\Services;

/**
 * Base class for the FileTransfer service.
 */
class FileTransferBaseService extends \DTS\eBaySDK\Services\BaseService
{
    /**
     * HTTP header constant. The API version your application supports.
     */
    const HDR_API_VERSION = 'X-EBAY-SOA-SERVICE-VERSION';

    /**
     * HTTP header constant. The Authentication Token that is used to validate the caller has permission to access the eBay servers.
     */
    const HDR_AUTH_TOKEN = 'X-EBAY-SOA-SECURITY-TOKEN';

    /**
     * HTTP header constant. The name of the operation you are calling.
     */
    const HDR_OPERATION_NAME = 'X-EBAY-SOA-OPERATION-NAME';

    /**
     * @param array $config Configuration option values.
     */
    public function __construct(array $config)
    {
        parent::__construct('https://storage.ebay.com/FileTransferService', 'https://storage.sandbox.ebay.com/FileTransferService', $config);
    }

    /**
     * Returns definitions for each configuration option that is supported.
     *
     * @return array An associative array of configuration definitions.
     */
    public static function getConfigDefinitions()
    {
        $definitions = parent::getConfigDefinitions();

        return $definitions + [
            'apiVersion' => [
                'valid' => ['string'],
                'default' => \DTS\eBaySDK\FileTransfer\Services\FileTransferService::API_VERSION
            ],
            'authToken' => [
                'valid' => ['string'],
                'required' => true
            ]
        ];
    }

    /**
     * Sends an API request.
     *
     * This method overrides the parent so that it can modify
     * the request object before it is handled by the parent class.
     *
     * @param string $name The name of the operation.
     * @param \DTS\eBaySDK\Types\BaseType $request Request object containing the request information.
     * @param string $responseClass The name of the PHP class that will be created from the XML response.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface A promise that will be resolved with an object created from the XML response.
     */
    protected function callOperationAsync($name, \DTS\eBaySDK\Types\BaseType $request, $responseClass)
    {
        /**
         * Modify the request object to add xop:Include element.
         */
        if ($name === 'uploadFile' && $request->hasAttachment()) {
            /**
             * Don't modify a request if the file attachment already exists.
             */
            if (!isset($request->fileAttachment)) {
                $request->fileAttachment = new \DTS\eBaySDK\FileTransfer\Types\FileAttachment();
            }

            if (!isset($request->fileAttachment->Data)) {
                $request->fileAttachment->Data = new \DTS\eBaySDK\FileTransfer\Types\Data([
                    'xopInclude' => new \DTS\eBaySDK\FileTransfer\Types\XopInclude([
                        'href' => 'cid:attachment.bin@devbay.net'
                    ])
                ]);
            }

            if (!isset($request->fileAttachment->Size)) {
                $attachment = $request->attachment();
                $request->fileAttachment->Size = strlen($attachment['data']);
            }
        }

        return parent::callOperationAsync($name, $request, $responseClass);
    }

    /**
     * Builds the needed eBay HTTP headers.
     *
     * @param string $operationName The name of the operation been called.
     *
     * @return array An associative array of eBay http headers.
     */
    protected function getEbayHeaders($operationName)
    {
        $headers = [];

        // Add required headers first.
        $headers[self::HDR_AUTH_TOKEN] = $this->getConfig('authToken');
        $headers[self::HDR_OPERATION_NAME] = $operationName;

        // Add optional headers.
        if ($this->getConfig('apiVersion')) {
            $headers[self::HDR_API_VERSION] = $this->getConfig('apiVersion');
        }

        return $headers;
    }
}


<?php
namespace DTS\eBaySDK\Marketing\Services;

/**
 * Base class for the Marketing service.
 */
class MarketingBaseService extends \DTS\eBaySDK\Services\BaseRestService
{
    /**
     * @var array $endPoints The API endpoints.
     */
    protected static $endPoints = [
        'sandbox'    => 'https://api.sandbox.ebay.com/sell/marketing',
        'production' => 'https://api.ebay.com/sell/marketing'
    ];

    /**
     * HTTP header constant. The Authentication Token that is used to validate the caller has permission to access the eBay servers.
     */
    const HDR_AUTHORIZATION = 'Authorization';

    /**
     * HTTP header constant. The global ID of the eBay site on which the transaction took place.
     */
    const HDR_MARKETPLACE_ID = 'X-EBAY-C-MARKETPLACE-ID';

    /**
     * @param array $config Configuration option values.
     */
    public function __construct(array $config)
    {
        parent::__construct($config);
    }

    /**
     * Returns definitions for each configuration option that is supported.
     *
     * @return array An associative array of configuration definitions.
     */
    public static function getConfigDefinitions()
    {
        $definitions = parent::getConfigDefinitions();

        return $definitions + [
            'apiVersion' => [
                'valid' => ['string'],
                'default' => \DTS\eBaySDK\Marketing\Services\MarketingService::API_VERSION,
                'required' => true
            ],
            'authorization' => [
                'valid' => ['string'],
                'required' => true
            ],
            'marketplaceId' => [
                'valid' => ['string']
            ]
        ];
    }

    /**
     * Builds the needed eBay HTTP headers.
     *
     * @return array An associative array of eBay HTTP headers.
     */
    protected function getEbayHeaders()
    {
        $headers = [];

        // Add required headers first.
        $headers[self::HDR_AUTHORIZATION] = 'Bearer '.$this->getConfig('authorization');

        // Add optional headers.
        if ($this->getConfig('marketplaceId')) {
            $headers[self::HDR_MARKETPLACE_ID] = $this->getConfig('marketplaceId');
        }

        return $headers;
    }
}


<?php
namespace DTS\eBaySDK\BulkDataExchange\Services;

/**
 * Base class for the BulkDataExchange service.
 */
class BulkDataExchangeBaseService extends \DTS\eBaySDK\Services\BaseService
{
    /**
     * HTTP header constant. The API version your application supports.
     */
    const HDR_API_VERSION = 'X-EBAY-SOA-SERVICE-VERSION';

    /**
     * HTTP header constant. The Authentication Token that is used to validate the caller has permission to access the eBay servers.
     */
    const HDR_AUTH_TOKEN = 'X-EBAY-SOA-SECURITY-TOKEN';

    /**
     * HTTP header constant. The name of the operation you are calling.
     */
    const HDR_OPERATION_NAME = 'X-EBAY-SOA-OPERATION-NAME';

    /**
     * @param array $config Configuration option values.
     */
    public function __construct(array $config)
    {
        parent::__construct('https://webservices.ebay.com/BulkDataExchangeService', 'https://webservices.sandbox.ebay.com/BulkDataExchangeService', $config);
    }

    /**
     * Returns definitions for each configuration option that is supported.
     *
     * @return array An associative array of configuration definitions.
     */
    public static function getConfigDefinitions()
    {
        $definitions = parent::getConfigDefinitions();

        return $definitions + [
            'apiVersion' => [
                'valid' => ['string'],
                'default' => \DTS\eBaySDK\BulkDataExchange\Services\BulkDataExchangeService::API_VERSION
            ],
            'authToken' => [
                'valid' => ['string'],
                'required' => true
            ]
        ];
    }

    /**
     * Builds the needed eBay HTTP headers.
     *
     * @param string $operationName The name of the operation been called.
     *
     * @return array An associative array of eBay http headers.
     */
    protected function getEbayHeaders($operationName)
    {
        $headers = [];

        // Add required headers first.
        $headers[self::HDR_AUTH_TOKEN] = $this->getConfig('authToken');
        $headers[self::HDR_OPERATION_NAME] = $operationName;

        // Add optional headers.
        if ($this->getConfig('apiVersion')) {
            $headers[self::HDR_API_VERSION] = $this->getConfig('apiVersion');
        }

        return $headers;
    }
}


<?php
namespace DTS\eBaySDK\Browse\Services;

/**
 * Base class for the Browse service.
 */
class BrowseBaseService extends \DTS\eBaySDK\Services\BaseRestService
{
    /**
     * @var array $endPoints The API endpoints.
     */
    protected static $endPoints = [
        'sandbox'    => 'https://api.sandbox.ebay.com/buy/browse',
        'production' => 'https://api.ebay.com/buy/browse'
    ];

    /**
     * HTTP header constant. The Authentication Token that is used to validate the caller has permission to access the eBay servers.
     */
    const HDR_AUTHORIZATION = 'Authorization';

    /**
     * HTTP header constant. The global ID of the eBay site on which the transaction took place.
     */
    const HDR_MARKETPLACE_ID = 'X-EBAY-C-MARKETPLACE-ID';

    /**
     * HTTP header constant.
     */
    const HDR_END_USER_CTX = 'X-EBAY-C-ENDUSERCTX';

    /**
     * @param array $config Configuration option values.
     */
    public function __construct(array $config)
    {
        parent::__construct($config);
    }

    /**
     * Returns definitions for each configuration option that is supported.
     *
     * @return array An associative array of configuration definitions.
     */
    public static function getConfigDefinitions()
    {
        $definitions = parent::getConfigDefinitions();

        return $definitions + [
            'affiliateCampaignId' => [
                'valid' => ['string']
            ],
            'affiliateReferenceId' => [
                'valid' => ['string']
            ],
            'apiVersion' => [
                'valid' => ['string'],
                'default' => \DTS\eBaySDK\Browse\Services\BrowseService::API_VERSION,
                'required' => true
            ],
            'authorization' => [
                'valid' => ['string'],
                'required' => true
            ],
            'contextualLocation' => [
                'valid' => ['string']
            ],
            'marketplaceId' => [
                'valid' => ['string']
            ]
        ];
    }

    /**
     * Builds the needed eBay HTTP headers.
     *
     * @return array An associative array of eBay HTTP headers.
     */
    protected function getEbayHeaders()
    {
        $headers = [];

        // Add required headers first.
        $headers[self::HDR_AUTHORIZATION] = 'Bearer '.$this->getConfig('authorization');

        // Add optional headers.
        if ($this->getConfig('marketplaceId')) {
            $headers[self::HDR_MARKETPLACE_ID] = $this->getConfig('marketplaceId');
        }

        $endUserCTX = [];
        if ($this->getConfig('affiliateCampaignId')) {
            $endUserCTX[ ] = 'affiliateCampaignId='.$this->getConfig('affiliateCampaignId');
        }
        if ($this->getConfig('affiliateReferenceId')) {
            $endUserCTX[ ] = 'affiliateReferenceId='.$this->getConfig('affiliateReferenceId');
        }
        if ($this->getConfig('contextualLocation')) {
            $endUserCTX[ ] = 'contextualLocation='.$this->getConfig('contextualLocation');
        }
        if (count($endUserCTX)) {
            $headers[self::HDR_END_USER_CTX ] = implode(',', $endUserCTX);
        }

        return $headers;
    }
}


<?php
namespace DTS\eBaySDK\Finding\Services;

/**
 * Base class for the Finding service.
 */
class FindingBaseService extends \DTS\eBaySDK\Services\BaseService
{
    /**
     * HTTP header constant. The API version your application supports.
     */
    const HDR_API_VERSION = 'X-EBAY-SOA-SERVICE-VERSION';

    /**
     * HTTP header constant. Your application ID.
     */
    const HDR_APP_ID = 'X-EBAY-SOA-SECURITY-APPNAME';

    /**
     * HTTP header constant. The global ID of the eBay site the request is for.
     */
    const HDR_GLOBAL_ID = 'X-EBAY-SOA-GLOBAL-ID';

    /**
     * HTTP header constant. The name of the operation you are calling.
     */
    const HDR_OPERATION_NAME = 'X-EBAY-SOA-OPERATION-NAME';

    /**
     * @param array $config Configuration option values.
     */
    public function __construct(array $config)
    {
        parent::__construct('https://svcs.ebay.com/services/search/FindingService/v1', 'https://svcs.sandbox.ebay.com/services/search/FindingService/v1', $config);
    }

    /**
     * Returns definitions for each configuration option that is supported.
     *
     * @return array An associative array of configuration definitions.
     */
    public static function getConfigDefinitions()
    {
        $definitions = parent::getConfigDefinitions();

        return $definitions + [
            'apiVersion' => [
                'valid' => ['string'],
                'default' => \DTS\eBaySDK\Finding\Services\FindingService::API_VERSION
            ],
            'globalId' => [
                'valid' => ['string']
            ]
        ];
    }

    /**
     * Builds the needed eBay HTTP headers.
     *
     * @param string $operationName The name of the operation been called.
     *
     * @return array An associative array of eBay http headers.
     */
    protected function getEbayHeaders($operationName)
    {
        $headers = [];

        // Add required headers first.
        $headers[self::HDR_APP_ID] = $this->getConfig('credentials')->getAppId();
        $headers[self::HDR_OPERATION_NAME] = $operationName;

        // Add optional headers.
        if ($this->getConfig('apiVersion')) {
            $headers[self::HDR_API_VERSION] = $this->getConfig('apiVersion');
        }

        if ($this->getConfig('globalId')) {
            $headers[self::HDR_GLOBAL_ID] = $this->getConfig('globalId');
        }

        return $headers;
    }
}


<?php
namespace DTS\eBaySDK\PostOrder\Services;

/**
 * Base class for the PostOrder service.
 */
class PostOrderBaseService extends \DTS\eBaySDK\Services\BaseRestService
{
    /**
     * @var array $endPoints The API endpoints.
     */
    protected static $endPoints = [
        'sandbox'    => 'https://api.sandbox.ebay.com/post-order',
        'production' => 'https://api.ebay.com/post-order'
    ];

    /**
     * HTTP header constant. The Authentication Token that is used to validate the caller has permission to access the eBay servers.
     */
    const HDR_AUTH_TOKEN = 'Authorization';

    /**
     * HTTP header constant. The global ID of the eBay site on which the transaction took place.
     */
    const HDR_MARKETPLACE_ID = 'X-EBAY-C-MARKETPLACE-ID';

    /**
     * @param array $config Configuration option values.
     */
    public function __construct(array $config)
    {
        parent::__construct($config);
    }

    /**
     * Returns definitions for each configuration option that is supported.
     *
     * @return array An associative array of configuration definitions.
     */
    public static function getConfigDefinitions()
    {
        $definitions = parent::getConfigDefinitions();

        return $definitions + [
            'apiVersion' => [
                'valid' => ['string'],
                'default' => \DTS\eBaySDK\PostOrder\Services\PostOrderService::API_VERSION,
                'required' => true
            ],
            'authToken' => [
                'valid' => ['string'],
                'required' => true
            ],
            'marketplaceId' => [
                'valid' => ['string']
            ]
        ];
    }

    /**
     * Builds the needed eBay HTTP headers.
     *
     * @return array An associative array of eBay HTTP headers.
     */
    protected function getEbayHeaders()
    {
        $headers = [];

        // Add required headers first.
        $headers[self::HDR_AUTH_TOKEN] = 'TOKEN '.$this->getConfig('authToken');

        // Add optional headers.
        if ($this->getConfig('marketplaceId')) {
            $headers[self::HDR_MARKETPLACE_ID] = $this->getConfig('marketplaceId');
        }

        return $headers;
    }
}


<?php
namespace DTS\eBaySDK\Metadata\Services;

/**
 * Base class for the Metadata service.
 */
class MetadataBaseService extends \DTS\eBaySDK\Services\BaseRestService
{
    /**
     * @var array $endPoints The API endpoints.
     */
    protected static $endPoints = [
        'sandbox'    => 'https://api.sandbox.ebay.com/sell/metadata',
        'production' => 'https://api.ebay.com/sell/metadata'
    ];

    /**
     * HTTP header constant. The Authentication Token that is used to validate the caller has permission to access the eBay servers.
     */
    const HDR_AUTHORIZATION = 'Authorization';

    /**
     * HTTP header constant. The global ID of the eBay site on which the transaction took place.
     */
    const HDR_MARKETPLACE_ID = 'X-EBAY-C-MARKETPLACE-ID';

    /**
     * @param array $config Configuration option values.
     */
    public function __construct(array $config)
    {
        parent::__construct($config);
    }

    /**
     * Returns definitions for each configuration option that is supported.
     *
     * @return array An associative array of configuration definitions.
     */
    public static function getConfigDefinitions()
    {
        $definitions = parent::getConfigDefinitions();

        return $definitions + [
            'apiVersion' => [
                'valid' => ['string'],
                'default' => \DTS\eBaySDK\Metadata\Services\MetadataService::API_VERSION,
                'required' => true
            ],
            'authorization' => [
                'valid' => ['string'],
                'required' => true
            ],
            'marketplaceId' => [
                'valid' => ['string']
            ]
        ];
    }

    /**
     * Builds the needed eBay HTTP headers.
     *
     * @return array An associative array of eBay HTTP headers.
     */
    protected function getEbayHeaders()
    {
        $headers = [];

        // Add required headers first.
        $headers[self::HDR_AUTHORIZATION] = 'Bearer '.$this->getConfig('authorization');

        // Add optional headers.
        if ($this->getConfig('marketplaceId')) {
            $headers[self::HDR_MARKETPLACE_ID] = $this->getConfig('marketplaceId');
        }

        return $headers;
    }
}


<?php
namespace DTS\eBaySDK\Services;

use DTS\eBaySDK\Parser\JsonParser;
use DTS\eBaySDK\ConfigurationResolver;
use DTS\eBaySDK\UriResolver;
use \DTS\eBaySDK as Functions;
use GuzzleHttp\Psr7\Request;
use Psr\Http\Message\ResponseInterface;

/**
 * The base class for every eBay REST service class.
 */
abstract class BaseRestService
{
    /**
     * HTTP header constant. Describes the natural language provided in the field values of the request payload.
     */
    const HDR_REQUEST_LANGUAGE = 'Content-Language';

    /**
     * HTTP header constant. Tells the server the natural language in which the client desires the response.
     */
    const HDR_RESPONSE_LANGUAGE = 'Accept-Language';

    /**
     * HTTP header constant. Tells the server the encoding in which the client desires the response.
     */
    const HDR_RESPONSE_ENCODING = 'Accept-Encoding';

    /**
     * @var \DTS\eBaySDK\ConfigurationResolver Resolves configuration options.
     */
    private $resolver;

    /**
     * @var \DTS\eBaySDK\UriResolver Resolves uri parameters.
     */
    private $uriResolver;

    /**
     * @var array Associative array storing the current configuration option values.
     */
    private $config;

    /**
     * @param array $config Configuration option values.
     */
    public function __construct(array $config)
    {
        $this->resolver = new ConfigurationResolver(static::getConfigDefinitions());
        $this->uriResolver = new UriResolver();
        $this->config = $this->resolver->resolve($config);
    }

    /**
     * Returns definitions for each configuration option that is supported.
     *
     * @return array An associative array of configuration definitions.
     */
    public static function getConfigDefinitions()
    {
        return [
            'compressResponse' => [
                'valid'   => ['bool'],
                'default' => false
            ],
            'debug' => [
                'valid'   => ['bool', 'array'],
                'fn'      => 'DTS\eBaySDK\applyDebug',
                'default' => false
            ],
            'httpHandler' => [
                'valid'   => ['callable'],
                'default' => 'DTS\eBaySDK\defaultHttpHandler'
            ],
            'httpOptions' => [
                'valid'   => ['array'],
                'default' => [
                    'http_errors' => false
                ]
            ],
            'requestLanguage' => [
                'valid' => ['string']
            ],
            'responseLanguage' => [
                'valid' => ['string']
            ],
            'sandbox' => [
                'valid'   => ['bool'],
                'default' => false
            ]
        ];
    }

    /**
     * Method to get the service's configuration.
     *
     * @param string|null $option The name of the option whos value will be returned.
     *
     * @return mixed Returns an associative array of configuration options if no parameters are passed,
     * otherwise returns the value for the specified configuration option.
     */
    public function getConfig($option = null)
    {
        return $option === null
            ? $this->config
            : (isset($this->config[$option])
                ? $this->config[$option]
                : null);
    }

    /**
     * Set multiple configuration options.
     *
     * @param array $configuration Associative array of configuration options and their values.
     */
    public function setConfig(array $configuration)
    {
        $this->config = Functions\arrayMergeDeep(
            $this->config,
            $this->resolver->resolveOptions($configuration)
        );
    }

    /**
     * Sends an asynchronous API request.
     *
     * @param string $name The name of the operation.
     * @param \DTS\eBaySDK\Types\BaseType $request Request object containing the request information.
     *
     * @return \GuzzleHttp\Promise\PromiseInterface A promise that will be resolved with an object created from the JSON response.
     */
    protected function callOperationAsync($name, \DTS\eBaySDK\Types\BaseType $request = null)
    {
        $operation = static::$operations[$name];

        $paramValues = [];
        $requestValues = [];

        if ($request) {
            $requestArray = $request->toArray();
            $paramValues = array_intersect_key($requestArray, $operation['params']);
            $requestValues = array_diff_key($requestArray, $operation['params']);
        }

        $url = $this->uriResolver->resolve(
            $this->getUrl(),
            $this->getConfig('apiVersion'),
            $operation['resource'],
            $operation['params'],
            $paramValues
        );
        $method = $operation['method'];
        $body = $this->buildRequestBody($requestValues);
        $headers = $this->buildRequestHeaders($body);
        $responseClass = $operation['responseClass'];
        $debug = $this->getConfig('debug');
        $httpHandler = $this->getConfig('httpHandler');
        $httpOptions = $this->getConfig('httpOptions');

        if ($debug !== false) {
            $this->debugRequest($url, $headers, $body);
        }

        $request = new Request($method, $url, $headers, $body);

        return $httpHandler($request, $httpOptions)->then(
            function (ResponseInterface $res) use ($debug, $responseClass) {
                $json = $res->getBody()->getContents();

                if ($debug !== false) {
                    $this->debugResponse($json);
                }

                $response =  new $responseClass(
                    [],
                    $res->getStatusCode(),
                    $res->getHeaders()
                );

                JsonParser::parseAndAssignProperties($response, $json);

                return $response;
            }
        );
    }

    /**
     * Helper function to return the URL as determined by the sandbox configuration option.
     *
     * @return string Either the production or sandbox URL.
     */
    private function getUrl()
    {
        return $this->getConfig('sandbox') ? static::$endPoints['sandbox'] : static::$endPoints['production'];
    }

    /**
     * Builds the request body string.
     *
     * @param array $request Associative array that is the request body.
     *
     * @return string The request body in JSON format.
     */
    private function buildRequestBody(array $request)
    {
        return empty($request) ? '' : json_encode($request);
    }

    /**
     * Helper function that builds the HTTP request headers.
     *
     * @param string $body The request body.
     *
     * @return array An associative array of HTTP headers.
     */
    private function buildRequestHeaders($body)
    {
        $headers = $this->getEbayHeaders();

        $headers['Accept'] = 'application/json';
        $headers['Content-Type'] = 'application/json';
        $headers['Content-Length'] = strlen($body);

        // Add optional headers.
        if ($this->getConfig('requestLanguage')) {
            $headers[self::HDR_REQUEST_LANGUAGE] = $this->getConfig('requestLanguage');
        }

        if ($this->getConfig('responseLanguage')) {
            $headers[self::HDR_RESPONSE_LANGUAGE] = $this->getConfig('responseLanguage');
        }

        if ($this->getConfig('compressResponse')) {
            $headers[self::HDR_RESPONSE_ENCODING] = 'application/gzip';
        }

        return $headers;
    }

    /**
     * Derived classes must implement this method that will build the needed eBay http headers.
     *
     * @return array An associative array of eBay http headers.
     */
    abstract protected function getEbayHeaders();

    /**
     * Sends a debug string of the request details.
     *
     * @param string $url API endpoint.
     * @param array $headers Associative array of HTTP headers.
     * @param string $body The JSON body of the request.
      */
    private function debugRequest($url, array $headers, $body)
    {
        $str = $url.PHP_EOL;

        $str .= array_reduce(array_keys($headers), function ($str, $key) use ($headers) {
            $str .= $key.': '.$headers[$key].PHP_EOL;
            return $str;
        }, '');

        $str .= $body;

        $this->debug($str);
    }

    /**
     * Sends a debug string of the response details.
     *
     * @param string $body The JSON body of the response.
      */
    private function debugResponse($body)
    {
        $this->debug($body);
    }

    /**
     * Sends a debug string via the attach debugger.
     *
     * @param string $str The debug information.
     */
    private function debug($str)
    {
        $debugger = $this->getConfig('debug');
        $debugger($str);
    }
}


<?php
/**
 * Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
namespace DTS\eBaySDK\JmesPath;

/**
 * Dispatches to named JMESPath functions using a single function that has the
 * following signature:
 *
 *     mixed $result = fn(string $function_name, array $args)
 */
class FnDispatcher
{
    /**
     * Gets a cached instance of the default function implementations.
     *
     * @return FnDispatcher
     */
    public static function getInstance()
    {
        static $instance = null;
        if (!$instance) {
            $instance = new self();
        }

        return $instance;
    }

    /**
     * @param string $fn   Function name.
     * @param array  $args Function arguments.
     *
     * @return mixed
     */
    public function __invoke($fn, array $args)
    {
        return $this->{'fn_' . $fn}($args);
    }

    private function fn_abs(array $args)
    {
        $this->validate('abs', $args, [['number']]);
        return abs($args[0]);
    }

    private function fn_avg(array $args)
    {
        $this->validate('avg', $args, [['array']]);
        $arg = Utils::toArray($args[0]);
        $sum = $this->reduce('avg:0', $arg, ['number'], function ($a, $b) {
            return $a + $b;
        });
        return $arg ? ($sum / count($arg)) : null;
    }

    private function fn_ceil(array $args)
    {
        $this->validate('ceil', $args, [['number']]);
        return ceil($args[0]);
    }

    private function fn_contains(array $args)
    {
        $this->validate('contains', $args, [['string', 'array'], ['any']]);
        $arg = Utils::toArray($args[0]);
        if (is_array($arg)) {
            return in_array($args[1], $arg);
        } elseif (is_string($args[1])) {
            return strpos($arg, $args[1]) !== false;
        } else {
            return null;
        }
    }

    private function fn_ends_with(array $args)
    {
        $this->validate('ends_with', $args, [['string'], ['string']]);
        list($search, $suffix) = $args;
        return $suffix === '' || substr($search, -strlen($suffix)) === $suffix;
    }

    private function fn_floor(array $args)
    {
        $this->validate('floor', $args, [['number']]);
        return floor($args[0]);
    }

    private function fn_not_null(array $args)
    {
        if (!$args) {
            throw new \RuntimeException(
                "not_null() expects 1 or more arguments, 0 were provided"
            );
        }

        return array_reduce($args, function ($carry, $item) {
            return $carry !== null ? $carry : $item;
        });
    }

    private function fn_join(array $args)
    {
        $this->validate('join', $args, [['string'], ['array']]);
        $fn = function ($a, $b, $i) use ($args) {
            return $i ? ($a . $args[0] . $b) : $b;
        };
        return $this->reduce('join:0', Utils::toArray($args[1]), ['string'], $fn);
    }

    private function fn_keys(array $args)
    {
        $this->validate('keys', $args, [['object']]);
        return array_keys((array) Utils::toArray($args[0]));
    }

    private function fn_length(array $args)
    {
        $this->validate('length', $args, [['string', 'array', 'object']]);
        $arg = Utils::toArray($args[0]);
        return is_string($arg) ? strlen($arg) : count((array) $arg);
    }

    private function fn_max(array $args)
    {
        $this->validate('max', $args, [['array']]);
        $fn = function ($a, $b) { return $a >= $b ? $a : $b; };
        return $this->reduce('max:0', Utils::toArray($args[0]), ['number', 'string'], $fn);
    }

    private function fn_max_by(array $args)
    {
        $this->validate('max_by', $args, [['array'], ['expression']]);
        $expr = $this->wrapExpression('max_by:1', $args[1], ['number', 'string']);
        $fn = function ($carry, $item, $index) use ($expr) {
            return $index
                ? ($expr($carry) >= $expr($item) ? $carry : $item)
                : $item;
        };
        return $this->reduce('max_by:1', Utils::toArray($args[0]), ['any'], $fn);
    }

    private function fn_min(array $args)
    {
        $this->validate('min', $args, [['array']]);
        $fn = function ($a, $b, $i) { return $i && $a <= $b ? $a : $b; };
        return $this->reduce('min:0', Utils::toArray($args[0]), ['number', 'string'], $fn);
    }

    private function fn_min_by(array $args)
    {
        $this->validate('min_by', $args, [['array'], ['expression']]);
        $expr = $this->wrapExpression('min_by:1', $args[1], ['number', 'string']);
        $i = -1;
        $fn = function ($a, $b) use ($expr, &$i) {
            return ++$i ? ($expr($a) <= $expr($b) ? $a : $b) : $b;
        };
        return $this->reduce('min_by:1', Utils::toArray($args[0]), ['any'], $fn);
    }

    private function fn_reverse(array $args)
    {
        $this->validate('reverse', $args, [['array', 'string']]);
        $arg = Utils::toArray($args[0]);
        if (is_array($arg)) {
            return array_reverse($arg);
        } elseif (is_string($arg)) {
            return strrev($arg);
        } else {
            throw new \RuntimeException('Cannot reverse provided argument');
        }
    }

    private function fn_sum(array $args)
    {
        $this->validate('sum', $args, [['array']]);
        $fn = function ($a, $b) { return $a + $b; };
        return $this->reduce('sum:0', Utils::toArray($args[0]), ['number'], $fn);
    }

    private function fn_sort(array $args)
    {
        $this->validate('sort', $args, [['array']]);
        $valid = ['string', 'number'];
        return Utils::stableSort(Utils::toArray($args[0]), function ($a, $b) use ($valid) {
            $this->validateSeq('sort:0', $valid, $a, $b);
            return strnatcmp($a, $b);
        });
    }

    private function fn_sort_by(array $args)
    {
        $this->validate('sort_by', $args, [['array'], ['expression']]);
        $expr = $args[1];
        $valid = ['string', 'number'];
        return Utils::stableSort(
            Utils::toArray($args[0]),
            function ($a, $b) use ($expr, $valid) {
                $va = $expr($a);
                $vb = $expr($b);
                $this->validateSeq('sort_by:0', $valid, $va, $vb);
                return strnatcmp($va, $vb);
            }
        );
    }

    private function fn_starts_with(array $args)
    {
        $this->validate('starts_with', $args, [['string'], ['string']]);
        list($search, $prefix) = $args;
        return $prefix === '' || strpos($search, $prefix) === 0;
    }

    private function fn_type(array $args)
    {
        $this->validateArity('type', count($args), 1);
        return Utils::type($args[0]);
    }

    private function fn_to_string(array $args)
    {
        $this->validateArity('to_string', count($args), 1);
        $v = $args[0];
        if (is_string($v)) {
            return $v;
        } elseif (is_object($v)
            && !($v instanceof \JsonSerializable)
            && method_exists($v, '__toString')
        ) {
            return (string) $v;
        } elseif (Utils::isArray($v)) {
            $v = Utils::toArray($v);
        }

        return json_encode($v);
    }

    private function fn_to_number(array $args)
    {
        $this->validateArity('to_number', count($args), 1);
        $value = $args[0];
        $type = Utils::type($value);
        if ($type == 'number') {
            return $value;
        } elseif ($type == 'string' && is_numeric($value)) {
            return strpos($value, '.') ? (float) $value : (int) $value;
        } else {
            return null;
        }
    }

    private function fn_values(array $args)
    {
        $this->validate('values', $args, [['array', 'object']]);
        return array_values((array) Utils::toArray($args[0]));
    }

    private function fn_merge(array $args)
    {
        if (!$args) {
            throw new \RuntimeException(
                "merge() expects 1 or more arguments, 0 were provided"
            );
        }

        return array_reduce($args, function ($carry, $arg) {
            $carry = array_replace($carry, Utils::toArray($arg));
            return $carry;
        }, []);
    }

    private function fn_to_array(array $args)
    {
        $this->validate('to_array', $args, [['any']]);

        return Utils::isArray($args[0]) ? Utils::toArray($args[0]) : [$args[0]];
    }

    private function fn_map(array $args)
    {
        $this->validate('map', $args, [['expression'], ['any']]);
        $result = [];
        foreach ($args[1] as $a) {
            $result[] = $args[0]($a);
        }
        return $result;
    }

    private function typeError($from, $msg)
    {
        if (strpos($from, ':')) {
            list($fn, $pos) = explode(':', $from);
            throw new \RuntimeException(
                sprintf('Argument %d of %s %s', $pos, $fn, $msg)
            );
        } else {
            throw new \RuntimeException(
                sprintf('Type error: %s %s', $from, $msg)
            );
        }
    }

    private function validateArity($from, $given, $expected)
    {
        if ($given != $expected) {
            $err = "%s() expects {$expected} arguments, {$given} were provided";
            throw new \RuntimeException(sprintf($err, $from));
        }
    }

    private function validate($from, $args, $types = [])
    {
        $this->validateArity($from, count($args), count($types));
        foreach ($args as $index => $value) {
            if (!isset($types[$index]) || !$types[$index]) {
                continue;
            }
            $this->validateType("{$from}:{$index}", $value, $types[$index]);
        }
    }

    private function validateType($from, $value, array $types)
    {
        if ($types[0] == 'any'
            || in_array(Utils::type($value), $types)
            || ($value === [] && in_array('object', $types))
        ) {
            return;
        }
        $msg = 'must be one of the following types: ' . implode(', ', $types)
            . '. ' . Utils::type($value) . ' found';
        $this->typeError($from, $msg);
    }

    /**
     * Validates value A and B, ensures they both are correctly typed, and of
     * the same type.
     *
     * @param string   $from   String of function:argument_position
     * @param array    $types  Array of valid value types.
     * @param mixed    $a      Value A
     * @param mixed    $b      Value B
     */
    private function validateSeq($from, array $types, $a, $b)
    {
        $ta = Utils::type($a);
        $tb = Utils::type($b);

        if ($ta !== $tb) {
            $msg = "encountered a type mismatch in sequence: {$ta}, {$tb}";
            $this->typeError($from, $msg);
        }

        $typeMatch = ($types && $types[0] == 'any') || in_array($ta, $types);
        if (!$typeMatch) {
            $msg = 'encountered a type error in sequence. The argument must be '
                . 'an array of ' . implode('|', $types) . ' types. '
                . "Found {$ta}, {$tb}.";
            $this->typeError($from, $msg);
        }
    }

    /**
     * Reduces and validates an array of values to a single value using a fn.
     *
     * @param string   $from   String of function:argument_position
     * @param array    $values Values to reduce.
     * @param array    $types  Array of valid value types.
     * @param callable $reduce Reduce function that accepts ($carry, $item).
     *
     * @return mixed
     */
    private function reduce($from, array $values, array $types, callable $reduce)
    {
        $i = -1;
        return array_reduce(
            $values,
            function ($carry, $item) use ($from, $types, $reduce, &$i) {
                if (++$i > 0) {
                    $this->validateSeq($from, $types, $carry, $item);
                }
                return $reduce($carry, $item, $i);
            }
        );
    }

    /**
     * Validates the return values of expressions as they are applied.
     *
     * @param string   $from  Function name : position
     * @param callable $expr  Expression function to validate.
     * @param array    $types Array of acceptable return type values.
     *
     * @return callable Returns a wrapped function
     */
    private function wrapExpression($from, callable $expr, array $types)
    {
        list($fn, $pos) = explode(':', $from);
        $from = "The expression return value of argument {$pos} of {$fn}";
        return function ($value) use ($from, $expr, $types) {
            $value = $expr($value);
            $this->validateType($from, $value, $types);
            return $value;
        };
    }

    /** @internal Pass function name validation off to runtime
     * @param $name
     * @param $args
     */
    public function __call($name, $args)
    {
        $name = str_replace('fn_', '', $name);
        throw new \RuntimeException("Call to undefined function {$name}");
    }
}


<?php
namespace DTS\eBaySDK\Taxonomy\Services;

/**
 * Base class for the Taxonomy service.
 */
class TaxonomyBaseService extends \DTS\eBaySDK\Services\BaseRestService
{
    /**
     * @var array $endPoints The API endpoints.
     */
    protected static $endPoints = [
        'sandbox'    => 'https://api.sandbox.ebay.com/commerce/taxonomy',
        'production' => 'https://api.ebay.com/commerce/taxonomy'
    ];

    /**
     * HTTP header constant. The Authentication Token that is used to validate the caller has permission to access the eBay servers.
     */
    const HDR_AUTHORIZATION = 'Authorization';

    /**
     * HTTP header constant. The global ID of the eBay site on which the transaction took place.
     */
    const HDR_MARKETPLACE_ID = 'X-EBAY-C-MARKETPLACE-ID';

    /**
     * @param array $config Configuration option values.
     */
    public function __construct(array $config)
    {
        parent::__construct($config);
    }

    /**
     * Returns definitions for each configuration option that is supported.
     *
     * @return array An associative array of configuration definitions.
     */
    public static function getConfigDefinitions()
    {
        $definitions = parent::getConfigDefinitions();

        return $definitions + [
            'apiVersion' => [
                'valid' => ['string'],
                'default' => \DTS\eBaySDK\Taxonomy\Services\TaxonomyService::API_VERSION,
                'required' => true
            ],
            'authorization' => [
                'valid' => ['string'],
                'required' => true
            ],
            'marketplaceId' => [
                'valid' => ['string']
            ]
        ];
    }

    /**
     * Builds the needed eBay HTTP headers.
     *
     * @return array An associative array of eBay HTTP headers.
     */
    protected function getEbayHeaders()
    {
        $headers = [];

        // Add required headers first.
        $headers[self::HDR_AUTHORIZATION] = 'Bearer '.$this->getConfig('authorization');

        // Add optional headers.
        if ($this->getConfig('marketplaceId')) {
            $headers[self::HDR_MARKETPLACE_ID] = $this->getConfig('marketplaceId');
        }

        return $headers;
    }
}


<?php
namespace DTS\eBaySDK\Account\Services;

/**
 * Base class for the Account service.
 */
class AccountBaseService extends \DTS\eBaySDK\Services\BaseRestService
{
    /**
     * @var array $endPoints The API endpoints.
     */
    protected static $endPoints = [
        'sandbox'    => 'https://api.sandbox.ebay.com/sell/account',
        'production' => 'https://api.ebay.com/sell/account'
    ];

    /**
     * HTTP header constant. The Authentication Token that is used to validate the caller has permission to access the eBay servers.
     */
    const HDR_AUTHORIZATION = 'Authorization';

    /**
     * HTTP header constant. The global ID of the eBay site on which the transaction took place.
     */
    const HDR_MARKETPLACE_ID = 'X-EBAY-C-MARKETPLACE-ID';

    /**
     * @param array $config Configuration option values.
     */
    public function __construct(array $config)
    {
        parent::__construct($config);
    }

    /**
     * Returns definitions for each configuration option that is supported.
     *
     * @return array An associative array of configuration definitions.
     */
    public static function getConfigDefinitions()
    {
        $definitions = parent::getConfigDefinitions();

        return $definitions + [
            'apiVersion' => [
                'valid' => ['string'],
                'default' => \DTS\eBaySDK\Account\Services\AccountService::API_VERSION,
                'required' => true
            ],
            'authorization' => [
                'valid' => ['string'],
                'required' => true
            ],
            'marketplaceId' => [
                'valid' => ['string']
            ]
        ];
    }

    /**
     * Builds the needed eBay HTTP headers.
     *
     * @return array An associative array of eBay HTTP headers.
     */
    protected function getEbayHeaders()
    {
        $headers = [];

        // Add required headers first.
        $headers[self::HDR_AUTHORIZATION] = 'Bearer '.$this->getConfig('authorization');

        // Add optional headers.
        if ($this->getConfig('marketplaceId')) {
            $headers[self::HDR_MARKETPLACE_ID] = $this->getConfig('marketplaceId');
        }

        return $headers;
    }
}


<?php
namespace DTS\eBaySDK;

/**
 * @internal Resolves a resource uri.
 */
class UriResolver
{
    /**
     * @var array Map of type to function that confirms type.
     */
    private static $typeMap = [
        'array' => 'is_array',
        'bool' => 'is_bool',
        'callable' => 'is_callable',
        'int' => 'is_int',
        'string' => 'is_string'
    ];

    public function __construct()
    {
    }

    /**
     * Resolve and validate the passed uri.
     *
     * @param string $uri Uri to resolve.
     * @param string $version API version.
     * @param string $resource The name of the resource.
     * @param array $paramDefs Associative array of uri parameter definitions for the operation.
     * @param array $paramValues Associative array of uri parameter values for the operation.
     *
     * @return string Returns a resolved uri.
     * @throws \InvalidArgumentException.
     */
    public function resolve($uri, $version, $resource, array $paramDefs, array $paramValues)
    {
        foreach ($paramValues as $param => $value) {
            if (!array_key_exists($param, $paramDefs)) {
                throw new \InvalidArgumentException("Unknown uri parameter \"$param\" provided");
            }
        }

        foreach ($paramDefs as $key => $def) {
            if (!isset($paramValues[$key])) {
                if (isset($def['default'])) {
                    $paramValues[$key] = is_callable($def['default'])
                        ? $def['default']($paramValues)
                        : $def['default'];
                } elseif (empty($def['required'])) {
                    continue;
                } else {
                    $this->throwRequired($paramDefs, $paramValues);
                }
            }

            $this->checkType($def['valid'], $key, $paramValues[$key]);

            if (isset($def['fn'])) {
                $def['fn']($paramValues[$key], $paramValues);
            }
        }

        return (
            "$uri/".
            "$version/".
            $this->fillPathParams($resource, $paramValues).
            $this->buildQueryParameters($paramValues)
        );
    }

    private function checkType(array $valid, $name, $provided)
    {
        foreach ($valid as $check) {
            if (isset(self::$typeMap[$check])) {
                $fn = self::$typeMap[$check];
                if ($fn($provided)) {
                    return;
                }
            } elseif ($provided instanceof $check) {
                return;
            }
        }

        $expected = implode('|', $valid);
        $msg = sprintf(
            'Invalid uri parameter value provided for "%s". Expected %s, but got %s',
            $name,
            $expected,
            describeType($provided)
        );
        throw new \InvalidArgumentException($msg);
    }

    private function throwRequired(array $paramDefs, array $paramValues)
    {
        $missing = [];

        foreach ($paramDefs as $key => $def) {
            if (empty($def['required'])
                || isset($def['default'])
                || array_key_exists($key, $paramValues)
            ) {
                continue;
            }
            $missing[] = $key;
        }

        $msg = "Missing required uri parameters: \n\n";
        $msg .= implode("\n\n", $missing);
        throw new \InvalidArgumentException($msg);
    }

    /**
     * Replaces path parameters placeholders in the uri with the correct values.
     *
     * @param string $uri A uri with path parameters.
     * @param array $paramValues Associative array of path parameter names and their values.
     *
     * @return string The path.
     */
    private function fillPathParams($uri, array &$paramValues)
    {
        return preg_replace_callback('/{(\S+)}/U', function ($matches) use (&$paramValues) {
            $path = $matches[1];
            if (array_key_exists($path, $paramValues)) {
                $value = $paramValues[$path];
                unset($paramValues[$path]);
            } else {
                $value = $path;
            }
            return $value;
        }, $uri);
    }

    /**
     * Builds a query parameters string.
     *
     * @param array $paramValues Associative array of query parameter names and their values.
     *
     * @return string The query parameters string
     */
    private function buildQueryParameters(array $paramValues)
    {
        if (empty($paramValues)) {
            return '';
        }

        $query = [];
        foreach ($paramValues as $param => $value) {
            if (is_array($value)) {
                $value = join(',', $value);
            } elseif (is_bool($value)) {
                $value = $value ? 'true' : 'false';
            } elseif (is_callable($value)) {
                $value = $value();
            }
            $query[] = $param.'='.urlencode($value);
        }
        return '?'.join('&', $query);
    }
}


<?php
namespace DTS\eBaySDK\Order\Services;

/**
 * Base class for the Order service.
 */
class OrderBaseService extends \DTS\eBaySDK\Services\BaseRestService
{
    /**
     * @var array $endPoints The API endpoints.
     */
    protected static $endPoints = [
        'sandbox'    => 'https://api.sandbox.ebay.com/buy/order',
        'production' => 'https://apix.ebay.com/buy/order'
    ];

    /**
     * HTTP header constant. The Authentication Token that is used to validate the caller has permission to access the eBay servers.
     */
    const HDR_AUTHORIZATION = 'Authorization';

    /**
     * HTTP header constant. The global ID of the eBay site on which the transaction took place.
     */
    const HDR_MARKETPLACE_ID = 'X-EBAY-C-MARKETPLACE-ID';

    /**
     * HTTP header constant. This header specifies the risk correlation ID, which is provided by a security software library.
     */
    const HDR_END_USER_CTX = 'X-EBAY-C-ENDUSERCTX';

    /**
     * @param array $config Configuration option values.
     */
    public function __construct(array $config)
    {
        parent::__construct($config);
    }

    /**
     * Returns definitions for each configuration option that is supported.
     *
     * @return array An associative array of configuration definitions.
     */
    public static function getConfigDefinitions()
    {
        $definitions = parent::getConfigDefinitions();

        return $definitions + [
            'apiVersion' => [
                'valid' => ['string'],
                'default' => \DTS\eBaySDK\Order\Services\OrderService::API_VERSION,
                'required' => true
            ],
            'authorization' => [
                'valid' => ['string'],
                'required' => true
            ],
            'marketplaceId' => [
                'valid' => ['string']
            ],
            'riskCorrelationId' => [
                'valid' => ['string']
            ]
        ];
    }

    /**
     * Builds the needed eBay HTTP headers.
     *
     * @return array An associative array of eBay HTTP headers.
     */
    protected function getEbayHeaders()
    {
        $headers = [];

        // Add required headers first.
        $headers[self::HDR_AUTHORIZATION] = 'Bearer '.$this->getConfig('authorization');

        // Add optional headers.
        if ($this->getConfig('marketplaceId')) {
            $headers[self::HDR_MARKETPLACE_ID] = $this->getConfig('marketplaceId');
        }

        if ($this->getConfig('riskCorrelationId')) {
            $headers[self::HDR_END_USER_CTX ] = 'deviceId='.$this->getConfig('riskCorrelationId');
        }

        return $headers;
    }
}


<?php
namespace DTS\eBaySDK\ProductMetadata\Services;

/**
 * Base class for the ProductMetadata service.
 */
class ProductMetadataBaseService extends \DTS\eBaySDK\Services\BaseService
{
    /**
     * HTTP header constant. The API version your application supports.
     */
    const HDR_API_VERSION = 'X-EBAY-SOA-SERVICE-VERSION';

    /**
     * HTTP header constant. Your application ID.
     */
    const HDR_APP_ID = 'X-EBAY-SOA-SECURITY-APPNAME';

    /**
     * HTTP header constant. The global ID of the eBay site the request is for.
     */
    const HDR_GLOBAL_ID = 'X-EBAY-SOA-GLOBAL-ID';

    /**
     * HTTP header constant. The name of the operation you are calling.
     */
    const HDR_OPERATION_NAME = 'X-EBAY-SOA-OPERATION-NAME';

    /**
     * @param array $config Configuration option values.
     */
    public function __construct(array $config)
    {
        parent::__construct('https://svcs.ebay.com/services/marketplacecatalog/ProductMetadataService/v1', 'https://svcs.sandbox.ebay.com/services/marketplacecatalog/ProductMetadataService/v1', $config);
    }

    /**
     * Returns definitions for each configuration option that is supported.
     *
     * @return array An associative array of configuration definitions.
     */
    public static function getConfigDefinitions()
    {
        $definitions = parent::getConfigDefinitions();

        return $definitions + [
            'apiVersion' => [
                'valid' => ['string'],
                'default' => \DTS\eBaySDK\ProductMetadata\Services\ProductMetadataService::API_VERSION
            ],
            'globalId' => [
                'valid' => ['string']
            ]
        ];
    }

    /**
     * Builds the needed eBay HTTP headers.
     *
     * @param string $operationName The name of the operation been called.
     *
     * @return array An associative array of eBay http headers.
     */
    protected function getEbayHeaders($operationName)
    {
        $headers = [];

        // Add required headers first.
        $headers[self::HDR_APP_ID] = $this->getConfig('credentials')->getAppId();
        $headers[self::HDR_OPERATION_NAME] = $operationName;

        // Add optional headers.
        if ($this->getConfig('apiVersion')) {
            $headers[self::HDR_API_VERSION] = $this->getConfig('apiVersion');
        }

        if ($this->getConfig('globalId')) {
            $headers[self::HDR_GLOBAL_ID] = $this->getConfig('globalId');
        }

        return $headers;
    }
}


<?php
namespace DTS\eBaySDK\Feed\Services;

/**
 * Base class for the Feed service.
 */
class FeedBaseService extends \DTS\eBaySDK\Services\BaseRestService
{
    /**
     * @var array $endPoints The API endpoints.
     */
    protected static $endPoints = [
        'sandbox'    => 'https://api.sandbox.ebay.com/buy/feed',
        'production' => 'https://api.ebay.com/buy/feed'
    ];

    /**
     * HTTP header constant. The Authentication Token that is used to validate the caller has permission to access the eBay servers.
     */
    const HDR_AUTHORIZATION = 'Authorization';

    /**
     * HTTP header constant. The global ID of the eBay site on which the transaction took place.
     */
    const HDR_MARKETPLACE_ID = 'X-EBAY-C-MARKETPLACE-ID';

    /**
     * @param array $config Configuration option values.
     */
    public function __construct(array $config)
    {
        parent::__construct($config);
    }

    /**
     * Returns definitions for each configuration option that is supported.
     *
     * @return array An associative array of configuration definitions.
     */
    public static function getConfigDefinitions()
    {
        $definitions = parent::getConfigDefinitions();

        return $definitions + [
            'apiVersion' => [
                'valid' => ['string'],
                'default' => \DTS\eBaySDK\Feed\Services\FeedService::API_VERSION,
                'required' => true
            ],
            'authorization' => [
                'valid' => ['string'],
                'required' => true
            ],
            'marketplaceId' => [
                'valid' => ['string']
            ]
        ];
    }

    /**
     * Builds the needed eBay HTTP headers.
     *
     * @return array An associative array of eBay HTTP headers.
     */
    protected function getEbayHeaders()
    {
        $headers = [];

        // Add required headers first.
        $headers[self::HDR_AUTHORIZATION] = 'Bearer '.$this->getConfig('authorization');

        // Add optional headers.
        if ($this->getConfig('marketplaceId')) {
            $headers[self::HDR_MARKETPLACE_ID] = $this->getConfig('marketplaceId');
        }

        return $headers;
    }
}


<?php
namespace DTS\eBaySDK\BusinessPoliciesManagement\Services;

/**
 * Base class for the BusinessPoliciesManagement service.
 */
class BusinessPoliciesManagementBaseService extends \DTS\eBaySDK\Services\BaseService
{
    /**
     * HTTP header constant. The API version your application supports.
     */
    const HDR_API_VERSION = 'X-EBAY-SOA-SERVICE-VERSION';

    /**
     * HTTP header constant. The Authentication Token that is used to validate the caller has permission to access the eBay servers.
     */
    const HDR_AUTH_TOKEN = 'X-EBAY-SOA-SECURITY-TOKEN';

    /**
     * HTTP header constant. The global ID of the eBay site the request is for.
     */
    const HDR_GLOBAL_ID = 'X-EBAY-SOA-GLOBAL-ID';

    /**
     * HTTP header constant. The name of the operation you are calling.
     */
    const HDR_OPERATION_NAME = 'X-EBAY-SOA-OPERATION-NAME';

    /**
     * @param array $config Configuration option values.
     */
    public function __construct(array $config)
    {
        parent::__construct('https://svcs.ebay.com/services/selling/v1/SellerProfilesManagementService', 'https://svcs.sandbox.ebay.com/services/selling/v1/SellerProfilesManagementService', $config);
    }

    /**
     * Returns definitions for each configuration option that is supported.
     *
     * @return array An associative array of configuration definitions.
     */
    public static function getConfigDefinitions()
    {
        $definitions = parent::getConfigDefinitions();

        return $definitions + [
            'apiVersion' => [
                'valid' => ['string'],
                'default' => \DTS\eBaySDK\BusinessPoliciesManagement\Services\BusinessPoliciesManagementService::API_VERSION
            ],
            'authToken' => [
                'valid' => ['string'],
                'required' => true
            ],
            'globalId' => [
                'valid' => ['string'],
                'required' => true
            ]
        ];
    }

    /**
     * Builds the needed eBay HTTP headers.
     *
     * @param string $operationName The name of the operation been called.
     *
     * @return array An associative array of eBay http headers.
     */
    protected function getEbayHeaders($operationName)
    {
        $headers = [];

        // Add required headers first.
        $headers[self::HDR_AUTH_TOKEN] = $this->getConfig('authToken');
        $headers[self::HDR_GLOBAL_ID] = $this->getConfig('globalId');
        $headers[self::HDR_OPERATION_NAME] = $operationName;

        // Add optional headers.
        if ($this->getConfig('apiVersion')) {
            $headers[self::HDR_API_VERSION] = $this->getConfig('apiVersion');
        }

        return $headers;
    }
}


<?php
/**
 * Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
namespace DTS\eBaySDK\JmesPath;

/**
 * Tokenizes JMESPath expressions
 */
class Lexer
{
    const T_DOT = 'dot';
    const T_STAR = 'star';
    const T_COMMA = 'comma';
    const T_COLON = 'colon';
    const T_CURRENT = 'current';
    const T_EXPREF = 'expref';
    const T_LPAREN = 'lparen';
    const T_RPAREN = 'rparen';
    const T_LBRACE = 'lbrace';
    const T_RBRACE = 'rbrace';
    const T_LBRACKET = 'lbracket';
    const T_RBRACKET = 'rbracket';
    const T_FLATTEN = 'flatten';
    const T_IDENTIFIER = 'identifier';
    const T_NUMBER = 'number';
    const T_QUOTED_IDENTIFIER = 'quoted_identifier';
    const T_UNKNOWN = 'unknown';
    const T_PIPE = 'pipe';
    const T_OR = 'or';
    const T_AND = 'and';
    const T_NOT = 'not';
    const T_FILTER = 'filter';
    const T_LITERAL = 'literal';
    const T_EOF = 'eof';
    const T_COMPARATOR = 'comparator';

    const STATE_IDENTIFIER = 0;
    const STATE_NUMBER = 1;
    const STATE_SINGLE_CHAR = 2;
    const STATE_WHITESPACE = 3;
    const STATE_STRING_LITERAL = 4;
    const STATE_QUOTED_STRING = 5;
    const STATE_JSON_LITERAL = 6;
    const STATE_LBRACKET = 7;
    const STATE_PIPE = 8;
    const STATE_LT = 9;
    const STATE_GT = 10;
    const STATE_EQ = 11;
    const STATE_NOT = 12;
    const STATE_AND = 13;

    /** @var array We know what token we are consuming based on each char */
    private static $transitionTable = [
        '<'  => self::STATE_LT,
        '>'  => self::STATE_GT,
        '='  => self::STATE_EQ,
        '!'  => self::STATE_NOT,
        '['  => self::STATE_LBRACKET,
        '|'  => self::STATE_PIPE,
        '&'  => self::STATE_AND,
        '`'  => self::STATE_JSON_LITERAL,
        '"'  => self::STATE_QUOTED_STRING,
        "'"  => self::STATE_STRING_LITERAL,
        '-'  => self::STATE_NUMBER,
        '0'  => self::STATE_NUMBER,
        '1'  => self::STATE_NUMBER,
        '2'  => self::STATE_NUMBER,
        '3'  => self::STATE_NUMBER,
        '4'  => self::STATE_NUMBER,
        '5'  => self::STATE_NUMBER,
        '6'  => self::STATE_NUMBER,
        '7'  => self::STATE_NUMBER,
        '8'  => self::STATE_NUMBER,
        '9'  => self::STATE_NUMBER,
        ' '  => self::STATE_WHITESPACE,
        "\t" => self::STATE_WHITESPACE,
        "\n" => self::STATE_WHITESPACE,
        "\r" => self::STATE_WHITESPACE,
        '.'  => self::STATE_SINGLE_CHAR,
        '*'  => self::STATE_SINGLE_CHAR,
        ']'  => self::STATE_SINGLE_CHAR,
        ','  => self::STATE_SINGLE_CHAR,
        ':'  => self::STATE_SINGLE_CHAR,
        '@'  => self::STATE_SINGLE_CHAR,
        '('  => self::STATE_SINGLE_CHAR,
        ')'  => self::STATE_SINGLE_CHAR,
        '{'  => self::STATE_SINGLE_CHAR,
        '}'  => self::STATE_SINGLE_CHAR,
        '_'  => self::STATE_IDENTIFIER,
        'A'  => self::STATE_IDENTIFIER,
        'B'  => self::STATE_IDENTIFIER,
        'C'  => self::STATE_IDENTIFIER,
        'D'  => self::STATE_IDENTIFIER,
        'E'  => self::STATE_IDENTIFIER,
        'F'  => self::STATE_IDENTIFIER,
        'G'  => self::STATE_IDENTIFIER,
        'H'  => self::STATE_IDENTIFIER,
        'I'  => self::STATE_IDENTIFIER,
        'J'  => self::STATE_IDENTIFIER,
        'K'  => self::STATE_IDENTIFIER,
        'L'  => self::STATE_IDENTIFIER,
        'M'  => self::STATE_IDENTIFIER,
        'N'  => self::STATE_IDENTIFIER,
        'O'  => self::STATE_IDENTIFIER,
        'P'  => self::STATE_IDENTIFIER,
        'Q'  => self::STATE_IDENTIFIER,
        'R'  => self::STATE_IDENTIFIER,
        'S'  => self::STATE_IDENTIFIER,
        'T'  => self::STATE_IDENTIFIER,
        'U'  => self::STATE_IDENTIFIER,
        'V'  => self::STATE_IDENTIFIER,
        'W'  => self::STATE_IDENTIFIER,
        'X'  => self::STATE_IDENTIFIER,
        'Y'  => self::STATE_IDENTIFIER,
        'Z'  => self::STATE_IDENTIFIER,
        'a'  => self::STATE_IDENTIFIER,
        'b'  => self::STATE_IDENTIFIER,
        'c'  => self::STATE_IDENTIFIER,
        'd'  => self::STATE_IDENTIFIER,
        'e'  => self::STATE_IDENTIFIER,
        'f'  => self::STATE_IDENTIFIER,
        'g'  => self::STATE_IDENTIFIER,
        'h'  => self::STATE_IDENTIFIER,
        'i'  => self::STATE_IDENTIFIER,
        'j'  => self::STATE_IDENTIFIER,
        'k'  => self::STATE_IDENTIFIER,
        'l'  => self::STATE_IDENTIFIER,
        'm'  => self::STATE_IDENTIFIER,
        'n'  => self::STATE_IDENTIFIER,
        'o'  => self::STATE_IDENTIFIER,
        'p'  => self::STATE_IDENTIFIER,
        'q'  => self::STATE_IDENTIFIER,
        'r'  => self::STATE_IDENTIFIER,
        's'  => self::STATE_IDENTIFIER,
        't'  => self::STATE_IDENTIFIER,
        'u'  => self::STATE_IDENTIFIER,
        'v'  => self::STATE_IDENTIFIER,
        'w'  => self::STATE_IDENTIFIER,
        'x'  => self::STATE_IDENTIFIER,
        'y'  => self::STATE_IDENTIFIER,
        'z'  => self::STATE_IDENTIFIER,
    ];

    /** @var array Valid identifier characters after first character */
    private $validIdentifier = [
        'A' => true, 'B' => true, 'C' => true, 'D' => true, 'E' => true,
        'F' => true, 'G' => true, 'H' => true, 'I' => true, 'J' => true,
        'K' => true, 'L' => true, 'M' => true, 'N' => true, 'O' => true,
        'P' => true, 'Q' => true, 'R' => true, 'S' => true, 'T' => true,
        'U' => true, 'V' => true, 'W' => true, 'X' => true, 'Y' => true,
        'Z' => true, 'a' => true, 'b' => true, 'c' => true, 'd' => true,
        'e' => true, 'f' => true, 'g' => true, 'h' => true, 'i' => true,
        'j' => true, 'k' => true, 'l' => true, 'm' => true, 'n' => true,
        'o' => true, 'p' => true, 'q' => true, 'r' => true, 's' => true,
        't' => true, 'u' => true, 'v' => true, 'w' => true, 'x' => true,
        'y' => true, 'z' => true, '_' => true, '0' => true, '1' => true,
        '2' => true, '3' => true, '4' => true, '5' => true, '6' => true,
        '7' => true, '8' => true, '9' => true,
    ];

    /** @var array Valid number characters after the first character */
    private $numbers = [
        '0' => true, '1' => true, '2' => true, '3' => true, '4' => true,
        '5' => true, '6' => true, '7' => true, '8' => true, '9' => true
    ];

    /** @var array Map of simple single character tokens */
    private $simpleTokens = [
        '.' => self::T_DOT,
        '*' => self::T_STAR,
        ']' => self::T_RBRACKET,
        ',' => self::T_COMMA,
        ':' => self::T_COLON,
        '@' => self::T_CURRENT,
        '(' => self::T_LPAREN,
        ')' => self::T_RPAREN,
        '{' => self::T_LBRACE,
        '}' => self::T_RBRACE,
    ];

    /**
     * Tokenize the JMESPath expression into an array of tokens hashes that
     * contain a 'type', 'value', and 'key'.
     *
     * @param string $input JMESPath input
     *
     * @return array
     * @throws SyntaxErrorException
     */
    public function tokenize($input)
    {
        $tokens = [];

        if ($input === '') {
            goto eof;
        }

        $chars = str_split($input);

        while (false !== ($current = current($chars))) {

            // Every character must be in the transition character table.
            if (!isset(self::$transitionTable[$current])) {
                $tokens[] = [
                    'type'  => self::T_UNKNOWN,
                    'pos'   => key($chars),
                    'value' => $current
                ];
                next($chars);
                continue;
            }

            $state = self::$transitionTable[$current];

            if ($state === self::STATE_SINGLE_CHAR) {

                // Consume simple tokens like ".", ",", "@", etc.
                $tokens[] = [
                    'type'  => $this->simpleTokens[$current],
                    'pos'   => key($chars),
                    'value' => $current
                ];
                next($chars);

            } elseif ($state === self::STATE_IDENTIFIER) {

                // Consume identifiers
                $start = key($chars);
                $buffer = '';
                do {
                    $buffer .= $current;
                    $current = next($chars);
                } while ($current !== false && isset($this->validIdentifier[$current]));
                $tokens[] = [
                    'type'  => self::T_IDENTIFIER,
                    'value' => $buffer,
                    'pos'   => $start
                ];

            } elseif ($state === self::STATE_WHITESPACE) {

                // Skip whitespace
                next($chars);

            } elseif ($state === self::STATE_LBRACKET) {

                // Consume "[", "[?", and "[]"
                $position = key($chars);
                $actual = next($chars);
                if ($actual === ']') {
                    next($chars);
                    $tokens[] = [
                        'type'  => self::T_FLATTEN,
                        'pos'   => $position,
                        'value' => '[]'
                    ];
                } elseif ($actual === '?') {
                    next($chars);
                    $tokens[] = [
                        'type'  => self::T_FILTER,
                        'pos'   => $position,
                        'value' => '[?'
                    ];
                } else {
                    $tokens[] = [
                        'type'  => self::T_LBRACKET,
                        'pos'   => $position,
                        'value' => '['
                    ];
                }

            } elseif ($state === self::STATE_STRING_LITERAL) {

                // Consume raw string literals
                $t = $this->inside($chars, "'", self::T_LITERAL);
                $t['value'] = str_replace("\\'", "'", $t['value']);
                $tokens[] = $t;

            } elseif ($state === self::STATE_PIPE) {

                // Consume pipe and OR
                $tokens[] = $this->matchOr($chars, '|', '|', self::T_OR, self::T_PIPE);

            } elseif ($state == self::STATE_JSON_LITERAL) {

                // Consume JSON literals
                $token = $this->inside($chars, '`', self::T_LITERAL);
                if ($token['type'] === self::T_LITERAL) {
                    $token['value'] = str_replace('\\`', '`', $token['value']);
                    $token = $this->parseJson($token);
                }
                $tokens[] = $token;

            } elseif ($state == self::STATE_NUMBER) {

                // Consume numbers
                $start = key($chars);
                $buffer = '';
                do {
                    $buffer .= $current;
                    $current = next($chars);
                } while ($current !== false && isset($this->numbers[$current]));
                $tokens[] = [
                    'type'  => self::T_NUMBER,
                    'value' => (int)$buffer,
                    'pos'   => $start
                ];

            } elseif ($state === self::STATE_QUOTED_STRING) {

                // Consume quoted identifiers
                $token = $this->inside($chars, '"', self::T_QUOTED_IDENTIFIER);
                if ($token['type'] === self::T_QUOTED_IDENTIFIER) {
                    $token['value'] = '"' . $token['value'] . '"';
                    $token = $this->parseJson($token);
                }
                $tokens[] = $token;

            } elseif ($state === self::STATE_EQ) {

                // Consume equals
                $tokens[] = $this->matchOr($chars, '=', '=', self::T_COMPARATOR, self::T_UNKNOWN);

            } elseif ($state == self::STATE_AND) {

                $tokens[] = $this->matchOr($chars, '&', '&', self::T_AND, self::T_EXPREF);

            } elseif ($state === self::STATE_NOT) {

                // Consume not equal
                $tokens[] = $this->matchOr($chars, '!', '=', self::T_COMPARATOR, self::T_NOT);

            } else {

                // either '<' or '>'
                // Consume less than and greater than
                $tokens[] = $this->matchOr($chars, $current, '=', self::T_COMPARATOR, self::T_COMPARATOR);

            }
        }

        eof:
        $tokens[] = [
            'type'  => self::T_EOF,
            'pos'   => strlen($input),
            'value' => null
        ];

        return $tokens;
    }

    /**
     * Returns a token based on whether or not the next token matches the
     * expected value. If it does, a token of "$type" is returned. Otherwise,
     * a token of "$orElse" type is returned.
     *
     * @param array  $chars    Array of characters by reference.
     * @param string $current  The current character.
     * @param string $expected Expected character.
     * @param string $type     Expected result type.
     * @param string $orElse   Otherwise return a token of this type.
     *
     * @return array Returns a conditional token.
     */
    private function matchOr(array &$chars, $current, $expected, $type, $orElse)
    {
        if (next($chars) === $expected) {
            next($chars);
            return [
                'type'  => $type,
                'pos'   => key($chars) - 1,
                'value' => $current . $expected
            ];
        }

        return [
            'type'  => $orElse,
            'pos'   => key($chars) - 1,
            'value' => $current
        ];
    }

    /**
     * Returns a token the is the result of consuming inside of delimiter
     * characters. Escaped delimiters will be adjusted before returning a
     * value. If the token is not closed, "unknown" is returned.
     *
     * @param array  $chars Array of characters by reference.
     * @param string $delim The delimiter character.
     * @param string $type  Token type.
     *
     * @return array Returns the consumed token.
     */
    private function inside(array &$chars, $delim, $type)
    {
        $position = key($chars);
        $current = next($chars);
        $buffer = '';

        while ($current !== $delim) {
            if ($current === '\\') {
                $buffer .= '\\';
                $current = next($chars);
            }
            if ($current === false) {
                // Unclosed delimiter
                return [
                    'type'  => self::T_UNKNOWN,
                    'value' => $buffer,
                    'pos'   => $position
                ];
            }
            $buffer .= $current;
            $current = next($chars);
        }

        next($chars);

        return ['type' => $type, 'value' => $buffer, 'pos' => $position];
    }

    /**
     * Parses a JSON token or sets the token type to "unknown" on error.
     *
     * @param array $token Token that needs parsing.
     *
     * @return array Returns a token with a parsed value.
     */
    private function parseJson(array $token)
    {
        $value = json_decode($token['value'], true);

        if ($error = json_last_error()) {
            // Legacy support for elided quotes. Try to parse again by adding
            // quotes around the bad input value.
            $value = json_decode('"' . $token['value'] . '"', true);
            if ($error = json_last_error()) {
                $token['type'] = self::T_UNKNOWN;
                return $token;
            }
        }

        $token['value'] = $value;
        return $token;
    }
}


<?php
namespace DTS\eBaySDK\Shopping\Services;

/**
 * Base class for the Shopping service.
 */
class ShoppingBaseService extends \DTS\eBaySDK\Services\BaseService
{
    /**
     * HTTP header constant. Your affiliate ID.
     */
    const HDR_AFFILIATE_USER_ID = 'X-EBAY-API-AFFILIATE-USER-ID';

    /**
     * HTTP header constant. The API version your application supports.
     */
    const HDR_API_VERSION = 'X-EBAY-API-VERSION';

    /**
     * HTTP header constant. Your application ID.
     */
    const HDR_APP_ID = 'X-EBAY-API-APP-ID';

    /**
     * HTTP header constant. The name of the operation you are calling.
     */
    const HDR_OPERATION_NAME = 'X-EBAY-API-CALL-NAME';

    /**
     * HTTP header constant. Indicates that the request uses XML.
     */
    const HDR_REQUEST_FORMAT = 'X-EBAY-API-REQUEST-ENCODING';

    /**
     * HTTP header constant. The site ID of the eBay site the request is for.
     */
    const HDR_SITE_ID = 'X-EBAY-API-SITE-ID';

    /**
     * HTTP header constant. Your tracking ID.
     */
    const HDR_TRACKING_ID = 'X-EBAY-API-TRACKING-ID';

    /**
     * HTTP header constant. Your tracking partner ID.
     */
    const HDR_TRACKING_PARTNER_CODE = 'X-EBAY-API-TRACKING-PARTNER-CODE';

    /**
     * @param array $config Configuration option values.
     */
    public function __construct(array $config)
    {
        parent::__construct('http://open.api.ebay.com/shopping', 'http://open.api.sandbox.ebay.com/shopping', $config);
    }

    /**
     * Returns definitions for each configuration option that is supported.
     *
     * @return array An associative array of configuration definitions.
     */
    public static function getConfigDefinitions()
    {
        $definitions = parent::getConfigDefinitions();

        return $definitions + [
            'affiliateUserId' => [
                'valid' => ['string']
            ],
            'apiVersion' => [
                'valid' => ['string'],
                'default' => \DTS\eBaySDK\Shopping\Services\ShoppingService::API_VERSION,
                'required' => true
            ],
            'siteId' => [
                'valid' => ['int', 'string']
            ],
            'trackingId' => [
                'valid' => ['string']
            ],
            'trackingPartnerCode' => [
                'valid' => ['string']
            ]
        ];
    }

    /**
     * Builds the needed eBay HTTP headers.
     *
     * @param string $operationName The name of the operation been called.
     *
     * @return array An associative array of eBay http headers.
     */
    protected function getEbayHeaders($operationName)
    {
        $headers = [];

        // Add required headers first.
        $headers[self::HDR_API_VERSION] = $this->getConfig('apiVersion');
        $headers[self::HDR_APP_ID] = $this->getConfig('credentials')->getAppId();
        $headers[self::HDR_OPERATION_NAME] = $operationName;
        $headers[self::HDR_REQUEST_FORMAT] = 'XML';

        // Add optional headers.
        // Take into account siteId is an integer that can be set to zero.
        if ($this->getConfig('siteId') !== null) {
            $headers[self::HDR_SITE_ID] = $this->getConfig('siteId');
        }

        if ($this->getConfig('affiliateUserId')) {
            $headers[self::HDR_AFFILIATE_USER_ID] = $this->getConfig('affiliateUserId');
        }

        if ($this->getConfig('trackingId')) {
            $headers[self::HDR_TRACKING_ID] = $this->getConfig('trackingId');
        }

        if ($this->getConfig('trackingPartnerCode')) {
            $headers[self::HDR_TRACKING_PARTNER_CODE] = $this->getConfig('trackingPartnerCode');
        }

        return $headers;
    }
}