<?php
	define( 'SHOPPING_API_URL',	'https://open.api.ebay.com/shopping?' );
	define( 'TRADING_API_URL',		'https://api.ebay.com/ws/api.dll' );
	define( 'FINDING_API_URL',		'https://svcs.ebay.com/services/search/FindingService/v1' );
	define( 'API_SHOPPING',			0 );
	define( 'API_TRADING',			1 );
	define( 'API_FINDING',			2 );
	define( 'API_VER',				'615' );
	define( 'FINDING_API_VER',		'1.13.0' );

	define( 'DEBUG', true );

	class eBayClass
	{
		private $DEVID		=	null;
		private $AppID		=	null;
		private $CertID		=	null;
		private	$TOKEN		=	null;

		function __construct( $DEVID, $AppID, $CertID, $TOKEN )
		{
			$this->DEVID	=	$DEVID;
			$this->AppID	=	$AppID;
			$this->CertID	=	$CertID;
			$this->TOKEN	=	$TOKEN;
		}
		
		private function DoPost( $api, $callname, $query )
		{
			// Does an HTTP post to either the shopping API or the trading API

			switch ( $api )
			{
				case API_SHOPPING:	$ch	=	curl_init( SHOPPING_API_URL );

							$headers=	Array(
								'X-EBAY-API-APP-ID: ' . $this->AppID,
								'X-EBAY-API-CALL-NAME: ' . $callname,
								'X-EBAY-API-VERSION: ' . API_VER,
								'X-EBAY-API-REQUEST-ENCODING: XML',
								'X-EBAY-API-RESPONSE-ENCODING: XML',
								'Content-Type: text/xml;charset=utf-8'
							);

							break;
				case API_TRADING:	$ch	=	curl_init( TRADING_API_URL );

							$headers=	Array(
								'X-EBAY-API-COMPATIBILITY-LEVEL: ' . API_VER,
								'X-EBAY-API-CALL-NAME: ' . $callname,
								'X-EBAY-API-SITEID: 0',
								'X-EBAY-API-DEV-NAME: ' . $this->DEVID,
								'X-EBAY-API-APP-NAME: ' . $this->AppID,
								'X-EBAY-API-CERT-NAME: ' . $this->CertID,
								'Content-Type: text/xml;charset=utf-8'
							);

							break;
							
				case API_FINDING:	$ch	=	curl_init( FINDING_API_URL );
				
							$headers=	Array(
								'X-EBAY-SOA-SERVICE-NAME: FindingService',
								'X-EBAY-SOA-OPERATION-NAME: ' . $callname,
								'X-EBAY-SOA-SERVICE-VERSION: ' . FINDING_API_VER,
								'X-EBAY-SOA-GLOBAL-ID: EBAY-US',
								'X-EBAY-SOA-SECURITY-APPNAME: ' . $this->AppID,
								'X-EBAY-SOA-REQUEST-DATA-FORMAT: XML',
								);
								
							break;
			}

			$curlLog = DEBUG ? fopen("/tmp/curl-error-log.txt", "a+") : null;

			curl_setopt_array( $ch, Array(
					CURLOPT_POST			=>	true,
					CURLOPT_POSTFIELDS		=>	$query,
					CURLOPT_HTTPHEADER		=>	$headers,
					CURLOPT_RETURNTRANSFER	=>	true,
					CURLOPT_VERBOSE			=>	DEBUG,
					CURLOPT_STDERR			=> 	$curlLog,
				)
			);

			if ( DEBUG ) echo $query;

			$ret = curl_exec( $ch );
			if ( DEBUG ) fwrite( $curlLog, "\n\n\nresponse:\n\n" . $ret . "\n\n" );
			
			curl_close( $ch );
			if ( DEBUG ) fclose( $curlLog );

			return $ret;
		}

		private function AddParameters( $ParentNode, $parameters, $root = true )
		{
			// Transforms an array of parameters into a DOM tree to be used in an API call
			// $root is true when this is the root of the parameter structure and not a recursive call, 
			//		this is used for adding in the RequesterCrendentials in the right place
			
			if ( $root && !isset( $parameters[ 'RequesterCredentials' ] ) )
			{
				$parameters[ 'RequesterCredentials' ]		=		Array(
					'eBayAuthToken'         =>      $this->TOKEN
				);
			}

			foreach ( $parameters as $key => $val )
			{
				// need this for repeatable fields
				$key	=	preg_split( '/\|/', $key );
				$key	=	$key[ 0 ];

				if ( is_array( $val ) )
				{
					$SubNode	=	$ParentNode->ownerDocument->createElement( $key );
					$this->AddParameters( $SubNode, $val, false );
				}
				else
				{
					$SubNode	=	$ParentNode->ownerDocument->createElement( $key );
					$TNode		=	$ParentNode->ownerDocument->createTextNode( $val );
					$SubNode->appendChild( $TNode );
				}

				$ParentNode->appendChild( $SubNode );
			}
		}

		private function eBayCall( $api, $callname, $parameters )
		{
			// Constructs the XML structure for an eBay API call, makes the call, feeds the response
			// into the PHP XML interpreter and returns the DOM tree that results.
			$DOM		=	new DOMdocument( '1.0', 'utf-8' );

			$RequestNode	=	$DOM->createElementNS( 
				$api == API_FINDING ? 'http://www.ebay.com/marketplace/search/v1/services' : 'urn:ebay:apis:eBLBaseComponents', 
				$callname . 'Request' 
			);
			$DOM->appendChild( $RequestNode );

			$this->AddParameters( $RequestNode, $parameters );

			$DOM->formatOutput	=	true;
			//echo $DOM->saveXML();
			$RawResponse		=	$this->DoPost( $api, $callname, $DOM->saveXML() );
			// echo $RawResponse;
			$Response		=	new DOMDocument;
			$Response->loadXML( $RawResponse );

			$Errors		=	$Response->getElementsByTagName( 'Errors' );
			if ( $Errors->length > 0 )
			{
				$LongMessage	=	$Errors->item( 0 )->getElementsByTagName( 'LongMessage' )->item( 0 )->nodeValue;
				return $LongMessage;	
			}
			else
				return $Response;
		}

		// Shortcuts for making calls specifically to the shopping and trading API's
		public function ShoppingCall( $callname, $parameters ) 		{	return $this->eBayCall( API_SHOPPING, $callname, $parameters );		}
		public function TradingCall( $callname, $parameters )		{	return $this->eBayCall( API_TRADING, $callname, $parameters );		}
		public function FindingCall( $callname, $parameters )		{	return $this->eBayCall( API_FINDING, $callname, $parameters );		}

		public function DOMToArray( $DOM )
		{
			// Converts a DOM tree response into an array.  For processing search results more easily.

			$ret	=	Array();

			foreach ( $DOM->childNodes as $Node )
			{
				if ( $Node->firstChild == null )
					$ret[ $Node->nodeName ] 	=	$Node->nodeValue;
				else
					$ret[ $Node->nodeName ]		=	$this->DOMToArray( $Node );
			}

			// for text nodes, just return the node's text content instead of the actual subnode.
			if ( ( count( $ret ) == 1 ) && ( isset( $ret[ '#text' ] ) ) )	return $ret[ '#text' ];
			else return $ret;
		}
		
		public function eBayDateFormat( $timestamp )
		{
			// accepts a time() format timestamp and returns a timestamp in eBay's unique little format
			// YYYY-MM-DDTHH:MM:SS.SSSZ (e.g., 2004-08-04T19:09:02.768Z)
			
			return date( 'Y-m-d\TH:i:s.000\Z', $timestamp );
		}

		public function getUsage()
		{
			$res	=	$this->TradingCall( 'GetApiAccessRules', Array() );

			$Rules	=	$res->getElementsByTagName( 'ApiAccessRule' );
			$ret	=	Array();

			foreach ( $Rules as $Rule )
			{	
				$Fields	=	Array( 'CallName', 'DailyHardLimit', 'DailyUsage', 'HourlyHardLimit', 'HourlyUsage' );
				$rdata	=	Array();

				foreach ( $Fields as $Field )
				{
					$foo	=	$Rule->getElementsByTagName( $Field );
					if ( $foo->length > 0 )
						$rdata[ $Field ] 		=	$foo->item( 0 )->nodeValue;
					else
						$rdata[ $Field ]		=	null;
				}

				$ret[ $rdata[ 'CallName' ] ]	=	$rdata;
			}

			return $ret;
		}

		public function getAggregateUsage()
		{
			$foo	=	$this->getUsage();
			return $foo[ 'ApplicationAggregate' ];
		}
	
	}
?>
