diff --git a/README.md b/README.md index 4869177..c371b40 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ Amazon Product Advertising API PHP Amazon Product Advertising API V5.0 (**Without Amazon SDK**). -This repository contains a PHP Lightweight (155 Ko) Wrapper Library, Allows you accessing the [Amazon Product Advertising API V5.0](https://webservices.amazon.com/paapi5/documentation/index.html) from your PHP App, Quickly & easily! +This repository contains a PHP Lightweight (155 Ko) Wrapper Library, +Easily access the [Amazon Product Advertising API V5.0](https://webservices.amazon.com/paapi5/documentation/index.html) from your PHP app. -- Become an Amazon Affiliate With PHP -- @@ -30,41 +31,56 @@ include('apaapi-master/src/Autoloader.php'); * **4** - You can now use the [Quickstart examples](#quickstart). -## 🔨 Upgrade : +## 💡 Upgrade : **See changes before migrate**: This version includes: -* Support for **disabled cURL** (*Used Stream*). -* Throws exception if **cURL** AND **Stream** are disabled. -* Error reporting (Including semantic errors with **status 200** & **HTTP Client Errors**), [More](https://webservices.amazon.com/paapi5/documentation/troubleshooting/processing-of-errors.html#processing-of-errors). -* HTTP Client helpers (*RequestClient::hasCurl() & RequestClient::hasStream()*). -* Response parsing (*object/array/serialized*). -* Throws exception if Locale (*Region/TLD*) is invalid, [More](https://webservices.amazon.fr/paapi5/documentation/locale-reference.html). -* Throws exception if Resource (*e.g. Images.Primary.Large*) is invalid, [More](https://webservices.amazon.fr/paapi5/documentation/resources.html). +* Basic built-in **Caching System**. +* **Request Builder** (Easier way to fetch data). +* **Response Normalizer** (Normalize response items). +* **Search Filters** (Using builder). +* **Geotargeting** (Automatically redirect links based on the visitor's region). +* **Rating** (Lagacy). +* **Keyword Converter** (ASIN, ISBN, EAN, Node, Root). -And had many improvements: - -* Uses default [Ressources](https://webservices.amazon.fr/paapi5/documentation/resources.html) for each [Operation](https://webservices.amazon.fr/paapi5/documentation/operations.html). -* Clean ecosystem. -* [Extendable HTTP Client](#advanced-custom-http-request-client). +[Full Changelog](#). ## ⚡ Getting Started: ### Variables (Basics): -* "{Your-partner-tag}" : From your Amazon Associates (*your locale*), [More](https://webservices.amazon.com/paapi5/documentation/troubleshooting/sign-up-as-an-associate.html). -* "{Your-secrect-key}" : From your Amazon Associates (*your locale*), [More](https://affiliate-program.amazon.com/help/node/topic/GTPNVFFUV2GQ8AZV). -* "{Your-key-id}" : From your Amazon Associates (*your locale*), [More](https://affiliate-program.amazon.com/help/node/topic/GTPNVFFUV2GQ8AZV). -* "{Your-keywords}" : What you are looking for (*Products*), [More](https://webservices.amazon.com/paapi5/documentation/search-items.html). -* "{Your-region}" : **TLD** of the target to which you are sending requests (*com/fr/com.be/de*), [Get TLD](https://webservices.amazon.com/paapi5/documentation/common-request-parameters.html#host-and-region). -* "{ASIN}" : Amazon Standard Identification Number (*your locale*), [More](https://webservices.amazon.com/paapi5/documentation/get-items.html#ItemLookup-rp). +* "_TAG_" : From your Amazon Associates (*your locale*), [More](https://webservices.amazon.com/paapi5/documentation/troubleshooting/sign-up-as-an-associate.html). +* "_SECRET_" : From your Amazon Associates (*your locale*), [More](https://affiliate-program.amazon.com/help/node/topic/GTPNVFFUV2GQ8AZV). +* "_KEY_" : From your Amazon Associates (*your locale*), [More](https://affiliate-program.amazon.com/help/node/topic/GTPNVFFUV2GQ8AZV). +* "_KEYWORDS_" : What you are looking for (*Products*), [More](https://webservices.amazon.com/paapi5/documentation/search-items.html). +* "_REGION_" : **TLD** of the target to which you are sending requests (*com/fr/com.be/de*), [Get TLD](https://webservices.amazon.com/paapi5/documentation/common-request-parameters.html#host-and-region). +* "_ASIN_" : Amazon Standard Identification Number (*your locale*), [More](https://webservices.amazon.com/paapi5/documentation/get-items.html#ItemLookup-rp). ### Quickstart: +```php + +/** + * @see Use Composer, + * Or include Apaapi Autoloader Here. + */ + +use Apaapi\includes\Builder; + +// Init request builder +$builder = new Builder('_KEY_', '_SECRET_', '_TAG_', '_REGION_'); + +// Get response +$data = $builder->searchOne('Sony Xperia Pro-I') // Normalized array + +``` + +### Quickstart (OLD): + ```php @@ -86,11 +102,11 @@ use Apaapi\lib\Response; // (1) Set Operation $operation = new SearchItems(); -$operation->setPartnerTag('{Your-partner-tag}')->setKeywords('{Your-keywords}'); +$operation->setPartnerTag('_TAG_')->setKeywords('_KEYWORDS_'); // (2) Prapere Request -$request = new Request('{Your-key-id}','{Your-secrect-key}'); -$request->setLocale('{Your-region}')->setPayload($operation); +$request = new Request('_KEY_','_SECRET_'); +$request->setLocale('_REGION_')->setPayload($operation); // (3) Get Response $response = new Response($request); @@ -116,22 +132,22 @@ use Apaapi\operations\GetBrowseNodes; // GetItems $operation = new GetItems(); -$operation->setPartnerTag('{Your-partner-tag}') -->setItemIds(['{ASIN}']); // Array|String +$operation->setPartnerTag('_TAG_') +->setItemIds(['_ASIN_']); // Array|String // SearchItems $operation = new SearchItems(); -$operation->setPartnerTag('{Your-partner-tag}') -->setKeywords('{Your-keywords}'); // Array|String +$operation->setPartnerTag('_TAG_') +->setKeywords('_KEYWORDS_'); // Array|String // GetVariations $operation = new GetVariations(); -$operation->setPartnerTag('{Your-partner-tag}') -->setASIN('{ASIN}'); // String +$operation->setPartnerTag('_TAG_') +->setASIN('_ASIN_'); // String // GetBrowseNodes $operation = new GetBrowseNodes(); -$operation->setPartnerTag('{Your-partner-tag}') +$operation->setPartnerTag('_TAG_') ->setBrowseNodeIds(['{NodeId}']); // Array|String ``` @@ -147,7 +163,7 @@ $operation->setPartnerTag('{Your-partner-tag}') */ // Set Operation -$operation->setPartnerTag('{Your-partner-tag}')->setKeywords('{Your-keywords}') +$operation->setPartnerTag('_TAG_')->setKeywords('_KEYWORDS_') ->setResources(['Images.Primary.Small','ItemInfo.Title','Offers.Listings.Price']); ``` @@ -174,10 +190,10 @@ class MyRequestClient extends RequestClient // Set Operation $operation = new GetItems(); -$operation->setPartnerTag('{Your-partner-tag}')->setItemIds('{ASIN}'); +$operation->setPartnerTag('_TAG_')->setItemIds('_ASIN_'); // Prapere Request -$request = new Request('{Your-key-id}','{Your-secrect-key}'); +$request = new Request('_KEY_','_SECRET_'); $request->setLocale('{your-region}')->setPayload($operation); // Set Custom Client After Payload @@ -249,11 +265,11 @@ if ( $response->hasError() ) { // Set Cart $cart = new Cart(); $cart->setLocale('{Your-locale}'); -$cart->setPartnerTag('{Your-partner-tag}'); +$cart->setPartnerTag('_TAG_'); // Set Items $items = [ - '{ASIN1}' => '3', // ({ASIN} => {Quantity}) + '{ASIN1}' => '3', // (_ASIN_ => {Quantity}) '{ASIN2}' => '5' ]; diff --git a/examples/add-to-cart.php b/examples/add-to-cart.php index d9bb906..77cdf68 100644 --- a/examples/add-to-cart.php +++ b/examples/add-to-cart.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -12,26 +12,26 @@ /** * @see You can use Composer, - * Or include Apaapi Standalone Autoloader Here. + * Or include Apaapi standalone autoloader here. */ include('../src/Autoloader.php'); \apaapi\Autoloader::init(); use Apaapi\lib\Cart; -// Set Cart +// init cart $cart = new Cart(); $cart->setLocale('{Your-locale}'); $cart->setPartnerTag('{Your-partner-tag}'); -// Set Items +// Set items $items = [ - '{ASIN1}' => '3', // ({ASIN} => {Quantity}) - '{ASIN2}' => '5' + '{ASIN1|ISBN1}' => '3', // ({ASIN|ISBN} => {Quantity}) + '{ASIN2|ISBN2}' => '5' ]; -// Get Response -$url = $cart->add($items); -var_dump($url); // String URL +// Get response +$url = $cart->set($items); +echo $url; // String -// Hope you found this useful, any suggestions (Pull requests) are welcome! +// Any suggestions (PR) are welcome! diff --git a/examples/basic.php b/examples/basic.php index 699450f..0d1188b 100644 --- a/examples/basic.php +++ b/examples/basic.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -12,7 +12,7 @@ /** * @see You can use Composer, - * Or include Apaapi Standalone Autoloader Here. + * Or include Apaapi standalone autoloader here. */ include('../src/Autoloader.php'); \apaapi\Autoloader::init(); @@ -21,16 +21,18 @@ use Apaapi\lib\Request; use Apaapi\lib\Response; -// Set Operation +// Set operation $operation = new SearchItems(); $operation->setPartnerTag('{Your-partner-tag}')->setKeywords('{Your-keywords}'); -// Prapere Request +// Prapere request $request = new Request('{Your-key-id}','{Your-secrect-key}'); $request->setLocale('{Your-locale}')->setPayload($operation); -// Get Response +// Get response $response = new Response($request); -echo $response->get(); // JSON ready to be parsed +$data = $response->get(); // Array +$body = $response->getBody(); // String +print_r($data); -// Hope you found this useful, any suggestions (Pull requests) are welcome! +// Any suggestions (PR) are welcome! diff --git a/examples/custom-client.php b/examples/custom-client.php index 87b03be..4793a64 100644 --- a/examples/custom-client.php +++ b/examples/custom-client.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -12,7 +12,7 @@ /** * @see You can use Composer, - * Or include Apaapi Standalone Autoloader Here. + * Or include Apaapi standalone autoloader here. */ include('../src/Autoloader.php'); \apaapi\Autoloader::init(); @@ -20,58 +20,65 @@ use Apaapi\operations\SearchItems; use Apaapi\lib\Request; use Apaapi\lib\Response; -use Apaapi\includes\RequestClient; +use Apaapi\includes\Client; /** - * Create Custom Request Client Class. + * Custom request client class. */ -class MyRequestClient extends RequestClient +class MyClient extends Client { - protected function init() + // Enable request client exception + public function __construct(string $endpoint, array $params = []) + { + parent::__construct($endpoint, $params, true); + } + + // Override handler behavior + protected function setHandler() { if ( self::hasCurl() ) { - // cURL override example + // Override curl $this->handler = curl_init(); curl_setopt($this->handler, CURLOPT_URL, $this->endpoint); curl_setopt($this->handler, CURLOPT_HTTPHEADER, $this->getRequestHeader()); curl_setopt($this->handler, CURLOPT_POSTFIELDS, $this->getRequestContent()); curl_setopt($this->handler, CURLOPT_POST, true); curl_setopt($this->handler, CURLOPT_RETURNTRANSFER, true); - curl_setopt($this->handler, CURLOPT_SSL_VERIFYPEER, true); // Force SSL instead of Auto - curl_setopt($this->handler, CURLOPT_TIMEOUT, 10); // Custom Timeout instead of 30 + curl_setopt($this->handler, CURLOPT_SSL_VERIFYPEER, true); // Force SSL + curl_setopt($this->handler, CURLOPT_TIMEOUT, 10); // Set custom timeout } elseif ( self::hasStream() ) { - // Stream override example - $this->handler = [ + // Override stream + $this->handler = stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => $this->getRequestHeader(), 'content' => $this->getRequestContent(), - 'timeout' => 10 // Custom Timeout instead of 30 + 'timeout' => $this->timeout // Set custom timeout ] - ]; + ]); } } } -// Set Operation +// Set operation $operation = new SearchItems(); -$operation->setPartnerTag('{Your-partner-tag}')->setKeywords('{Your-keywords}') -->setResources(['Images.Primary.Small','ItemInfo.Title','Offers.Listings.Price']); +$operation->setPartnerTag('{Your-partner-tag}')->setKeywords('{Your-keywords}'); -// Prapere Request +// Prapere request $request = new Request('{Your-key-id}','{Your-secrect-key}'); $request->setLocale('{Your-locale}')->setPayload($operation); -// Set Custom Client after Payload +// Set custom client after payload $request->setClient( - new MyRequestClient($request->getEndpoint(), $request->getParams()) + new MyClient($request->getEndpoint(), $request->getParams()) ); -// Get Response +// Get response $response = new Response($request); -echo $response->get(); // JSON ready to be parsed +$data = $response->get(); // Array +print_r($data); -// Hope you found this useful, any suggestions (Pull requests) are welcome! +// Any suggestions (PR) are welcome! diff --git a/examples/custom-error-handling.php b/examples/custom-error-handling.php index d615746..f720312 100644 --- a/examples/custom-error-handling.php +++ b/examples/custom-error-handling.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -23,8 +23,7 @@ // Set Operation $operation = new SearchItems(); -$operation->setPartnerTag('{Your-partner-tag}')->setKeywords('{Your-keywords}') -->setResources(['Images.Primary.Small','ItemInfo.Title','Offers.Listings.Price']); +$operation->setPartnerTag('{Your-partner-tag}')->setKeywords('{Your-keywords}'); // Prapere Request $request = new Request('{Your-key-id}','{Your-secrect-key}'); diff --git a/examples/custom-response.php b/examples/custom-response.php index ff87430..c2d788d 100644 --- a/examples/custom-response.php +++ b/examples/custom-response.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -12,7 +12,7 @@ /** * @see You can use Composer, - * Or include Apaapi Standalone Autoloader Here. + * Or include Apaapi standalone autoloader here. */ include('../src/Autoloader.php'); \apaapi\Autoloader::init(); @@ -20,18 +20,18 @@ use Apaapi\operations\SearchItems; use Apaapi\lib\Request; use Apaapi\lib\Response; -use Apaapi\includes\ResponseType; -// Set Operation +// Set operation $operation = new SearchItems(); $operation->setPartnerTag('{Your-partner-tag}')->setKeywords('{Your-keywords}'); -// Prapere Request +// Prapere request $request = new Request('{Your-key-id}','{Your-secrect-key}'); $request->setLocale('{Your-locale}')->setPayload($operation); -// Get Response +// Get response $response = new Response($request, new ResponseType('array'), Response::PARSE); -var_dump($response->get()); // Array ready to be used +$body = $response->getBody(); // String +print_r($body); // Hope you found this useful, any suggestions (Pull requests) are welcome! diff --git a/examples/custom-ressources.php b/examples/custom-ressources.php index 2c54e9f..9ce6854 100644 --- a/examples/custom-ressources.php +++ b/examples/custom-ressources.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -12,7 +12,7 @@ /** * @see You can use Composer, - * Or include Apaapi Standalone Autoloader Here. + * Or include Apaapi standalone autoloader here. */ include('../src/Autoloader.php'); \apaapi\Autoloader::init(); @@ -21,17 +21,22 @@ use Apaapi\lib\Request; use Apaapi\lib\Response; -// Set Operation +// Set operation $operation = new SearchItems(); $operation->setPartnerTag('{Your-partner-tag}')->setKeywords('{Your-keywords}') -->setResources(['Images.Primary.Small','ItemInfo.Title','Offers.Listings.Price']); +->setResources([ + 'Images.Primary.Small', + 'ItemInfo.Title', + 'Offers.Listings.Price' +]); -// Prapere Request +// Prapere request $request = new Request('{Your-key-id}','{Your-secrect-key}'); $request->setLocale('{Your-locale}')->setPayload($operation); -// Get Response +// Get response $response = new Response($request); -echo $response->get(); // JSON ready to be parsed +$data = $response->get(); // Array +print_r($data); -// Hope you found this useful, any suggestions (Pull requests) are welcome! +// Any suggestions (PR) are welcome! diff --git a/examples/disable-client-exception.php b/examples/disable-client-exception.php deleted file mode 100644 index b213d77..0000000 --- a/examples/disable-client-exception.php +++ /dev/null @@ -1,64 +0,0 @@ - - * @link : https://jakiboy.github.io/apaapi/ - * @license : MIT - * - * This file if a part of Apaapi Lib. - */ - -/** - * @see You can use Composer, - * Or include Apaapi Standalone Autoloader Here. - */ -include('../src/Autoloader.php'); -\apaapi\Autoloader::init(); - -use Apaapi\operations\SearchItems; -use Apaapi\lib\Request; -use Apaapi\lib\Response; -use Apaapi\includes\RequestClient; - -/** - * Create Custom Request Client Class. - */ -class MyRequestClient extends RequestClient -{ - public function __construct($endpoint, $params) - { - // Disable request client exception when both cURL & Stream functions are disabled - parent::__construct($endpoint,$params,false); - } -} - -// Set Operation -$operation = new SearchItems(); -$operation->setPartnerTag('{Your-partner-tag}')->setKeywords('{Your-keywords}') -->setResources(['Images.Primary.Small','ItemInfo.Title','Offers.Listings.Price']); - -// Prapere Request -$request = new Request('{Your-key-id}','{Your-secrect-key}'); -$request->setLocale('{Your-locale}')->setPayload($operation); - -// Set Custom Client after Payload -$request->setClient( - new MyRequestClient($request->getEndpoint(), $request->getParams()) -); - -// Get Response -$response = new Response($request); -$data = $response->get(); // JSON error ready for parsing - -// Get formated error without exception handling -if ( $response->hasError() ) { - /** - * @param bool $single error - * @return string|array - */ - echo $response->getError(true); // Parsed error -} - -// Hope you found this useful, any suggestions (Pull requests) are welcome! diff --git a/examples/simple.php b/examples/simple.php new file mode 100644 index 0000000..8c55a5b --- /dev/null +++ b/examples/simple.php @@ -0,0 +1,29 @@ + + * @link : https://jakiboy.github.io/apaapi/ + * @license : MIT + * + * This file if a part of Apaapi Lib. + */ + +/** + * @see You can use Composer, + * Or include Apaapi standalone autoloader here. + */ +include('../src/Autoloader.php'); +\apaapi\Autoloader::init(); + +use Apaapi\includes\Builder; + +// Prapere request +$builder = new Builder('{Your-key-id}', '{Your-secrect-key}', '{Your-partner-tag}', '{Your-locale}'); + +// Get response +$data = $builder->search('{Your-keywords}'); // Array +print_r($data); + +// Any suggestions (PR) are welcome! diff --git a/src/Autoloader.php b/src/Autoloader.php index 3d0d3d1..d89eb7b 100644 --- a/src/Autoloader.php +++ b/src/Autoloader.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -13,7 +13,7 @@ namespace Apaapi; /** - * Apaapi Standalone Autoloader. + * Apaapi standalone autoloader. */ final class Autoloader { @@ -24,24 +24,22 @@ final class Autoloader private static $initialized = false; /** - * Register Apaapi autoloader. - * - * @param void + * Register autoloader. */ public function __construct() { if ( !static::$initialized ) { - spl_autoload_register([__CLASS__,'autoload']); + spl_autoload_register([__CLASS__, 'autoload']); static::$initialized = true; } } /** - * Unregister Apaapi autoloader. + * Unregister autoloader. */ public function __destruct() { - spl_autoload_unregister([__CLASS__,'autoload']); + spl_autoload_unregister([__CLASS__, 'autoload']); } /** @@ -53,43 +51,42 @@ public function __clone() } /** - * Restrict object clone. + * Restrict object unserialize. */ public function __wakeup() { die(__METHOD__.': Unserialize denied'); } + /** + * Initialize autoloader. + * + * @access public + * @return void + */ + public static function init() + { + if ( !static::$initialized ) { + new static; + } + } + /** * Autoloader method. * * @access private - * @param string $class __CLASS__ + * @param string $class * @return void * @see https://www.php-fig.org/psr/psr-4/ * @see https://www.php-fig.org/psr/psr-0/ */ - private function autoload($class) + private function autoload(string $class) { if ( strpos($class, __NAMESPACE__ . '\\') === 0 ) { $class = str_replace(__NAMESPACE__ . '\\', '', $class); - $class = str_replace('\\','/',$class); - $root = str_replace('\\','/',dirname(__DIR__)); + $class = str_replace('\\', '/', $class); + $root = str_replace('\\', '/', dirname(__DIR__)); require_once("{$root}/src/{$class}.php"); } } - - /** - * Initialize autoloader. - * - * @access public - * @param void - * @return void - */ - public static function init() - { - if ( !static::$initialized ) { - new static; - } - } } diff --git a/src/exceptions/OperationException.php b/src/exceptions/OperationException.php index fcca1c0..9eb5db4 100644 --- a/src/exceptions/OperationException.php +++ b/src/exceptions/OperationException.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -13,12 +13,13 @@ namespace Apaapi\exceptions; /** - * Basic Apaapi Operation Exception Class. + * Apaapi operation exception. */ final class OperationException extends \Exception { - public static function invalidOperationRessource($ressource = null) + public static function invalidRessources(?string $item = null) : string { - return "Invalid operation ressource '{$ressource}'"; + if ( !$item ) $item = 'undefined'; + return "Invalid operation ressources '{$item}'"; } } diff --git a/src/exceptions/RequestException.php b/src/exceptions/RequestException.php index 4280633..90805a8 100644 --- a/src/exceptions/RequestException.php +++ b/src/exceptions/RequestException.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -13,17 +13,18 @@ namespace Apaapi\exceptions; /** - * Basic Apaapi Request Exception Class. + * Apaapi request exception. */ final class RequestException extends \Exception { - public static function invalidRequestClientMessage() + public static function invalidClient() : string { return 'Could not send request (Missing cURL and Stream functions)'; } - public static function invalidRequestLocaleMessage($local = null) + public static function invalidLocale(?string $locale = null) : string { - return "Invalid request locale '{$local}'"; + if ( !$locale ) $locale = 'undefined'; + return "Invalid request locale '{$locale}'"; } } diff --git a/src/exceptions/ResponseTypeException.php b/src/exceptions/ResponseTypeException.php deleted file mode 100644 index 4e739e3..0000000 --- a/src/exceptions/ResponseTypeException.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @link : https://jakiboy.github.io/apaapi/ - * @license : MIT - * - * This file if a part of Apaapi Lib. - */ - -namespace Apaapi\exceptions; - -/** - * Basic Apaapi Response Type Exception Class. - */ -final class ResponseTypeException extends \Exception -{ - public static function invalidResponseTypeFormat($type = null) - { - return "Invalid response type format '{$type}', Expected (Object/Array/Serialized)"; - } -} diff --git a/src/includes/Builder.php b/src/includes/Builder.php new file mode 100644 index 0000000..760af96 --- /dev/null +++ b/src/includes/Builder.php @@ -0,0 +1,881 @@ + + * @link : https://jakiboy.github.io/apaapi/ + * @license : MIT + * + * This file if a part of Apaapi Lib. + */ + +declare(strict_types=1); + +namespace Apaapi\includes; + +use Apaapi\lib\{ + Request, + Response, + Cart +}; +use Apaapi\operations\{ + GetItems, + SearchItems, + GetVariations, + GetBrowseNodes +}; + +/** + * Apaapi request builder. + */ +final class Builder +{ + /** + * @access private + * @var string $key, API key + * @var string $secret, API secret + * @var string $tag, API tag + * @var string $locale, API locale|region + * @var object $operation, API operation + * @var object $request, API request + * @var string $error, API response error + * @var array $redirect, Geotargeting args + * @var mixed $order, Response order + */ + private $key; + private $secret; + private $tag; + private $locale; + private $operation; + private $request; + private $error = false; + private $redirect; + private $order = ['title', 'price']; + + /** + * Set request authentication. + * + * @param string $key + * @param string $secret + * @param string $tag + * @param string $locale + */ + public function __construct(string $key, string $secret, string $tag, string $locale) + { + $this->key = $key; + $this->secret = $secret; + $this->tag = $tag; + $this->locale = $locale; + } + + /** + * Check authorization. + * + * @access public + * @return bool + */ + public function isAuthorized() : bool + { + $this->setup('search'); + + $this->operation->setResources(['ItemInfo.Title']); + $this->operation->setItemCount(1)->setKeywords('amazon'); + + $this->prepare(); + $response = new Response($this->request, false, false); + $response->get(); + + if ( $response->hasError() ) { + $this->error = $response->getError(); + return false; + } + + return true; + } + + /** + * Get items. + * + * @access public + * @param mixed $ids + * @param array $filter + * @return array + */ + public function get($ids, array $filter = []) : array + { + $this->setup('get')->filter($filter); + + $this->operation->setResources( + $this->getDefaultResources() + ); + + $this->operation->setItemIds( + Normalizer::formatIds($ids) + ); + + $items = $this->prepare()->fetch(); + return Normalizer::order($items, $this->order); + } + + /** + * Get single item. + * + * @access public + * @param string $id + * @param array $filter + * @return array + */ + public function getOne(string $id, array $filter = []) : array + { + $this->setup('get')->filter($filter); + + $this->operation->setResources( + $this->getDefaultResources() + ); + + $this->operation->setItemIds([ + Normalizer::formatId($id) + ]); + + $item = $this->prepare()->fetch(); + return $item[0] ?? []; + } + + /** + * Search items. + * + * @access public + * @param mixed $keywords + * @param int $count + * @param int $page + * @param array $filter + * @return array + */ + public function search($keywords, int $count = 5, int $page = 1, array $filter = []) : array + { + $this->setup('search')->filter($filter); + + $this->operation->setResources( + $this->getDefaultResources() + ); + + $this->operation->setItemCount($count)->setItemPage($page); + $this->operation->setKeywords( + Normalizer::formatKeywords($keywords) + ); + + $items = $this->prepare()->fetch(); + return Normalizer::order($items, $this->order); + } + + /** + * Search single item. + * + * @access public + * @param string $keyword + * @param array $filter + * @return array + */ + public function searchOne(string $keyword, array $filter = []) : array + { + $this->setup('search')->filter($filter); + + $this->operation->setResources( + $this->getDefaultResources() + ); + + $this->operation->setItemCount(1); + $this->operation->setKeywords( + Normalizer::formatKeyword($keyword) + ); + + $item = $this->prepare()->fetch(); + return $item[0] ?? []; + } + + /** + * Get item variations. + * + * @access public + * @param string $id + * @param int $count + * @param int $page + * @param array $filter + * @return array + */ + public function getVariation(string $id, int $count = 5, int $page = 1, array $filter = []) : array + { + $this->setup('variation')->filter($filter); + + $this->operation->setResources( + $this->getDefaultResources() + ); + + $this->operation->setVariationCount($count)->setVariationPage($page); + $this->operation->setASIN( + Normalizer::formatId($id) + ); + + $items = $this->prepare()->fetch(); + return Normalizer::order($items, $this->order); + } + + /** + * Seatch item variations. + * + * @access public + * @param string $keyword + * @param int $count + * @param int $page + * @param array $filter + * @return array + */ + public function searchVariation(string $keyword, int $count = 5, int $page = 1, array $filter = []) : array + { + $id = Normalizer::formatId($keyword); + if ( !Keyword::isASIN($id) ) { + $keyword = $this->toASIN($keyword, $filter); + } + return $this->getVariation($keyword, $count, $page, $filter); + } + + /** + * Convert keyword to ASIN (ISBN). + * + * @access public + * @param string $keyword + * @param array $filter + * @return string + */ + public function toASIN(string $keyword, array $filter = []) : string + { + $id = Normalizer::formatId($keyword); + if ( Keyword::isASIN($id) ) { + return $id; + } + + $this->setup('search')->filter($filter); + + $this->operation->setResources(['ItemInfo.Title']); + $this->operation->setItemCount(1); + $this->operation->setKeywords( + Normalizer::formatKeyword($keyword) + ); + + $item = $this->prepare()->fetch(); + $item = $item[0] ?? []; + return $item['asin'] ?? ''; + } + + /** + * Convert keyword to EAN. + * + * @access public + * @param string $keyword + * @param array $filter + * @return string + */ + public function toEAN(string $keyword, array $filter = []) : string + { + $this->setup('search')->filter($filter); + + $this->operation->setResources(['ItemInfo.ExternalIds']); + $this->operation->setItemCount(1); + $this->operation->setKeywords( + Normalizer::formatKeyword($keyword) + ); + + $item = $this->prepare()->fetch(); + $item = $item[0] ?? []; + return $item['ean'] ?? ''; + } + + /** + * Convert keyword to node Id. + * + * @access public + * @param string $keyword + * @param array $filter + * @param bool $root + * @return string + */ + public function toNode(string $keyword, array $filter = [], bool $root = false) : string + { + $this->setup('search')->filter($filter); + + $this->operation->setResources([ + 'BrowseNodeInfo.BrowseNodes.Ancestor' + ]); + + $this->operation->setItemCount(1); + $this->operation->setKeywords( + Normalizer::formatKeyword($keyword) + ); + + $item = $this->prepare()->fetch(); + $item = $item[0] ?? []; + + if ( $root ) { + return $item['root'] ?? ''; + } + return $item['node'] ?? ''; + } + + /** + * Convert keyword to node Id (root). + * + * @access public + * @param string $keyword + * @param array $filter + * @return string + */ + public function toRoot(string $keyword, array $filter = []) : string + { + return $this->toNode($keyword, $filter, true); + } + + /** + * Get node (Category). + * + * @access public + * @param string $id + * @return array + */ + public function getNode(string $id) : array + { + $this->setup('node'); + + $this->operation->setBrowseNodeIds([ + Normalizer::formatId($id) + ]); + + $node = $this->prepare()->fetch(); + $node = Normalizer::applyArgs($node, [ + '{locale}' => $this->locale, + '{tag}' => $this->tag + ]); + + return (array)$node; + } + + /** + * Search node (Category). + * + * @access public + * @param string $keyword + * @param array $filter + * @return array + */ + public function searchNode(string $keyword, array $filter = []) : array + { + $id = Normalizer::formatId($keyword); + if ( Keyword::isASIN($id) || !Keyword::isBarcode($id) ) { + $id = $this->toNode($id, $filter); + } + return $this->getNode($id); + } + + /** + * Get item root category Id (Search Index). + * + * @access public + * @param string $keyword + * @param array $filter + * @return string + */ + public function getCategory(string $keyword, array $filter = []) : string + { + unset($filter['category']); + $this->setup('search')->filter($filter); + + $this->operation->setResources([ + 'BrowseNodeInfo.BrowseNodes.Ancestor' + ]); + + $this->operation->setItemCount(1); + $this->operation->setKeywords( + Normalizer::formatKeyword($keyword) + ); + + $item = $this->prepare()->fetch(); + $item = $item[0] ?? []; + + $category = $item['category'] ?? ''; + return Provider::getCategoryId($category, $this->locale); + } + + /** + * Search item category (Search Index). + * + * @access public + * @param string $keyword + * @param array $filter + * @return array + */ + public function searchCategory(string $keyword, array $filter = []) : array + { + $this->setup('search')->filter($filter); + + $this->operation->setResources([ + 'SearchRefinements' + ]); + + $this->operation->setItemCount(1); + $this->operation->setKeywords( + Normalizer::formatKeyword($keyword) + ); + + $item = $this->prepare()->fetch(); + return $item[0] ?? []; + } + + /** + * Get bestseller items (Id). + * + * @access public + * @param string $id + * @param int $count + * @param int $page + * @param array $filter + * @return array + */ + public function getBestseller(string $id, int $count = 5, int $page = 1, array $filter = []) : array + { + $id = Normalizer::formatId($id); + if ( !Keyword::isBarcode($id) ) { + return []; + } + + unset($filter['node']); + $filter['node'] = $id; + + $node = $this->searchNode($id); + if ( !$this->hasError() ) { + $id = $node['name']; + } + + $this->setup('search')->filter($filter); + + $this->operation->setResources( + $this->getDefaultResources([ + 'BrowseNodeInfo.BrowseNodes.SalesRank' + ]) + ); + + $this->operation->setItemCount($count)->setItemPage($page); + $this->operation->setKeywords($id); + + $items = $this->prepare()->fetch(); + return Normalizer::order($items, ['rank', 'title']); + } + + /** + * Search bestseller items (Keyword). + * + * @access public + * @param string $keyword + * @param int $count + * @param int $page + * @param array $filter + * @return array + */ + public function searchBestseller(string $keyword, int $count = 5, int $page = 1, array $filter = []) : array + { + $keyword = Normalizer::formatKeyword($keyword); + if ( Keyword::isBarcode($keyword) ) { + return []; + } + $filter['title'] = $keyword; + + $this->setup('search')->filter($filter); + + $this->operation->setResources( + $this->getDefaultResources([ + 'BrowseNodeInfo.BrowseNodes.SalesRank' + ]) + ); + + $this->operation->setItemCount($count)->setItemPage($page); + $this->operation->setKeywords($keyword); + + $items = $this->prepare()->fetch(); + return Normalizer::order($items, ['rank', 'title']); + } + + /** + * Get newest items. + * + * @access public + * @param string $keyword + * @param int $count + * @param int $page + * @param array $filter + * @return array + */ + public function getNewest(string $keyword, int $count = 5, int $page = 1, array $filter = []) : array + { + $keyword = Normalizer::formatKeyword($keyword); + $filter['sort'] = 'newest'; + $filter['title'] = $keyword; + return $this->search($keyword, $count, $page, $filter); + } + + /** + * Get cart (URL). + * + * @access public + * @param mixed $items + * @return string + */ + public function getCart($items) : string + { + $cart = new Cart(); + $cart->setLocale($this->locale); + $cart->setPartnerTag($this->tag); + + return $cart->set( + Normalizer::formatCart($items) + ); + } + + /** + * Get rating. + * + * @access public + * @param string $keyword + * @return array + */ + public function getRating(string $keyword) : array + { + $rating = new Rating($keyword, $this->locale, $this->tag); + return $rating->get(); + } + + /** + * Search rating. + * + * @access public + * @param string $keyword + * @param array $filter + * @return array + */ + public function searchRating(string $keyword, array $filter = []) : array + { + $id = Normalizer::formatId($keyword); + if ( !Keyword::isASIN($id) ) { + $keyword = $this->toASIN($keyword, $filter); + } + return $this->getRating($keyword); + } + + /** + * Enable geotargeting. + * + * @access public + * @param array $redirect + * @return object + */ + public function redirect(array $redirect) : self + { + $this->redirect = array_merge([ + 'tag' => $this->tag, + 'locale' => $this->locale + ], $redirect); + return $this; + } + + /** + * Get response error. + * + * @access public + * @return string + */ + public function getError() : string + { + return (string)$this->error; + } + + /** + * Check response error. + * + * @access public + * @return bool + */ + public function hasError() : bool + { + return (bool)$this->error; + } + + /** + * Setup operation. + * + * @access private + * @param string $operation + * @return object + */ + private function setup(string $operation) : self + { + switch ($operation) { + case 'get': + $this->operation = new GetItems(); + break; + + case 'search': + $this->operation = new SearchItems(); + break; + + case 'variation': + $this->operation = new GetVariations(); + break; + + case 'node': + $this->operation = new GetBrowseNodes(); + break; + } + + $this->operation->setPartnerTag($this->tag); + + return $this; + } + + /** + * Get default resources. + * + * @access private + * @param array $resources + * @return array + */ + private function getDefaultResources(array $resources = []) : array + { + return array_merge([ + 'BrowseNodeInfo.BrowseNodes.Ancestor', + 'Images.Primary.Large', + 'Images.Variants.Large', + 'Offers.Listings.Price', + 'Offers.Listings.Condition', + 'Offers.Listings.Promotions', + 'Offers.Listings.SavingBasis', + 'Offers.Listings.DeliveryInfo.IsAmazonFulfilled', + 'Offers.Listings.DeliveryInfo.IsFreeShippingEligible', + 'Offers.Listings.DeliveryInfo.IsPrimeEligible', + 'Offers.Listings.Availability.Message', + 'ItemInfo.ExternalIds', + 'ItemInfo.Features', + 'ItemInfo.Title' + ], $resources); + } + + /** + * Filter request. + * + * @access private + * @param array $filter + * @return object + */ + private function filter(array $filter) : self + { + if ( $this->isSearch() ) { + + $min = $filter['min'] ?? null; + if ( $min ) { + $this->operation->setMinPrice( + Normalizer::formatPrice($min) + ); + } + + $max = $filter['max'] ?? null; + if ( $max ) { + $this->operation->setMaxPrice( + Normalizer::formatPrice($max) + ); + } + + $available = $filter['available'] ?? null; + if ( $available === false ) { + $this->operation->setAvailability('IncludeOutOfStock'); + } + + $delivery = $filter['delivery'] ?? null; + if ( $delivery ) { + $this->operation->setDeliveryFlags( + Normalizer::formatDelivery($delivery) + ); + } + + $reviews = $filter['reviews'] ?? null; + $reviews = (int)$reviews; + if ( $reviews ) { + $this->operation->setMinReviewsRating($reviews); + } + + $saving = $filter['saving'] ?? null; + $saving = (int)$saving; + if ( $saving ) { + $this->operation->setMinSavingPercent($saving); + } + + $sort = $filter['sort'] ?? null; + $sort = (string)$sort; + if ( $sort ) { + $this->operation->setSortBy( + Normalizer::formatSort($sort) + ); + } + + $category = $filter['category'] ?? null; + $category = (string)$category; + if ( $category ) { + $this->operation->setSearchIndex( + Normalizer::formatCategory($category) + ); + } + + $brand = $filter['brand'] ?? null; + $brand = (string)$brand; + if ( $brand ) { + $this->operation->setBrand($brand); + } + + $node = $filter['node'] ?? null; + $node = (string)$node; + if ( $node ) { + $this->operation->setBrowseNodeId($node); + } + + $title = $filter['title'] ?? null; + $title = (string)$title; + if ( $title ) { + $this->operation->setTitle($title); + } + + $actor = $filter['actor'] ?? null; + $actor = (string)$actor; + if ( $actor ) { + $this->operation->setActor($actor); + } + + $artist = $filter['artist'] ?? null; + $artist = (string)$artist; + if ( $artist ) { + $this->operation->setArtist($artist); + } + + $author = $filter['author'] ?? null; + $author = (string)$author; + if ( $author ) { + $this->operation->setAuthor($author); + } + + } + + $condition = $filter['condition'] ?? null; + $condition = (string)$condition; + if ( $condition ) { + $this->operation->setCondition( + Normalizer::formatCondition($condition) + ); + } + + $merchant = $filter['merchant'] ?? null; + $merchant = (string)$merchant; + if ( $merchant ) { + $this->operation->setMerchant($merchant); + } + + $language = $filter['lang'] ?? null; + if ( $language ) { + $this->operation->setLanguages( + Normalizer::formatLanguage($language) + ); + } + + $currency = $filter['currency'] ?? null; + $currency = (string)$currency; + if ( $currency ) { + $this->operation->setCurrency( + Normalizer::formatCurrency($currency) + ); + } + + $marketplace = $filter['marketplace'] ?? null; + $marketplace = (string)$marketplace; + if ( $marketplace ) { + $this->operation->setMarketplace( + Normalizer::formatMarketplace($marketplace) + ); + } + + return $this; + } + + /** + * Set order. + * + * @access public + * @param mixed $order + * @return self + */ + public function order($order) : self + { + $this->order = $order; + return $this; + } + + /** + * Check search operation. + * + * @access private + * @return bool + */ + private function isSearch() : bool + { + $class = 'Apaapi\operations\SearchItems'; + return is_a($this->operation, $class, true); + } + + /** + * Check node operation. + * + * @access private + * @return bool + */ + private function isNode() : bool + { + $class = 'Apaapi\operations\GetBrowseNodes'; + return is_a($this->operation, $class, true); + } + + /** + * Prepare request. + * + * @access private + * @return object + */ + private function prepare() : self + { + $this->request = new Request($this->key, $this->secret); + $this->request->setLocale($this->locale)->setPayload($this->operation); + return $this; + } + + /** + * Fetch response. + * + * @access private + * @return array + */ + private function fetch() : array + { + $response = new Response($this->request, Response::NORMALIZE); + + $this->error = false; + if ( $response->hasError() ) { + $this->error = $response->getError(); + } + + return $response->get( + $this->redirect + ); + } +} diff --git a/src/includes/Cache.php b/src/includes/Cache.php new file mode 100644 index 0000000..15b83a6 --- /dev/null +++ b/src/includes/Cache.php @@ -0,0 +1,198 @@ + + * @link : https://jakiboy.github.io/apaapi/ + * @license : MIT + * + * This file if a part of Apaapi Lib. + */ + +declare(strict_types=1); + +namespace Apaapi\includes; + +use Apaapi\interfaces\RequestInterface; + +/** + * Apaapi built-in cache helper. + * For high-performance applications use a dedicated caching system like Memcached or Redis. + * @see https://webservices.amazon.com/paapi5/documentation/best-programming-practices.html + */ +final class Cache +{ + private const TTL = 3600; + private const SALT = '0RhuksYDF'; + private const EXT = 'db'; + + /** + * @access private + * @var int $ttl, Cache TTL + * @var string $salt, Cache salt + * @var string $ext, Cache extension + */ + private static $ttl; + private static $salt; + private static $ext; + + /** + * Set custom cache TTL. + * + * @access public + * @param int $ttl + * @return void + */ + public static function setTtl(int $ttl = self::TTL) + { + self::$ttl = $ttl; + } + + /** + * Set hash salt. + * + * @access public + * @param string $salt + * @return void + */ + public static function setSalt(string $salt = self::SALT) + { + self::$salt = $salt; + } + + /** + * Set cache extension. + * + * @access public + * @param string $ext + * @return void + */ + public static function setExt(string $ext = self::EXT) + { + self::$ext = $ext; + } + + /** + * Get request cache key. + * + * @access public + * @param RequestInterface $request + * @return string + */ + public static function getKey(RequestInterface $request) : string + { + $params = $request->getParams(); + $payload = $params['payload'] ?? ''; + return self::generateKey((string)$payload); + } + + /** + * Generate cache key. + * + * @access public + * @param string $item + * @return string + */ + public static function generateKey(string $item) : string + { + self::init(); + $item = self::$salt . $item; + return hash('sha256', $item); + } + + /** + * Get cache value. + * + * @access public + * @param string $key + * @return mixed + */ + public static function get(string $key) + { + if ( self::isCached($key) ) { + $file = self::getFile($key); + $value = @file_get_contents($file); + return unserialize($value); + } + return false; + } + + /** + * Set cache value. + * + * @access public + * @param string $key + * @param mixed $value + * @return bool + */ + public static function set(string $key, $value) : bool + { + self::init(); + if ( $key == self::$salt ) { + return false; + } + $file = self::getFile($key); + $value = serialize($value); + return (bool)@file_put_contents($file, $value); + } + + /** + * Check cache key. + * + * @access private + * @param string $key + * @return bool + */ + private static function isCached(string $key) : bool + { + self::init(); + $file = self::getFile($key); + + if ( @file_exists($file) ) { + $time = time() - self::$ttl; + if ( $time < filemtime($file) ) { + return true; + + } else { + @unlink($file); + } + } + + return false; + } + + /** + * Get cache file. + * + * @access private + * @param string $key + * @return string + */ + private static function getFile(string $key) : string + { + self::init(); + $path = sys_get_temp_dir(); + $path = "{$path}/{$key}." . self::$ext; + return Normalizer::formatPath($path); + } + + /** + * Init cache settings. + * + * @access private + * @return void + */ + private static function init() + { + if ( !self::$ttl ) { + self::setTTL(); + } + if ( !self::$salt ) { + self::setSalt(); + } + if ( !self::$ext ) { + self::setExt(); + } + } +} diff --git a/src/includes/Client.php b/src/includes/Client.php new file mode 100644 index 0000000..d117caa --- /dev/null +++ b/src/includes/Client.php @@ -0,0 +1,391 @@ + + * @link : https://jakiboy.github.io/apaapi/ + * @license : MIT + * + * This file if a part of Apaapi Lib. + */ + +declare(strict_types=1); + +namespace Apaapi\includes; + +use Apaapi\interfaces\ClientInterface; +use Apaapi\exceptions\RequestException; + +/** + * Apaapi request client wrapper class. + */ +class Client implements ClientInterface +{ + /** + * @access private + */ + private const CURL = 'curl'; + private const STREAM = 'stream'; + + /** + * @access protected + * @var string $endpoint, Request URL + * @var string $method, Request method + * @var int $timeout, Request timeout + * @var int $redirect, Request redirect + * @var string $encoding, Request encoding + * @var array $params, Request params + * @var CurlHandle|ressource $handler, Request handler + * @var string $gateway, curl|stream + * @var mixed $response, Request response content + * @var bool $throwable, Request exception throw + * @var string $error, Response error content + * @var int $code, Response code + */ + protected $endpoint; + protected $method = 'POST'; + protected $timeout = 30; + protected $redirect = false; + protected $encoding = false; + protected $params; + protected $handler; + protected $gateway; + protected $throwable = false; + protected $response = false; + protected $error = false; + protected $code = 200; + + /** + * @inheritdoc + */ + public function __construct(string $endpoint, array $params = [], bool $throwable = false) + { + $this->endpoint = $endpoint; + $this->params = $params; + $this->throwable = $throwable; + $this->setGateway(); + } + + /** + * @inheritdoc + */ + public function setMethod(string $method) : self + { + $this->method = strtoupper($method); + return $this; + } + + /** + * @inheritdoc + */ + public function setTimeout(int $timeout) : self + { + $this->timeout = $timeout; + return $this; + } + + /** + * @inheritdoc + */ + public function setRedirect(int $redirect) : self + { + $this->redirect = $redirect; + return $this; + } + + /** + * @inheritdoc + */ + public function setEncoding(string $encoding) : self + { + $this->encoding = $encoding; + return $this; + } + + /** + * @inheritdoc + */ + public function getResponse() : string + { + $this->setHandler(); + $this->send(); + + if ( $this->hasError() ) { + return $this->error; + } + + return (string)$this->response; + } + + /** + * @inheritdoc + */ + public function getCode() : int + { + return $this->code; + } + + /** + * @inheritdoc + */ + public function close() + { + if ( $this->isCurl() ) { + curl_close($this->handler); + + } elseif ( $this->isStream() ) { + fclose($this->handler); + } + } + + /** + * Check curl status. + * + * @access public + * @return bool + */ + public static function hasCurl() : bool + { + return function_exists('curl_init'); + } + + /** + * Check stream status. + * + * @access public + * @return bool + */ + public static function hasStream() : bool + { + $val = intval(ini_get('allow_url_fopen')); + return (bool)$val; + } + + /** + * Set HTTP handler. + * + * @access protected + * @return void + */ + protected function setHandler() + { + if ( $this->isCurl() ) { + + $this->handler = curl_init(); + + curl_setopt($this->handler, CURLOPT_URL, $this->endpoint); + curl_setopt($this->handler, CURLOPT_RETURNTRANSFER, true); + curl_setopt($this->handler, CURLOPT_HTTPHEADER, $this->getRequestHeader()); + curl_setopt($this->handler, CURLOPT_SSL_VERIFYPEER, $this->isSsl()); + curl_setopt($this->handler, CURLOPT_TIMEOUT, $this->timeout); + + if ( $this->encoding !== false ) { + curl_setopt($this->handler, CURLOPT_ENCODING, $this->encoding); + } + + if ( $this->redirect ) { + curl_setopt($this->handler, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($this->handler, CURLOPT_MAXREDIRS, $this->redirect); + } + + if ( $this->isPost() ) { + curl_setopt($this->handler, CURLOPT_POSTFIELDS, $this->getRequestPayload()); + curl_setopt($this->handler, CURLOPT_POST, true); + } + + } elseif ( $this->isStream() ) { + $this->handler = stream_context_create([ + 'http' => [ + 'method' => $this->method, + 'header' => $this->getRequestHeader(), + 'content' => $this->getRequestPayload(), + 'timeout' => $this->timeout + ] + ]); + } + } + + /** + * Check client error. + * + * @access protected + * @return bool + */ + protected function hasError() : bool + { + return (bool)$this->error; + } + + /** + * Send request. + * + * @access protected + * @return void + */ + protected function send() + { + if ( $this->isCurl() ) { + $this->sendCurl(); + + } elseif ( $this->isStream() ) { + $this->sendStream(); + } + } + + /** + * Check SSL status. + * + * @access protected + * @return bool + */ + protected function isSsl() : bool + { + if ( isset($_SERVER['HTTPS']) ) { + if ( strtolower($_SERVER['HTTPS']) === 'on' ) { + return true; + } + if ( $_SERVER['HTTPS'] === '1' ) { + return true; + } + + } elseif ( isset($_SERVER['SERVER_PORT']) ) { + return ($_SERVER['SERVER_PORT'] === '443'); + } + return false; + } + + /** + * Set client gateway. + * + * @access protected + * @return void + * @throws RequestException + */ + protected function setGateway() + { + if ( !self::hasCurl() && !self::hasStream() ) { + $this->code = 0; + $this->error = RequestException::invalidClient(); + if ( $this->throwable ) { + throw new RequestException($this->error); + } + + } else { + if ( self::hasCurl() ) { + $this->gateway = self::CURL; + + } elseif ( self::hasStream() ) { + $this->gateway = self::STREAM; + } + } + } + + /** + * Get request payload. + * + * @access protected + * @return string + */ + protected function getRequestPayload() : string + { + $payload = $this->params['payload'] ?? ''; + return (string)$payload; + } + + /** + * Get request header. + * + * @access protected + * @return array + */ + protected function getRequestHeader() : array + { + $header = $this->params['header'] ?? ''; + if ( is_array($header)) { + return $header; + } + $header = explode("\r\n", (string)$header); + return ($header) ? $header : []; + } + + /** + * Send curl request. + * + * @access protected + * @return void + */ + protected function sendCurl() + { + $this->response = curl_exec($this->handler); + $info = curl_getinfo($this->handler); + $this->code = $info['http_code'] ?? 0; + + if ( curl_errno($this->handler) ) { + $this->code = 0; + $this->error = curl_error($this->handler); + } + + if ( $this->response && $this->code !== 200 ) { + $this->error = $this->response; + $this->response = false; + } + } + + /** + * Send stream request. + * + * @access protected + * @return void + */ + protected function sendStream() + { + $this->response = @file_get_contents($this->endpoint, false, $this->handler); + $headers = @get_headers($this->endpoint, false, $this->handler); + + if ( $headers ) { + $status = $headers[0] ?? ''; + $this->code = (int)substr($status, 9, 3); + if ( !$this->response || $this->code !== 200 ) { + $this->error = $status; + } + + } else { + $this->code = 0; + $this->error = 'Failed to open stream'; + } + } + + /** + * Check curl gateway. + * + * @access private + * @return bool + */ + private function isCurl() : bool + { + return ($this->gateway == self::CURL); + } + + /** + * Check stream gateway. + * + * @access private + * @return bool + */ + private function isStream() : bool + { + return ($this->gateway == self::STREAM); + } + + /** + * Check post method. + * + * @access private + * @return bool + */ + private function isPost() : bool + { + return ($this->method == 'POST'); + } +} diff --git a/src/includes/Geotargeting.php b/src/includes/Geotargeting.php new file mode 100644 index 0000000..2eaf13b --- /dev/null +++ b/src/includes/Geotargeting.php @@ -0,0 +1,359 @@ + + * @link : https://jakiboy.github.io/apaapi/ + * @license : MIT + * + * This file if a part of Apaapi Lib. + */ + +declare(strict_types=1); + +namespace Apaapi\includes; + +/** + * Apaapi built-in geotargeting helper. + * For high-performance applications use MaxMind GeoIP2 database. + * @see https://dev.maxmind.com/geoip + */ +final class Geotargeting +{ + /** + * @access private + * @var string $visitorKey, Cache key + * @var array $exception, Exception IP addresses + * @var bool $redirectNotFound, Redirect 404 result + */ + private static $visitorKey = '--visitor-id'; + private static $exception = ['::1', '127.0.0.1', '0.0.0.0']; + private static $redirectNotFound = false; + + /** + * @access private + * @var bool $isDetected, Geotargeting status + * @var array $api, Geotargeting APIs + * @var array $target, Geotargeting partner tags + * @var mixed $code, External country code + * @var string $tag, Current partner tag + * @var string $locale, Current locale + * @var array $redirect, Redirection target + */ + private $isDetected = false; + private $api = []; + private $target = []; + private $code = false; + private $tag; + private $locale; + private $redirect = []; + + /** + * Set redirection args. + * + * @param array $args + */ + public function __construct(array $args) + { + $this->api = $args['api'] ?? []; + $this->target = $args['target'] ?? []; + $this->tag = $args['tag'] ?? ''; + $this->locale = $args['locale'] ?? ''; + $this->code = $args['code'] ?? ''; + + $this->api = array_merge([ + 'ip' => ['address' => null, 'param' => null], + 'geo' => ['address' => null, 'param' => null] + ], $this->api); + + $this->detect(); + } + + /** + * Get redirected response data. + * + * @access private + * @param array $data + * @return array + */ + public function get(array $data) : array + { + if ( $this->isDetected ) { + $data = array_map(function($item) { + return self::redirect($item); + }, $data); + } + return $data; + } + + /** + * Set visitor key. + * + * @access public + * @param string $visitorKey + * @return void + */ + public static function setVisitorKey(string $visitorKey) + { + self::$visitorKey = $visitorKey; + } + + /** + * Set exception IP addresses. + * + * @access public + * @param array $exception + * @return void + */ + public static function setException(array $exception) + { + self::$exception = $exception; + } + + /** + * Redirect 404 result. + * + * @access public + * @return void + */ + public static function redirectNotFound() + { + self::$redirectNotFound = true; + } + + /** + * Redirect item. + * + * @access private + * @param array $item + * @return array + */ + private function redirect(array $item) : array + { + if ( isset($item['url']) ) { + + $host = Provider::HOST; + $from = str_replace('{locale}', $this->locale, $host); + $to = str_replace('{locale}', $this->redirect['tld'], $host); + + $item['url'] = str_replace([ + $from, + "?tag={$this->tag}" + ], [ + $to, + "?tag={$this->redirect['tag']}" + ], $item['url']); + + if ( self::$redirectNotFound ) { + + $client = new Client($item['url'], [ + 'header' => Provider::generateHeader( + $this->redirect['tld'] + ) + ]); + + $client->setMethod('GET') + ->setRedirect(2) + ->setTimeout(0); + + $client->getResponse(); + $client->close(); + + if ( $client->getCode() == 404 ) { + + $title = $item['title'] ?? ''; + $keyword = substr($title, 0, 20); + $keyword = preg_replace('/[^a-zA-Z0-9\s]/', '', $keyword); + $keyword = urlencode($keyword); + + $url = "{$to}/s?k={$keyword}&tag={$this->redirect['tag']}"; + $url = "{$url}&linkCode=ll2"; + $item['url'] = $url; + + } + } + } + + return $item; + } + + /** + * Detect redirection target. + * + * @access private + * @return void + */ + private function detect() + { + $code = $this->getCountryCode(); + if ( $this->target ) { + if ( isset($this->target[$code]) && !empty($this->target[$code]) ) { + $this->isDetected = true; + $this->redirect = [ + 'tld' => Normalizer::formatTLD($code), + 'tag' => $this->target[$code] + ]; + } + + } else { + $this->isDetected = false; + } + } + + /** + * Get current country code. + * + * @access private + * @return string + */ + private function getCountryCode() : string + { + if ( $this->code ) { + return $this->code; + } + + // Get visitor IP + if ( !($ip = $this->getIp()) ) { + $ip = '0.0.0.0'; + } + + // Get IP from external API + if ( in_array($ip, self::$exception) ) { + $ip = $this->getApiAddressIp(); + } + + // Get code from external API + return $this->getApiCountryCode($ip); + } + + /** + * Get current country code using API. + * + * @access private + * @param string $ip + * @return string + */ + private function getApiCountryCode(string $ip = '0.0.0.0') : string + { + $code = 'us'; + $address = $this->api['geo']['address'] ?? ''; + $param = $this->api['geo']['param'] ?? ''; + + if ( !$address || !$param ) { + return $code; + } + + $key = Cache::generateKey("geo-{$this->getVisitorId()}"); + if ( !($code = Cache::get($key)) ) { + + $address = str_replace('{ip}', $ip, $address); + $client = new Client($address); + $client->setMethod('GET')->setTimeout(5); + $response = $client->getResponse(); + $client->close(); + + if ( $client->getCode() == 200 ) { + $response = Normalizer::decode($response); + $code = $response[$param] ?? ''; + } + + Cache::set($key, $code); + + } + + $code = (string)$code; + return Normalizer::formatCountryCode($code); + } + + /** + * Get current IP address using API. + * + * @access private + * @return string + */ + private function getApiAddressIp() : string + { + $ip = '0.0.0.0'; + $address = $this->api['ip']['address'] ?? ''; + $param = $this->api['ip']['param'] ?? ''; + + if ( !$address || !$param ) { + return $ip; + } + + $key = Cache::generateKey("ip-{$this->getVisitorId()}"); + if ( !($ip = Cache::get($key)) ) { + + $client = new Client($address); + $client->setMethod('GET')->setTimeout(5); + $response = $client->getResponse(); + $client->close(); + + if ( $client->getCode() == 200 ) { + $response = Normalizer::decode($response); + $ip = $response[$param] ?? ''; + } + + Cache::set($key, $ip); + + } + + return (string)$ip; + } + + /** + * Get remote IP address. + * + * @access private + * @return string + */ + private function getIp() : string + { + if ( isset($_SERVER['HTTP_X_FORWARDED_FOR']) ) { + return $this->parseIp($_SERVER['HTTP_X_FORWARDED_FOR']); + + } elseif ( isset($_SERVER['HTTP_X_REAL_IP']) ) { + return $this->parseIp($_SERVER['HTTP_X_REAL_IP']); + + } elseif ( isset($_SERVER['HTTP_CF_CONNECTING_IP']) ) { + return $this->parseIp($_SERVER['HTTP_CF_CONNECTING_IP']); + } + + $ip = $_SERVER['REMOTE_ADDR'] ?? ''; + return $this->parseIp($ip); + } + + /** + * Parse remote IP address. + * + * @access private + * @param string $ip + * @return string + */ + private function parseIp(string $ip) : string + { + $ip = stripslashes($ip); + $ip = preg_split('/,/', $ip, -1, 0); + $ip = (string)current($ip); + return trim($ip); + } + + /** + * Get visitor ID. + * + * @access private + * @return string + */ + private function getVisitorId() : string + { + if ( isset($_COOKIE[self::$visitorKey]) ) { + return $_COOKIE[self::$visitorKey]; + } + + $visitorId = uniqid(); + setcookie(self::$visitorKey, $visitorId, time() + (86400 * 30), '/'); + + return $visitorId; + } +} diff --git a/src/includes/Keyword.php b/src/includes/Keyword.php new file mode 100644 index 0000000..9b90dad --- /dev/null +++ b/src/includes/Keyword.php @@ -0,0 +1,171 @@ + + * @link : https://jakiboy.github.io/apaapi/ + * @license : MIT + * + * This file if a part of Apaapi Lib. + */ + +declare(strict_types=1); + +namespace Apaapi\includes; + +final class Keyword +{ + /** + * Check GTIN (EAN, UPC, ISBN). + * + * /^(?:\d{8,14}|(?:\d{3}-)?\d{9}[\dX])$/ + * + * @access public + * @param string $keyword + * @return bool + */ + public static function isGTIN(string $keyword) : bool + { + return (bool)preg_match('/^(?:\d{8,14}|(?:\d{3}-)?\d{9}[\dX])$/', $keyword); + } + + /** + * Check EAN (EAN-8, EAN-13). + * + * /^\d{7}\d$/ + * /^\d{12}\d$/ + * + * @access public + * @param string $keyword + * @return bool + */ + public static function isEAN(string $keyword) : bool + { + if ( preg_match('/^\d{7}\d$/', $keyword) ) { + return true; + + } elseif ( preg_match('/^\d{12}\d$/', $keyword) ) { + return true; + } + return false; + } + + /** + * Check UPC (UPC-E, UPC-A). + * + * /^\d{6,8}$/ + * /^\d{12}$/ + * + * @access public + * @param string $keyword + * @return bool + */ + public static function isUPC(string $keyword) : bool + { + if ( preg_match('/^\d{6,8}$/', $keyword) ) { + return true; + + } elseif ( preg_match('/^\d{12}$/', $keyword) ) { + return true; + } + return false; + } + + /** + * Check ISBN (ISBN-10, ISBN-13). + * + * /^(?:\d{9}[\dX]|\d{13})$/ + * + * @access public + * @param string $keyword + * @return bool + */ + public static function isISBN(string $keyword) : bool + { + return (bool)preg_match('/^(?:\d{9}[\dX]|\d{13})$/', $keyword); + } + + /** + * Check ASIN. + * + * /^B0[A-Z0-9]{8}$/ + * + * @access public + * @param string $keyword + * @return bool + */ + public static function isASIN(string $keyword) : bool + { + return (bool)preg_match('/^B0[A-Z0-9]{8}$/', $keyword); + } + + /** + * Check barcode (GTIN, ASIN). + * + * @access public + * @param string $keyword + * @return bool + */ + public static function isBarcode(string $keyword) : bool + { + if ( self::isGTIN($keyword) || self::isASIN($keyword) ) { + return true; + } + return false; + } + + /** + * Parse ISBN. + * + * /(?:\d{9}[\dX]|\d{13})/ + * + * @access public + * @param string $keyword + * @return string + */ + public static function parseISBN(string $keyword) : string + { + preg_match('/(?:\d{9}[\dX]|\d{13})/', $keyword, $match); + if ( $match ) { + $keyword = $match[0] ?? ''; + } + return (self::isISBN($keyword)) ? $keyword : ''; + } + + /** + * Parse ASIN. + * + * /B0[A-Z0-9]{8}/ + * + * @access public + * @param string $keyword + * @return string + */ + public static function parseASIN(string $keyword) : string + { + preg_match('/B0[A-Z0-9]{8}/', $keyword, $match); + if ( $match ) { + $keyword = $match[0] ?? ''; + } + return (self::isASIN($keyword)) ? $keyword : ''; + } + + /** + * Parse barcode (GTIN, ASIN). + * + * @access public + * @param string $keyword + * @return string + */ + public static function parseBarcode(string $keyword) : string + { + if ( ($isbn = self::parseISBN($keyword)) ) { + $keyword = $isbn; + + } elseif ( ($asin = self::parseASIN($keyword)) ) { + $keyword = $asin; + } + return $keyword; + } +} diff --git a/src/includes/Normalizer.php b/src/includes/Normalizer.php new file mode 100644 index 0000000..afe8bc7 --- /dev/null +++ b/src/includes/Normalizer.php @@ -0,0 +1,1115 @@ + + * @link : https://jakiboy.github.io/apaapi/ + * @license : MIT + * + * This file if a part of Apaapi Lib. + */ + +declare(strict_types=1); + +namespace Apaapi\includes; + +/** + * Apaapi data normalizer. + */ +final class Normalizer +{ + /** + * @access private + * @var int $limit, List limit + * @var bool $format, String format + * @var bool $order, Data order + */ + private static $limit = 5; + private static $format = false; + private static $order = true; + + /** + * Set lists limit. + * + * @access public + * @param int $limit + * @return void + */ + public static function limit(int $limit) + { + self::$limit = $limit; + } + + /** + * Keep default string format. + * + * @access public + * @return void + */ + public static function noFormat() + { + self::$format = true; + } + + /** + * Disable order. + * + * @access public + * @return void + */ + public static function noOrder() + { + self::$order = false; + } + + /** + * Get normalized response data. + * + * @access public + * @param array $data + * @param string $operation + * @return array + */ + public static function get(array $data, string $operation) : array + { + $data = self::parse($data, $operation); + if ( isset($data['node']) ) { + return self::normalizeNode($data['node']); + } + if ( isset($data['category']) ) { + return self::normalizeCategory($data['category']); + return $data['category']; + } + return array_map(function($item) { + return self::normalize($item); + }, $data); + } + + /** + * Parse response data items. + * + * @access public + * @param array $data + * @param string $operation + * @return array + */ + public static function parse(array $data, string $operation) : array + { + switch ($operation) { + case 'GetItems': + return self::parseItem($data); + break; + + case 'SearchItems': + return self::parseSearch($data); + break; + + case 'GetVariations': + return self::parseVariation($data); + break; + + case 'GetBrowseNodes': + return self::parseNode($data); + break; + } + return $data; + } + + /** + * Format single id. + * + * @access public + * @param string $id + * @return string + */ + public static function formatId(string $id) : string + { + $id = self::stripSpace( + strtoupper($id) + ); + if ( self::isUrl($id) ) { + return Keyword::parseBarcode($id); + } + return $id; + } + + /** + * Format ids. + * + * @access public + * @param mixed $ids + * @return array + */ + public static function formatIds($ids) : array + { + if ( is_string($ids) ) { + $ids = self::stripSpace( + strtoupper($ids) + ); + $ids = explode(',', $ids); + } + + $ids = array_map(function($id) { + return self::formatId($id); + }, (array)$ids); + + return $ids; + } + + /** + * Format single keyword. + * + * @access public + * @param string $keyword + * @param bool $single + * @return string + */ + public static function formatKeyword(string $keyword) : string + { + $keyword = trim($keyword); + + if ( strpos($keyword, ',') !== false ) { + $keyword = explode(',', $keyword); + $keyword = (string)array_shift($keyword); + } + + if ( self::isUrl($keyword) ) { + return Keyword::parseBarcode($keyword); + } + + return $keyword; + } + + /** + * Format keywords. + * + * @access public + * @param mixed $keywords + * @return string + */ + public static function formatKeywords($keywords) : string + { + if ( !is_array($keywords) ) { + $keywords = trim((string)$keywords); + $keywords = explode(',', $keywords); + } + + $keywords = array_map(function($keyword) { + return self::formatKeyword($keyword); + }, $keywords); + + return implode(', ', $keywords); + } + + /** + * Format cart items. + * + * @access public + * @param mixed $items + * @return array + */ + public static function formatCart($items) : array + { + if ( is_string($items) ) { + $items = self::formatIds($items); + } + + $items = (array)$items; + $cart = []; + + foreach ($items as $key => $item) { + if ( is_int($key) ) { + $item = self::formatId((string)$item); + $cart[$item] = '1'; + + } else { + $key = self::formatId($key); + $cart[$key] = (string)intval($item); + } + } + + return $cart; + } + + /** + * Format filter price. + * + * @access public + * @param mixed $price + * @return int + */ + public static function formatPrice($price) : int + { + $price = (string)$price; + + if ( strpos($price, '.') === false ) { + $price = "{$price}00"; + + } else { + $price = str_replace('.', '', $price); + } + + return (int)$price; + } + + /** + * Format filter delivery. + * + * @access public + * @param mixed $delivery + * @return array + */ + public static function formatDelivery($delivery) : array + { + if ( is_string($delivery) ) { + $delivery = self::stripSpace( + strtolower($delivery) + ); + $delivery = explode(',', $delivery); + } + + $delivery = (array)$delivery; + $flags = []; + + $delivery = array_map(function($item) { + return trim($item); + }, $delivery); + + foreach ($delivery as $key => $item) { + if ( $item === 'global' ) { + $flags[] = 'AmazonGlobal'; + + } elseif ( $item === 'free' ) { + $flags[] = 'FreeShipping'; + + } elseif ( $item === 'fulfilled' ) { + $flags[] = 'FulfilledByAmazon'; + + } elseif ( $item === 'prime' ) { + $flags[] = 'Prime'; + } + } + + return $flags; + } + + /** + * Format filter sort. + * + * @access public + * @param string $sort + * @return string + */ + public static function formatSort(string $sort) : string + { + $sort = self::stripSpace( + strtolower($sort) + ); + + if ( $sort == 'reviews' ) { + $sort = 'AvgCustomerReviews'; + + } elseif ( $sort == 'featured' ) { + $sort = 'Featured'; + + } elseif ( $sort == 'newest' ) { + $sort = 'NewestArrivals'; + + } elseif ( $sort == 'highest' ) { + $sort = 'Price:HighToLow'; + + } elseif ( $sort == 'lowest' ) { + $sort = 'Price:LowToHigh'; + + } elseif ( $sort == 'relevance' ) { + $sort = 'Relevance'; + } + + return $sort; + } + + /** + * Format filter condition. + * + * @access public + * @param string $condition + * @return string + */ + public static function formatCondition(string $condition) : string + { + $condition = self::stripSpace( + strtolower($condition) + ); + return ucfirst($condition); + } + + /** + * Format filter category. + * + * @access public + * @param string $category + * @return string + */ + public static function formatCategory(string $category) : string + { + return self::stripSpace($category); + } + + /** + * Format filter language. + * + * @access public + * @param mixed $language + * @return array + */ + public static function formatLanguage($language) : array + { + return (array)$language; + } + + /** + * Format filter currency. + * + * @access public + * @param string $currency + * @return string + */ + public static function formatCurrency(string $currency) : string + { + return $currency; + } + + /** + * Format filter marketplace. + * + * @access public + * @param string $marketplace + * @return string + */ + public static function formatMarketplace(string $marketplace) : string + { + return $marketplace; + } + + /** + * Format request locale. + * + * @access public + * @param string $locale + * @return mixed + */ + public static function formatLocale(string $locale) + { + $locale = self::stripSpace( + strtolower($locale) + ); + if ( ($l = explode('_', $locale)) ) { + $locale = $l[0]; + } + if ( !in_array($locale, Provider::getLocales()) ) { + $locale = false; + } + return $locale; + } + + /** + * Format response error. + * + * @access public + * @param string $body + * @return string + */ + public static function formatError(string $body) : string + { + $error = false; + if ( ($data = self::decode($body)) ) { + $error = $data['Errors'][0]['Message'] ?? ''; + $regex = [ + '/\s(? 'asc']; + } + + $order = (array)$order; + + foreach ($order as $key => $dir) { + if ( is_int($key) ) { + unset($order[$key]); + $key = (string)$dir; + $dir = 'asc'; + + } else { + $key = (string)$key; + $dir = (string)$dir; + } + $order[$key] = $dir; + } + + return $order; + } + + /** + * Decode data. + * + * @access public + * @param string $data + * @return array + */ + public static function decode(string $data) : array + { + return (array)@json_decode($data, true); + } + + /** + * Format country code (Fix 3 DIGIT ISO), + * Lowercased ISO (3166‑1 alpha‑2). + * + * @access private + * @param string $code + * @return string + * @see https://countrycode.org/ + */ + public static function formatCountryCode(string $code) : string + { + return substr(strtolower($code), 0, 2); + } + + /** + * Format TLD from country code. + * + * @access public + * @param string $code + * @return string + */ + public static function formatTLD(string $code) : string + { + switch ($code) { + case 'au': + return 'com.au'; + break; + case 'be': + return 'com.be'; + break; + case 'br': + return 'com.br'; + break; + case 'jp': + return 'co.jp'; + break; + case 'mx': + return 'com.mx'; + break; + case 'tr': + return 'com.tr'; + break; + case 'uk': + case 'gb': + return 'co.uk'; + break; + case 'us': + return 'com'; + break; + default: + return $code; + break; + }; + } + + /** + * Apply dynamic args recursively. + * + * @access public + * @param mixed $data + * @param array $args + * @return mixed + */ + public static function applyArgs($data, array $args) + { + $recursive = function ($item) use (&$recursive, $args) { + if ( is_array($item) ) { + foreach ($item as $key => $value) { + $item[$key] = $recursive($value); + } + + } elseif ( is_string($item) ) { + $item = str_replace( + array_keys($args), + array_values($args), + $item + ); + } + return $item; + }; + return $recursive($data); + } + + /** + * Order data. + * + * @access public + * @param array $data + * @param mixed $orderby + * @param string $order + * @param bool $preserve (keys) + * @return array + */ + public static function order(array $data, $orderby, string $order = 'asc') : array + { + if ( !self::$order ) { + return $data; + } + + $orderby = self::formatOrderBy($orderby); + + foreach ($orderby as $key => $dir) { + $orderby[$key] = ('desc' === strtolower($dir)) ? 'desc' : 'asc'; + } + + $sort = function($a, $b) use ($orderby) { + + $a = (array)$a; + $b = (array)$b; + + foreach ($orderby as $key => $dir) { + + if ( !isset($a[$key]) || !isset($b[$key]) ) { + continue; + } + + if ($a[$key] == 0) return 1; + if ($b[$key] == 0) return -1; + + if ( $a[$key] == $b[$key] ) { + continue; + } + + $val = ('desc' === $dir) ? [1, -1] : [-1, 1]; + + if ( is_numeric($a[$key]) && is_numeric($b[$key]) ) { + return ($a[$key] < $b[$key]) ? $val[0] : $val[1]; + } + + return 0 > strcmp($a[$key], $b[$key]) ? $val[0] : $val[1]; + } + + return 0; + }; + + usort($data, $sort); + return $data; + } + + /** + * Normalize item. + * + * @access private + * @param array $item + * @return array + */ + private static function normalize(array $item) : array + { + return [ + 'asin' => self::sanitizeASIN($item), + 'ean' => self::sanitizeEAN($item), + 'node' => self::sanitizeNode($item), + 'root' => self::sanitizeRoot($item), + 'rank' => self::sanitizeRank($item), + 'title' => self::sanitizeTitle($item), + 'category' => self::sanitizeCategory($item), + 'url' => self::sanitizeUrl($item), + 'image' => self::sanitizeImage($item), + 'gallery' => self::sanitizeGallery($item), + 'price' => self::sanitizePrice($item), + 'discount' => self::sanitizeDiscount($item), + 'discounted' => self::sanitizeDiscounted($item), + 'percent' => self::sanitizePercent($item), + 'currency' => self::sanitizeCurrency($item), + 'availability' => self::sanitizeAvailability($item), + 'shipping' => self::sanitizeShipping($item), + 'features' => self::sanitizeFeatures($item) + ]; + } + + /** + * Normalize node. + * + * @access private + * @param array $data + * @return array + */ + private static function normalizeNode(array $data) : array + { + $node = []; + if ( ($id = $data['Id'] ?? false) ) { + $node = [ + 'id' => $id, + 'name' => $data['ContextFreeName'] ?? '', + 'root' => $data['IsRoot'] ?? false, + 'url' => Provider::getNodeUrl($id), + 'ancestor' => self::parseAncestor($data), + 'children' => self::parseChildren($data) + ]; + } + return $node; + } + + /** + * Normalize category. + * + * @access private + * @param array $data + * @return array + */ + private static function normalizeCategory(array $data) : array + { + $categories = []; + foreach ($data as $category) { + if ( ($id = $category['Id'] ?? false) ) { + $categories[] = [ + 'id' => $id, + 'name' => $category['DisplayName'] ?? '' + ]; + } + } + return $categories; + } + + /** + * Parse node. + * + * @access private + * @param array $data + * @return array + */ + private static function parseNode(array $data) : array + { + $data = $data['BrowseNodesResult']['BrowseNodes'][0] ?? []; + return ['node' => $data]; + } + + /** + * Parse item. + * + * @access private + * @param array $data + * @return array + */ + private static function parseItem(array $data) : array + { + return $data['ItemsResult']['Items'] ?? []; + } + + /** + * Parse search. + * + * @access private + * @param array $data + * @return array + */ + private static function parseSearch(array $data) : array + { + if ( isset($data['SearchResult']['SearchRefinements']) ) { + $data = $data['SearchResult']['SearchRefinements']; + $data = $data['SearchIndex']['Bins'] ?? []; + return ['category' => $data]; + } + return $data['SearchResult']['Items'] ?? []; + } + + /** + * Parse variation. + * + * @access private + * @param array $data + * @return array + */ + private static function parseVariation(array $data) : array + { + return $data['VariationsResult']['Items'] ?? []; + } + + /** + * Parse node ancestor. + * + * @access private + * @param array $item + * @return array + */ + private static function parseAncestor(array $item) : array + { + $ancestor = []; + if ( ($id = $item['Ancestor']['Id'] ?? false) ) { + $ancestor = [ + 'id' => $id, + 'name' => $item['Ancestor']['ContextFreeName'] ?? '', + 'url' => Provider::getNodeUrl($id) + ]; + } + return $ancestor; + } + + /** + * Parse node children. + * + * @access private + * @param array $item + * @return array + */ + private static function parseChildren(array $item) : array + { + $children = []; + $item = $item['Children'] ?? []; + foreach ($item as $child) { + if ( ($id = $child['Id']) ) { + $children[] = [ + 'id' => $id, + 'name' => $child['ContextFreeName'] ?? '', + 'url' => Provider::getNodeUrl($id) + ]; + } + } + return $children; + } + + /** + * Sanitize item EAN. + * + * @access private + * @param array $item + * @return string + */ + private static function sanitizeEAN(array $item) : string + { + return $item['ItemInfo']['ExternalIds']['EANs']['DisplayValues'][0] ?? ''; + } + + /** + * Sanitize item ASIN. + * + * @access private + * @param array $item + * @return string + */ + private static function sanitizeASIN(array $item) : string + { + return $item['ASIN'] ?? ''; + } + + /** + * Sanitize item title. + * + * @access private + * @param array $item + * @return string + */ + private static function sanitizeTitle(array $item) : string + { + $title = $item['ItemInfo']['Title']['DisplayValue'] ?? ''; + return self::formatString($title); + } + + /** + * Sanitize item url. + * + * @access private + * @param array $item + * @return string + */ + private static function sanitizeUrl(array $item) : string + { + return $item['DetailPageURL'] ?? ''; + } + + /** + * Sanitize item image. + * + * @access private + * @param array $item + * @return string + */ + private static function sanitizeImage(array $item) : string + { + return $item['Images']['Primary']['Large']['URL'] ?? ''; + } + + /** + * Sanitize item gallery. + * + * @access private + * @param array $item + * @return array + */ + private static function sanitizeGallery(array $item) : array + { + $gallery = []; + $variants = $item['Images']['Variants'] ?? []; + foreach ($variants as $variant) { + $gallery[] = $variant['Large']['URL']; + } + if ( self::$limit ) { + $gallery = array_slice($gallery, 0, self::$limit); + } + return $gallery; + } + + /** + * Sanitize item category. + * + * @access private + * @param array $item + * @return string + */ + private static function sanitizeCategory(array $item) : string + { + $node = $item['BrowseNodeInfo']['BrowseNodes'][0] ?? []; + $count = count($node); + for ($i = 0; $i < $count; $i++) { + if ( !isset($node['Ancestor']) ) break; + $node = $node['Ancestor']; + } + return $node['ContextFreeName'] ?? ''; + } + + /** + * Sanitize item node Id. + * + * @access private + * @param array $item + * @return string + */ + private static function sanitizeNode(array $item) : string + { + $node = $item['BrowseNodeInfo']['BrowseNodes'][0] ?? []; + return $node['Id'] ?? ''; + } + + /** + * Sanitize item root Id. + * + * @access private + * @param array $item + * @return string + */ + private static function sanitizeRoot(array $item) : string + { + $node = $item['BrowseNodeInfo']['BrowseNodes'][0] ?? []; + $count = count($node); + for ($i = 0; $i < $count; $i++) { + if ( !isset($node['Ancestor']) ) break; + $node = $node['Ancestor']; + } + return $node['Id'] ?? ''; + } + + /** + * Sanitize item rank. + * + * @access private + * @param array $item + * @return int + */ + private static function sanitizeRank(array $item) : int + { + $node = $item['BrowseNodeInfo']['BrowseNodes'][0] ?? []; + return $node['SalesRank'] ?? 0; + } + + /** + * Sanitize item price. + * + * @access private + * @param array $item + * @return float + */ + private static function sanitizePrice(array $item) : float + { + $listing = $item['Offers']['Listings'][0] ?? []; + return $listing['Price']['Amount'] ?? 0; + } + + /** + * Sanitize item currency. + * + * @access private + * @param array $item + * @return string + */ + private static function sanitizeCurrency(array $item) : string + { + $listing = $item['Offers']['Listings'][0] ?? []; + return $listing['Price']['Currency'] ?? ''; + } + + /** + * Sanitize item discount. + * + * @access private + * @param array $item + * @return float + */ + private static function sanitizeDiscount(array $item) : float + { + $listing = $item['Offers']['Listings'][0] ?? []; + return $listing['Price']['Savings']['Amount'] ?? 0; + } + + /** + * Sanitize item discount percent. + * + * @access private + * @param array $item + * @return float + */ + private static function sanitizePercent(array $item) : float + { + $listing = $item['Offers']['Listings'][0] ?? []; + return $listing['Price']['Savings']['Percentage'] ?? 0; + } + + /** + * Sanitize item discounted. + * + * @access private + * @param array $item + * @return float + */ + private static function sanitizeDiscounted(array $item) : float + { + return (self::sanitizePrice($item) + self::sanitizeDiscount($item)); + } + + /** + * Sanitize item shipping. + * + * @access private + * @param array $item + * @return array + */ + private static function sanitizeShipping(array $item) : array + { + $listing = $item['Offers']['Listings'][0] ?? []; + return [ + 'fulfilled' => $listing['DeliveryInfo']['IsAmazonFulfilled'] ?? false, + 'free' => $listing['DeliveryInfo']['IsFreeShippingEligible'] ?? false, + 'prime' => $listing['DeliveryInfo']['IsPrimeEligible'] ?? false + ]; + } + + /** + * Sanitize item availability. + * + * @access private + * @param array $item + * @return string + */ + private static function sanitizeAvailability(array $item) : string + { + $listing = $item['Offers']['Listings'][0] ?? []; + return $listing['Availability']['Message'] ?? ''; + } + + /** + * Sanitize item features. + * + * @access private + * @param array $item + * @return array + */ + private static function sanitizeFeatures(array $item) : array + { + $features = $item['ItemInfo']['Features']['DisplayValues'] ?? []; + if ( self::$limit ) { + $features = array_slice($features, 0, self::$limit); + } + foreach ($features as $key => $feature) { + $feature = self::formatString($feature); + $features[$key] = $feature; + } + return $features; + } + + /** + * Format string. + * + * @access private + * @param string $string + * @return string + */ + private static function formatString(string $string) : string + { + if ( !self::$format ) { + $string = preg_replace('/[^\x{0000}-\x{FFFF}]/u', ' ', $string); + $string = str_replace(['【', ], ' [', $string); + $string = str_replace(['】', ], '] ', $string); + } + + $string = trim($string); + $string = str_replace("\r", "\n", $string); + $string = preg_replace(['/\n+/', '/[ \t]+/'], ["\n", ' '], $string); + + return $string; + } + + /** + * Strip space. + * + * @access private + * @param string $string + * @return string + */ + private static function stripSpace(string $string) : string + { + $string = trim($string); + $string = preg_replace('/\s+/', '', $string); + return $string; + } + + /** + * Check URL. + * + * @access private + * @param string $keyword + * @return bool + */ + private static function isUrl(string $keyword) : bool + { + if ( strpos(strtolower($keyword), 'amazon') !== false ) { + return (bool)filter_var($keyword, FILTER_VALIDATE_URL); + } + return false; + } +} diff --git a/src/includes/OperationParser.php b/src/includes/OperationParser.php deleted file mode 100644 index 7d0f735..0000000 --- a/src/includes/OperationParser.php +++ /dev/null @@ -1,37 +0,0 @@ - - * @link : https://jakiboy.github.io/apaapi/ - * @license : MIT - * - * This file if a part of Apaapi Lib. - */ - -namespace Apaapi\includes; - -use Apaapi\interfaces\OperationInterface; - -/** - * Basic Apaapi Request Operation Parsing Helper. - */ -final class OperationParser extends Parser -{ - /** - * @access public - * @param OperationInterface $operation - * @return string - */ - public static function toString(OperationInterface $operation) - { - $wrapper = []; - foreach ($operation as $key => $value) { - if ( $value ) { - $wrapper[ucfirst($key)] = $value; - } - } - return json_encode($wrapper); - } -} diff --git a/src/includes/Parser.php b/src/includes/Parser.php index 60ea574..c961717 100644 --- a/src/includes/Parser.php +++ b/src/includes/Parser.php @@ -1,31 +1,92 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * * This file if a part of Apaapi Lib. */ +declare(strict_types=1); + namespace Apaapi\includes; use Apaapi\interfaces\ParsableInterface; /** - * Basic Apaapi Request Parser. + * Apaapi request parser. */ -class Parser +final class Parser { /** + * Get class name. + * * @access public * @param ParsableInterface $parsable * @return string */ - public static function getName(ParsableInterface $parsable) + public static function getName(ParsableInterface $parsable) : string + { + $class = get_class($parsable); + return basename(str_replace('\\', '/', $class)); + } + + /** + * Convert items. + * + * @access public + * @param mixed $items + * @return mixed + */ + public static function convert($items) + { + if ( is_array($items) ) { + return self::resourcesToArray($items); + } + return self::operationToString($items); + } + + /** + * Convert resources object to array. + * + * @access private + * @param array $resources + * @return array + */ + private static function resourcesToArray(array $resources) : array + { + $wrapper = []; + foreach ($resources as $resource) { + $parent = self::getName($resource); + if ( is_array($resource->items) ) { + foreach ($resource->items as $item) { + $wrapper[] = "{$parent}.{$item}"; + } + } elseif ( $resource->items === false ) { + $wrapper[] = $parent; + } + } + return $wrapper; + } + + /** + * Convert operation to string. + * + * @access private + * @param ParsableInterface $operation + * @return string + */ + private static function operationToString(ParsableInterface $operation) : string { - return basename(str_replace('\\', '/', get_class($parsable))); + $wrapper = []; + foreach ($operation as $key => $value) { + if ( $value ) { + $wrapper[ucfirst($key)] = $value; + } + } + return json_encode($wrapper); } } diff --git a/src/includes/Provider.php b/src/includes/Provider.php new file mode 100644 index 0000000..217dbf6 --- /dev/null +++ b/src/includes/Provider.php @@ -0,0 +1,267 @@ + + * @link : https://jakiboy.github.io/apaapi/ + * @license : MIT + * + * This file if a part of Apaapi Lib. + */ + +declare(strict_types=1); + +namespace Apaapi\includes; + +/** + * Apaapi static helper. + * @see https://webservices.amazon.com/paapi5/documentation/locale-reference.html + */ +final class Provider +{ + /** + * @access public + * @var string HOST, Dynamic API host + * @var array CONDITIONS, API product conditions + */ + public const HOST = 'https://www.amazon.{locale}'; + public const CONDITIONS = ['Any', 'New', 'Used', 'Refurbished', 'Collectible']; + + /** + * Get request regions. + * + * @access public + * @return array + */ + public static function getRegions() : array + { + return self::load('regions'); + } + + /** + * Get request region by locale. + * + * @access public + * @param string $locale + * @return string + */ + public static function getRegion(string $locale) : string + { + $default = 'eu-west-1'; + foreach (self::getRegions() as $name => $region) { + if ( in_array($locale, $region) ) { + $default = $name; + break; + } + } + return $default; + } + + /** + * Get request locales. + * + * @access public + * @return array + */ + public static function getLocales() : array + { + $locales = []; + foreach (self::getRegions() as $region => $locale) { + $locales = array_merge($locales, $locale); + } + return $locales; + } + + /** + * Get languages. + * + * @access public + * @param string $locale + * @return array + */ + public static function getLanguages(?string $locale = null) : array + { + $languages = self::load('languages'); + if ( $locale ) { + return $languages[$locale] ?? []; + } + return $languages; + } + + /** + * Get country. + * + * @access public + * @param string $locale + * @return string + */ + public static function getCountry(string $locale) : string + { + return self::getCountries()[$locale] ?? ''; + } + + /** + * Get countries. + * + * @access public + * @return array + */ + public static function getCountries() : array + { + return self::load('countries'); + } + + /** + * Get currency. + * + * @access public + * @param string $locale + * @return array + */ + public static function getCurrency(string $locale) : array + { + return self::getCurrencies()[$locale] ?? []; + } + + /** + * Get currencies. + * + * @access public + * @return array + */ + public static function getCurrencies() : array + { + return self::load('currencies'); + } + + /** + * Get symbols (currency). + * + * @access public + * @return array + */ + public static function getSymbols() : array + { + return self::load('symbols'); + } + + /** + * Get symbol (currency). + * + * @access public + * @param string $currency + * @return string + */ + public static function getSymbol(string $currency) : string + { + return self::getSymbols()[$currency] ?? ''; + } + + /** + * Get static categories (Search Index). + * + * @access public + * @param string $locale + * @return array + */ + public static function getCategories(?string $locale = null) : array + { + $categories = self::load('categories'); + if ( $locale ) { + return $categories[$locale] ?? []; + } + return $categories; + } + + /** + * Get static category Id (Search Index). + * + * @access public + * @param string $category + * @param string $locale + * @return string + */ + public static function getCategoryId(string $category, string $locale) : string + { + $key = Cache::generateKey("category-id-{$category}-{$locale}"); + + if ( !($cached = Cache::get($key)) ) { + + $categories = self::getCategories($locale); + $column = array_column($categories, 'name'); + $index = array_search($category, $column); + if ( is_int($index) ) { + $cached = $categories[$index]['id'] ?? ''; + Cache::set($key, $cached); + } + } + + return (string)$cached; + } + + /** + * Get node url. + * + * @access public + * @param string $id + * @return string + */ + public static function getNodeUrl(string $id) : string + { + return self::HOST . "/b?node={$id}&tag={tag}"; + } + + /** + * Generate header. + * + * @access public + * @return string + */ + public static function generateHeader(string $locale = 'com') : string + { + $currency = 'USD'; + if ( $locale !== 'com' ) { + $currency = self::getCurrency($locale)[0] ?? $currency; + } + + $time = time() . 'l'; + + $id = mt_rand(100, 999); + $id .= '-' . mt_rand(1000000, 9999999); + $id .= '-' . mt_rand(1000000, 9999999); + + $cookie = [ + "i18n-prefs={$currency}", + "session-id={$id}", + "session-id-time={$time}" + ]; + + $header = [ + 'Cookie' => implode('; ', $cookie), + 'Connection' => 'close' + ]; + + return implode("\r\n", array_map(function($key, $value) { + return "{$key}: {$value}"; + }, array_keys($header), $header)); + } + + /** + * Load file. + * + * @access private + * @param string $name + * @return array + */ + private static function load(string $name) : array + { + $file = __DIR__ . "/bin/{$name}.json"; + $data = []; + if ( file_exists($file) ) { + $content = @file_get_contents($file); + $data = Normalizer::decode($content); + } + return $data; + } +} diff --git a/src/includes/Rating.php b/src/includes/Rating.php new file mode 100644 index 0000000..3ed9f26 --- /dev/null +++ b/src/includes/Rating.php @@ -0,0 +1,161 @@ + + * @link : https://jakiboy.github.io/apaapi/ + * @license : MIT + * + * This file if a part of Apaapi Lib. + */ + +declare(strict_types=1); + +namespace Apaapi\includes; + +use DOMDocument; +use DOMNodeList; +use DOMXPath; + +final class Rating +{ + /** + * @access private + * @var string $keyword + * @var string $locale + * @var string $tag + */ + private $keyword; + private $locale; + private $tag; + + private const ACTION = '/product-reviews/'; + private const VALUExPATH = "//i[contains(@class,'averageStarRating')]"; + private const COUNTxPATH = "//div[contains(@class,'averageStarRatingNumerical')]"; + + /** + * Init rating. + * + * @param string $keyword + * @param string $locale + * @param string $tag + */ + public function __construct(string $keyword, string $locale = 'com', ?string $tag = null) + { + $this->keyword = Normalizer::formatId($keyword); + $this->locale = Normalizer::formatTLD($locale); + $this->tag = $tag; + } + + /** + * Get rating data. + * + * @access public + * @return array + */ + public function get() : array + { + $rating = $this->getDefault(); + + if ( !Keyword::isASIN($this->keyword) + && !Keyword::isISBN($this->keyword) ) { + return $rating; + } + + $key = "rating-{$this->locale}-{$this->keyword}"; + $key = Cache::generateKey($key); + + if ( !($rating = Cache::get($key)) ) { + + $host = Provider::HOST; + $url = str_replace('{locale}', $this->locale, $host); + $url .= self::ACTION; + $url .= $this->keyword; + + $client = new Client($url, [ + 'header' => Provider::generateHeader() + ]); + + $client->setMethod('GET') + ->setRedirect(1) + ->setEncoding('') + ->setTimeout(0); + + $response = $client->getResponse(); + $client->close(); + + if ( $client->getCode() == 200 ) { + $rating = $this->extract($response); + $rating['url'] = "{$url}?tag={$this->tag}&linkCode=ll2"; + Cache::set($key, $rating); + } + + } + + return ($rating) ? $rating : $this->getDefault(); + } + + /** + * Extract rating data from HTML. + * + * @access private + * @param string $html + * @return array + */ + private function extract(string $html) : array + { + $rating = []; + + if ( class_exists('DomDocument') ) { + + // Ignore XML errors + libxml_use_internal_errors(true); + + // Init DOM document + $dom = new DOMDocument(); + $dom->loadHTML($html); + $xPath = new DOMXPath($dom); + + // Extract rating value + $nodes = $xPath->query(self::VALUExPATH); + if ( $nodes instanceof DOMNodeList && ($nodes->length > 0) ) { + $node = $nodes[0]; + $data = explode(' ', (string)$node->nodeValue); + $value = $data[0]; + $value = str_replace(',', '.', $value); + $rating['value'] = (float)$value; + } + + // Extract rating count + $nodes = $xPath->query(self::COUNTxPATH); + if ( $nodes instanceof DOMNodeList && ($nodes->length > 0) ) { + $node = $nodes[0]; + $count = (string)$node->nodeValue; + $count = preg_replace('/\D/', '', $count); + $rating['count'] = (int)$count; + } + + // Clear XML errors + libxml_clear_errors(); + + } + + return $rating; + } + + /** + * Get default rating. + * + * @access private + * @return array + */ + private function getDefault() : array + { + return [ + 'value' => null, + 'count' => null, + 'url' => null + ]; + } +} diff --git a/src/includes/RequestClient.php b/src/includes/RequestClient.php deleted file mode 100644 index 5e90897..0000000 --- a/src/includes/RequestClient.php +++ /dev/null @@ -1,326 +0,0 @@ - - * @link : https://jakiboy.github.io/apaapi/ - * @license : MIT - * - * This file if a part of Apaapi Lib. - */ - -namespace Apaapi\includes; - -use Apaapi\interfaces\RequestClientInterface; -use Apaapi\exceptions\RequestException; - -/** - * Basic Apaapi Request Client Wrapper Class. - */ -class RequestClient implements RequestClientInterface -{ - /** - * @access protected - * @var string $endpoint, Request URL - * @var array $params, Request params - * @var mixed $handler, resource|array Request handler - * @var string $method, curl|stream - * @var string $response, Request response content - * @var string $error, Response error content - * @var int $code, Response code - */ - protected $endpoint; - protected $params; - protected $handler; - protected $method; - protected $response = false; - protected $error = false; - protected $code = 200; - - /** - * @param string $endpoint - * @param array $params - * @param bool $throwClientException - * @throws bool RequestException - */ - public function __construct($endpoint, $params, $throwClientException = true) - { - if ( !self::hasCurl() && !self::hasStream() ) { - if ( $throwClientException ) { - throw new RequestException( - RequestException::invalidRequestClientMessage() - ); - } else { - $this->code = 0; - $this->error = $this->setError( - 'HTTP Client', - RequestException::invalidRequestClientMessage() - ); - } - } - - $this->endpoint = $endpoint; - $this->params = $params; - - if ( self::hasCurl() ) { - $this->method = 'curl'; - - } elseif ( self::hasStream() ) { - $this->method = 'stream'; - } - - $this->init(); - } - - /** - * Get response including error(s). - * - * @access public - * @param void - * @return mixed - */ - public function getResponse() - { - // Send request - $this->send(); - - // Return error - if ( $this->hasError() ) { - return $this->error; - } - - // Return response - return $this->response; - } - - /** - * Get HTTP response code. - * - * @access public - * @param void - * @return int - */ - public function getCode() - { - return $this->code; - } - - /** - * Close handler. - * - * @access public - * @param void - * @return void - */ - public function close() - { - if ( $this->method == 'curl' ) { - curl_close($this->handler); - } - } - - /** - * Check if Curl activated, - * Requires: "Curl" extension. - * - * @access public - * @param void - * @return bool - */ - public static function hasCurl() - { - return function_exists('curl_init'); - } - - /** - * Check if Stream activated, - * Requires: "allow_url_fopen" parameter. - * - * @access public - * @param void - * @return bool - */ - public static function hasStream() - { - return intval(ini_get('allow_url_fopen')); - } - - /** - * Init HTTP client. - * - * @access protected - * @param void - * @return void - */ - protected function init() - { - if ( $this->method == 'curl' ) { - $this->handler = curl_init(); - curl_setopt($this->handler, CURLOPT_URL, $this->endpoint); - curl_setopt($this->handler, CURLOPT_HTTPHEADER, $this->getRequestHeader()); - curl_setopt($this->handler, CURLOPT_POSTFIELDS, $this->getRequestContent()); - curl_setopt($this->handler, CURLOPT_POST, true); - curl_setopt($this->handler, CURLOPT_RETURNTRANSFER, true); - curl_setopt($this->handler, CURLOPT_SSL_VERIFYPEER, $this->isSSL()); - curl_setopt($this->handler, CURLOPT_TIMEOUT, 30); - - } elseif ( $this->method == 'stream' ) { - $this->handler = [ - 'http' => [ - 'method' => 'POST', - 'header' => $this->getRequestHeader(), - 'content' => $this->getRequestContent(), - 'timeout' => 30 - ] - ]; - } - } - - /** - * Check for HTTP error. - * - * @access protected - * @param void - * @return bool - */ - protected function hasError() - { - return (bool)$this->error; - } - - /** - * Normalize HTTP error, - * Uses "Amazon API error" format (JSON). - * - * @access protected - * @param string $method - * @param string $error - * @return string - */ - protected function setError($method, $error) - { - return json_encode([ - '__type' => basename(__CLASS__) . 'Exception', - 'Errors' => [ - [ - 'Code' => basename(__CLASS__) . "{$method}Error", - 'Message' => "{$method}: {$error}." - ] - ] - ]); - } - - /** - * Send request & catch errors. - * - * @access protected - * @param void - * @return void - */ - protected function send() - { - if ( $this->method == 'curl' ) { - - // Get cURL response & HTTP status code - $this->response = curl_exec($this->handler); - $this->code = curl_getinfo($this->handler)['http_code']; - - // Catch cURL error content - if ( curl_errno($this->handler) ) { - $this->error = $this->setError( - 'Curl', - curl_error($this->handler) - ); - } - - // Catch HTTP error content from response - if ( $this->response && $this->code !== 200 ) { - $this->error = $this->response; - $this->response = false; - } - - } elseif ( $this->method == 'stream' ) { - - // Create stream context - $context = stream_context_create($this->handler); - - // Get stream response - $this->response = @file_get_contents($this->endpoint, false, $context); - - // Catch HTTP response headers - $headers = @get_headers($this->endpoint, false, $context); - - if ( isset($headers[0]) ) { - - // Catch HTTP status code from headers - $this->code = (int)substr($headers[0], 9, 3); - - // Catch HTTP error content from headers - if ( !$this->response || $this->code !== 200 ) { - $this->error = $this->setError( - 'Stream', - $headers[0] - ); - } - - } else { - $this->code = 0; - $this->error = $this->setError( - 'Stream', - 'Failed to open stream, operation failed' - ); - } - } - } - - /** - * Check SSL. - * - * @access protected - * @param void - * @return bool - */ - protected function isSSL() - { - if ( isset($_SERVER['HTTPS']) ) { - if ( strtolower($_SERVER['HTTPS']) === 'on' ) { - return true; - } - if ( $_SERVER['HTTPS'] == '1' ) { - return true; - } - } elseif ( isset($_SERVER['SERVER_PORT']) - && ( $_SERVER['SERVER_PORT'] == '443' ) ) { - return true; - } - return false; - } - - /** - * Get request content. - * - * @access protected - * @param void - * @return mixed - */ - protected function getRequestContent() - { - return isset($this->params['http']['content']) - ? $this->params['http']['content'] : ''; - } - - /** - * Get request header. - * - * @access protected - * @param void - * @return mixed - */ - protected function getRequestHeader() - { - $header = isset($this->params['http']['header']) - ? $this->params['http']['header'] : []; - return explode("\n", $header); - } -} diff --git a/src/includes/ResourceParser.php b/src/includes/ResourceParser.php deleted file mode 100644 index 5a4662f..0000000 --- a/src/includes/ResourceParser.php +++ /dev/null @@ -1,40 +0,0 @@ - - * @link : https://jakiboy.github.io/apaapi/ - * @license : MIT - * - * This file if a part of Apaapi Lib. - */ - -namespace Apaapi\includes; - -/** - * Basic Apaapi Request Resource Parsing Helper. - */ -final class ResourceParser extends Parser -{ - /** - * @access public - * @param array $resources - * @return array - */ - public static function toString($resources) - { - $wrapper = []; - foreach ($resources as $resource) { - $parent = self::getName($resource); - if ( is_array($resource->items) ) { - foreach ($resource->items as $item) { - $wrapper[] = "{$parent}.{$item}"; - } - } elseif ( $resource->items === false ) { - $wrapper[] = $parent; - } - } - return $wrapper; - } -} diff --git a/src/includes/ResponseType.php b/src/includes/ResponseType.php deleted file mode 100644 index 1232992..0000000 --- a/src/includes/ResponseType.php +++ /dev/null @@ -1,129 +0,0 @@ - - * @link : https://jakiboy.github.io/apaapi/ - * @license : MIT - * - * This file if a part of Apaapi Lib. - */ - -namespace Apaapi\includes; - -use Apaapi\interfaces\ResponseTypeInterface; -use Apaapi\exceptions\ResponseTypeException; - -/** - * Basic Apaapi Response Helper. - */ -class ResponseType implements ResponseTypeInterface -{ - /** - * @access private - * @var string $type - */ - private $type; - - /** - * @param string $type - */ - public function __construct($type = 'object') - { - $this->type = strtolower($type); - } - - /** - * @access public - * @param string $response - * @return mixed - * @throws ResponseTypeException - */ - public function format($response) - { - if ( $this->isValidFormat() !== true ) { - throw new ResponseTypeException( - ResponseTypeException::invalidResponseTypeFormat($this->type) - ); - } - - switch ($this->type) { - case 'array': - return self::decode($response,true); - break; - case 'object': - return self::decode($response); - break; - case 'serialized': - return serialize(self::decode($response)); - break; - } - } - - /** - * @access public - * @param object $response - * @param string $operation - * @return string - */ - public function parse($response, $operation) - { - // JSON Decode - $response = self::decode($response); - - if ( $operation == 'GetItems' ) { - $response = isset($response->ItemsResult->Items) - ? $response->ItemsResult->Items : []; - - } elseif ( $operation == 'SearchItems' ) { - $response = isset($response->SearchResult->Items) - ? $response->SearchResult->Items : []; - - } elseif ( $operation == 'GetVariations' ) { - $response = isset($response->VariationsResult->Items) - ? $response->VariationsResult->Items : []; - - } elseif ( $operation == 'GetBrowseNodes' ) { - $response = isset($response->BrowseNodesResult->BrowseNodes) - ? $response->BrowseNodesResult->BrowseNodes : []; - } - - // JSON Encode for format - return self::encode($response); - } - - /** - * @access public - * @param string $json - * @param bool $object - * @return array|object - */ - public static function decode($json, $object = false) - { - return json_decode((string)$json, $object); - } - - /** - * @access public - * @param array|object $json - * @return string - */ - public static function encode($json) - { - return json_encode($json); - } - - /** - * @access private - * @param void - * @return bool - */ - private function isValidFormat() - { - if ( !in_array($this->type, ['object','array','serialized']) ) { - return false; - } - return true; - } -} diff --git a/src/includes/bin/categories.json b/src/includes/bin/categories.json new file mode 100644 index 0000000..f239c73 --- /dev/null +++ b/src/includes/bin/categories.json @@ -0,0 +1,2408 @@ +{ + "com.au":[ + { + "id":"Automotive", + "name":"Automotive" + }, + { + "id":"Baby", + "name":"Baby" + }, + { + "id":"Beauty", + "name":"Beauty" + }, + { + "id":"Books", + "name":"Books" + }, + { + "id":"Computers", + "name":"Computers" + }, + { + "id":"Electronics", + "name":"Electronics" + }, + { + "id":"EverythingElse", + "name":"Everything Else" + }, + { + "id":"Fashion", + "name":"Clothing & Shoes" + }, + { + "id":"GiftCards", + "name":"Gift Cards" + }, + { + "id":"HealthPersonalCare", + "name":"Health, Household & Personal Care" + }, + { + "id":"HomeAndKitchen", + "name":"Home & Kitchen" + }, + { + "id":"KindleStore", + "name":"Kindle Store" + }, + { + "id":"Lighting", + "name":"Lighting" + }, + { + "id":"Luggage", + "name":"Luggage & Travel Gear" + }, + { + "id":"MobileApps", + "name":"Apps & Games" + }, + { + "id":"MoviesAndTV", + "name":"Movies & TV" + }, + { + "id":"Music", + "name":"CDs & Vinyl" + }, + { + "id":"OfficeProducts", + "name":"Stationery & Office Products" + }, + { + "id":"PetSupplies", + "name":"Pet Supplies" + }, + { + "id":"Software", + "name":"Software" + }, + { + "id":"SportsAndOutdoors", + "name":"Sports, Fitness & Outdoors" + }, + { + "id":"ToolsAndHomeImprovement", + "name":"Home Improvement" + }, + { + "id":"ToysAndGames", + "name":"Toys & Games" + }, + { + "id":"VideoGames", + "name":"Video Games" + } + ], + "com.be":[ + { + "id":"Automotive", + "name":"Auto et Moto" + }, + { + "id":"Baby", + "name":"B\u00e9b\u00e9" + }, + { + "id":"Beauty", + "name":"Beaut\u00e9 et Parfum" + }, + { + "id":"Books", + "name":"Livres" + }, + { + "id":"Electronics", + "name":"High-Tech" + }, + { + "id":"Fashion", + "name":"Mode" + }, + { + "id":"Garden", + "name":"Jardin" + }, + { + "id":"GiftCards", + "name":"Boutique ch\u00e8ques-cadeaux" + }, + { + "id":"Grocery", + "name":"Alimentation" + }, + { + "id":"HomeImprovement", + "name":"Bricolage" + }, + { + "id":"HealthPersonalCare", + "name":"Sant\u00e9 & Hygi\u00e8ne personnelle" + }, + { + "id":"Industrial", + "name":"Secteur industriel et scientifique" + }, + { + "id":"Music", + "name":"Musique : CD & Vinyles" + }, + { + "id":"MusicalInstruments", + "name":"Instruments de musique" + }, + { + "id":"MoviesAndTV", + "name":"Cin\u00e9ma & TV" + }, + { + "id":"OfficeProducts", + "name":"Office Produits de bureau" + }, + { + "id":"PetSupplies", + "name":"Animalerie" + }, + { + "id":"Software", + "name":"Logiciels" + }, + { + "id":"SportsAndOutdoors", + "name":"Sports & Activit\u00e9s en plein-air" + }, + { + "id":"Toys", + "name":"Jeux et Jouets" + }, + { + "id":"VideoGames", + "name":"Jeux vid\u00e9o" + } + ], + "com.br":[ + { + "id":"Books", + "name":"Livros" + }, + { + "id":"Computers", + "name":"Computadores e Inform\u00e1tica" + }, + { + "id":"Electronics", + "name":"Eletr\u00f4nicos" + }, + { + "id":"HomeAndKitchen", + "name":"Casa e Cozinha" + }, + { + "id":"KindleStore", + "name":"Loja Kindle" + }, + { + "id":"MobileApps", + "name":"Apps e Jogos" + }, + { + "id":"OfficeProducts", + "name":"Material para Escrit\u00f3rio e Papelaria" + }, + { + "id":"ToolsAndHomeImprovement", + "name":"Ferramentas e Materiais de Constru\u00e7\u00e3o" + }, + { + "id":"VideoGames", + "name":"Games" + } + ], + "ca":[ + { + "id":"Apparel", + "name":"Clothing & Accessories" + }, + { + "id":"Automotive", + "name":"Automotive" + }, + { + "id":"Baby", + "name":"Baby" + }, + { + "id":"Beauty", + "name":"Beauty" + }, + { + "id":"Books", + "name":"Books" + }, + { + "id":"Classical", + "name":"Classical Music" + }, + { + "id":"Electronics", + "name":"Electronics" + }, + { + "id":"EverythingElse", + "name":"Everything Else" + }, + { + "id":"ForeignBooks", + "name":"English Books" + }, + { + "id":"GardenAndOutdoor", + "name":"Patio, Lawn & Garden" + }, + { + "id":"GiftCards", + "name":"Gift Cards" + }, + { + "id":"GroceryAndGourmetFood", + "name":"Grocery & Gourmet Food" + }, + { + "id":"Handmade", + "name":"Handmade" + }, + { + "id":"HealthPersonalCare", + "name":"Health & Personal Care" + }, + { + "id":"HomeAndKitchen", + "name":"Home & Kitchen" + }, + { + "id":"Industrial", + "name":"Industrial & Scientific" + }, + { + "id":"Jewelry", + "name":"Jewelry" + }, + { + "id":"KindleStore", + "name":"Kindle Store" + }, + { + "id":"Luggage", + "name":"Luggage & Bags" + }, + { + "id":"LuxuryBeauty", + "name":"Luxury Beauty" + }, + { + "id":"MobileApps", + "name":"Apps & Games" + }, + { + "id":"MoviesAndTV", + "name":"Movies & TV" + }, + { + "id":"Music", + "name":"Music" + }, + { + "id":"MusicalInstruments", + "name":"Musical Instruments, Stage & Studio" + }, + { + "id":"OfficeProducts", + "name":"Office Products" + }, + { + "id":"PetSupplies", + "name":"Pet Supplies" + }, + { + "id":"Shoes", + "name":"Shoes & Handbags" + }, + { + "id":"Software", + "name":"Software" + }, + { + "id":"SportsAndOutdoors", + "name":"Sports & Outdoors" + }, + { + "id":"ToolsAndHomeImprovement", + "name":"Tools & Home Improvement" + }, + { + "id":"ToysAndGames", + "name":"Toys & Games" + }, + { + "id":"VHS", + "name":"VHS" + }, + { + "id":"VideoGames", + "name":"Video Games" + }, + { + "id":"Watches", + "name":"Watches" + } + ], + "eg":[ + { + "id":"ArtsAndCrafts", + "name":"Arts, Crafts & Sewing" + }, + { + "id":"Automotive", + "name":"Automotive Parts & Accessories" + }, + { + "id":"Baby", + "name":"Baby" + }, + { + "id":"Beauty", + "name":"Beauty & Personal Care" + }, + { + "id":"Books", + "name":"Books" + }, + { + "id":"Electronics", + "name":"Electronics" + }, + { + "id":"Fashion", + "name":"Amazon Fashion" + }, + { + "id":"Garden", + "name":"Home & Garden" + }, + { + "id":"Grocery", + "name":"Grocery & Gourmet Food" + }, + { + "id":"HealthPersonalCare", + "name":"Health, Household & Baby Care" + }, + { + "id":"Home", + "name":"Home Related" + }, + { + "id":"HomeImprovement", + "name":"Tools & Home Improvement" + }, + { + "id":"Industrial", + "name":"Industrial & Scientific" + }, + { + "id":"MusicalInstruments", + "name":"Musical Instruments" + }, + { + "id":"OfficeProducts", + "name":"Office Products" + }, + { + "id":"PetSupplies", + "name":"Pet Supplies" + }, + { + "id":"Software", + "name":"Software" + }, + { + "id":"SportsAndOutdoors", + "name":"Sports" + }, + { + "id":"Toys", + "name":"Toys & Games" + }, + { + "id":"VideoGames", + "name":"Video Games" + } + ], + "fr":[ + { + "id":"Apparel", + "name":"V\u00eatements et accessoires" + }, + { + "id":"Appliances", + "name":"Gros \u00e9lectrom\u00e9nager" + }, + { + "id":"Automotive", + "name":"Auto et Moto" + }, + { + "id":"Baby", + "name":"B\u00e9b\u00e9s & Pu\u00e9riculture" + }, + { + "id":"Beauty", + "name":"Beaut\u00e9 et Parfum" + }, + { + "id":"Books", + "name":"Livres en fran\u00e7ais" + }, + { + "id":"Computers", + "name":"Informatique" + }, + { + "id":"DigitalMusic", + "name":"T\u00e9l\u00e9chargement de musique" + }, + { + "id":"Electronics", + "name":"High-Tech" + }, + { + "id":"EverythingElse", + "name":"Autres" + }, + { + "id":"Fashion", + "name":"Mode" + }, + { + "id":"ForeignBooks", + "name":"Livres anglais et \u00e9trangers" + }, + { + "id":"GardenAndOutdoor", + "name":"Jardin" + }, + { + "id":"GiftCards", + "name":"Boutique ch\u00e8ques-cadeaux" + }, + { + "id":"GroceryAndGourmetFood", + "name":"Epicerie" + }, + { + "id":"Handmade", + "name":"Handmade" + }, + { + "id":"HealthPersonalCare", + "name":"Hygi\u00e8ne et Sant\u00e9" + }, + { + "id":"HomeAndKitchen", + "name":"Cuisine & Maison" + }, + { + "id":"Industrial", + "name":"Secteur industriel & scientifique" + }, + { + "id":"Jewelry", + "name":"Bijoux" + }, + { + "id":"KindleStore", + "name":"Boutique Kindle" + }, + { + "id":"Lighting", + "name":"Luminaires et Eclairage" + }, + { + "id":"Luggage", + "name":"Bagages" + }, + { + "id":"LuxuryBeauty", + "name":"Beaut\u00e9 Prestige" + }, + { + "id":"MobileApps", + "name":"Applis & Jeux" + }, + { + "id":"MoviesAndTV", + "name":"DVD & Blu-ray" + }, + { + "id":"Music", + "name":"Musique : CD & Vinyles" + }, + { + "id":"MusicalInstruments", + "name":"Instruments de musique & Sono" + }, + { + "id":"OfficeProducts", + "name":"Fournitures de bureau" + }, + { + "id":"PetSupplies", + "name":"Animalerie" + }, + { + "id":"Shoes", + "name":"Chaussures et Sacs" + }, + { + "id":"Software", + "name":"Logiciels" + }, + { + "id":"SportsAndOutdoors", + "name":"Sports et Loisirs" + }, + { + "id":"ToolsAndHomeImprovement", + "name":"Bricolage" + }, + { + "id":"ToysAndGames", + "name":"Jeux et Jouets" + }, + { + "id":"VHS", + "name":"VHS" + }, + { + "id":"VideoGames", + "name":"Jeux vid\u00e9o" + }, + { + "id":"Watches", + "name":"Montres" + } + ], + "de":[ + { + "id":"AmazonVideo", + "name":"Prime Video" + }, + { + "id":"Apparel", + "name":"Bekleidung" + }, + { + "id":"Appliances", + "name":"Elektro-Gro\u00dfger\u00e4te" + }, + { + "id":"Automotive", + "name":"Auto & Motorrad" + }, + { + "id":"Baby", + "name":"Baby" + }, + { + "id":"Beauty", + "name":"Beauty" + }, + { + "id":"Books", + "name":"B\u00fccher" + }, + { + "id":"Classical", + "name":"Klassik" + }, + { + "id":"Computers", + "name":"Computer & Zubeh\u00f6r" + }, + { + "id":"DigitalMusic", + "name":"Musik-Downloads" + }, + { + "id":"Electronics", + "name":"Elektronik & Foto" + }, + { + "id":"EverythingElse", + "name":"Sonstiges" + }, + { + "id":"Fashion", + "name":"Fashion" + }, + { + "id":"ForeignBooks", + "name":"B\u00fccher (Fremdsprachig)" + }, + { + "id":"GardenAndOutdoor", + "name":"Garten" + }, + { + "id":"GiftCards", + "name":"Geschenkgutscheine" + }, + { + "id":"GroceryAndGourmetFood", + "name":"Lebensmittel & Getr\u00e4nke" + }, + { + "id":"Handmade", + "name":"Handmade" + }, + { + "id":"HealthPersonalCare", + "name":"Drogerie & K\u00f6rperpflege" + }, + { + "id":"HomeAndKitchen", + "name":"K\u00fcche, Haushalt & Wohnen" + }, + { + "id":"Industrial", + "name":"Gewerbe, Industrie & Wissenschaft" + }, + { + "id":"Jewelry", + "name":"Schmuck" + }, + { + "id":"KindleStore", + "name":"Kindle-Shop" + }, + { + "id":"Lighting", + "name":"Beleuchtung" + }, + { + "id":"Luggage", + "name":"Koffer, Rucks\u00e4cke & Taschen" + }, + { + "id":"LuxuryBeauty", + "name":"Luxury Beauty" + }, + { + "id":"Magazines", + "name":"Zeitschriften" + }, + { + "id":"MobileApps", + "name":"Apps & Spiele" + }, + { + "id":"MoviesAndTV", + "name":"DVD & Blu-ray" + }, + { + "id":"Music", + "name":"Musik-CDs & Vinyl" + }, + { + "id":"MusicalInstruments", + "name":"Musikinstrumente & DJ-Equipment" + }, + { + "id":"OfficeProducts", + "name":"B\u00fcrobedarf & Schreibwaren" + }, + { + "id":"PetSupplies", + "name":"Haustier" + }, + { + "id":"Photo", + "name":"Kamera & Foto" + }, + { + "id":"Shoes", + "name":"Schuhe & Handtaschen" + }, + { + "id":"Software", + "name":"Software" + }, + { + "id":"SportsAndOutdoors", + "name":"Sport & Freizeit" + }, + { + "id":"ToolsAndHomeImprovement", + "name":"Baumarkt" + }, + { + "id":"ToysAndGames", + "name":"Spielzeug" + }, + { + "id":"VHS", + "name":"VHS" + }, + { + "id":"VideoGames", + "name":"Games" + }, + { + "id":"Watches", + "name":"Uhren" + } + ], + "in":[ + { + "id":"Apparel", + "name":"Clothing & Accessories" + }, + { + "id":"Appliances", + "name":"Appliances" + }, + { + "id":"Automotive", + "name":"Car & Motorbike" + }, + { + "id":"Baby", + "name":"Baby" + }, + { + "id":"Beauty", + "name":"Beauty" + }, + { + "id":"Books", + "name":"Books" + }, + { + "id":"Collectibles", + "name":"Collectibles" + }, + { + "id":"Computers", + "name":"Computers & Accessories" + }, + { + "id":"Electronics", + "name":"Electronics" + }, + { + "id":"EverythingElse", + "name":"Everything Else" + }, + { + "id":"Fashion", + "name":"Amazon Fashion" + }, + { + "id":"Furniture", + "name":"Furniture" + }, + { + "id":"GardenAndOutdoor", + "name":"Garden & Outdoors" + }, + { + "id":"GiftCards", + "name":"Gift Cards" + }, + { + "id":"GroceryAndGourmetFood", + "name":"Grocery & Gourmet Foods" + }, + { + "id":"HealthPersonalCare", + "name":"Health & Personal Care" + }, + { + "id":"HomeAndKitchen", + "name":"Home & Kitchen" + }, + { + "id":"Industrial", + "name":"Industrial & Scientific" + }, + { + "id":"Jewelry", + "name":"Jewellery" + }, + { + "id":"KindleStore", + "name":"Kindle Store" + }, + { + "id":"Luggage", + "name":"Luggage & Bags" + }, + { + "id":"LuxuryBeauty", + "name":"Luxury Beauty" + }, + { + "id":"MobileApps", + "name":"Apps & Games" + }, + { + "id":"MoviesAndTV", + "name":"Movies & TV Shows" + }, + { + "id":"Music", + "name":"Music" + }, + { + "id":"MusicalInstruments", + "name":"Musical Instruments" + }, + { + "id":"OfficeProducts", + "name":"Office Products" + }, + { + "id":"PetSupplies", + "name":"Pet Supplies" + }, + { + "id":"Shoes", + "name":"Shoes & Handbags" + }, + { + "id":"Software", + "name":"Software" + }, + { + "id":"SportsAndOutdoors", + "name":"Sports, Fitness & Outdoors" + }, + { + "id":"ToolsAndHomeImprovement", + "name":"Tools & Home Improvement" + }, + { + "id":"ToysAndGames", + "name":"Toys & Games" + }, + { + "id":"VideoGames", + "name":"Video Games" + }, + { + "id":"Watches", + "name":"Watches" + } + ], + "it":[ + { + "id":"Apparel", + "name":"Abbigliamento" + }, + { + "id":"Appliances", + "name":"Grandi elettrodomestici" + }, + { + "id":"Automotive", + "name":"Auto e Moto" + }, + { + "id":"Baby", + "name":"Prima infanzia" + }, + { + "id":"Beauty", + "name":"Bellezza" + }, + { + "id":"Books", + "name":"Libri" + }, + { + "id":"Computers", + "name":"Informatica" + }, + { + "id":"DigitalMusic", + "name":"Musica Digitale" + }, + { + "id":"Electronics", + "name":"Elettronica" + }, + { + "id":"EverythingElse", + "name":"Altro" + }, + { + "id":"Fashion", + "name":"Moda" + }, + { + "id":"ForeignBooks", + "name":"Libri in altre lingue" + }, + { + "id":"GardenAndOutdoor", + "name":"Giardino e giardinaggio" + }, + { + "id":"GiftCards", + "name":"Buoni Regalo" + }, + { + "id":"GroceryAndGourmetFood", + "name":"Alimentari e cura della casa" + }, + { + "id":"Handmade", + "name":"Handmade" + }, + { + "id":"HealthPersonalCare", + "name":"Salute e cura della persona" + }, + { + "id":"HomeAndKitchen", + "name":"Casa e cucina" + }, + { + "id":"Industrial", + "name":"Industria e Scienza" + }, + { + "id":"Jewelry", + "name":"Gioielli" + }, + { + "id":"KindleStore", + "name":"Kindle Store" + }, + { + "id":"Lighting", + "name":"Illuminazione" + }, + { + "id":"Luggage", + "name":"Valigeria" + }, + { + "id":"MobileApps", + "name":"App e Giochi" + }, + { + "id":"MoviesAndTV", + "name":"Film e TV" + }, + { + "id":"Music", + "name":"CD e Vinili" + }, + { + "id":"MusicalInstruments", + "name":"Strumenti musicali e DJ" + }, + { + "id":"OfficeProducts", + "name":"Cancelleria e prodotti per ufficio" + }, + { + "id":"PetSupplies", + "name":"Prodotti per animali domestici" + }, + { + "id":"Shoes", + "name":"Scarpe e borse" + }, + { + "id":"Software", + "name":"Software" + }, + { + "id":"SportsAndOutdoors", + "name":"Sport e tempo libero" + }, + { + "id":"ToolsAndHomeImprovement", + "name":"Fai da te" + }, + { + "id":"ToysAndGames", + "name":"Giochi e giocattoli" + }, + { + "id":"VideoGames", + "name":"Videogiochi" + }, + { + "id":"Watches", + "name":"Orologi" + } + ], + "co.jp":[ + { + "id":"AmazonVideo", + "name":"Prime Video" + }, + { + "id":"Apparel", + "name":"Clothing & Accessories" + }, + { + "id":"Appliances", + "name":"Large Appliances" + }, + { + "id":"Automotive", + "name":"Car & Bike Products" + }, + { + "id":"Baby", + "name":"Baby & Maternity" + }, + { + "id":"Beauty", + "name":"Beauty" + }, + { + "id":"Books", + "name":"Japanese Books" + }, + { + "id":"Classical", + "name":"Classical" + }, + { + "id":"Computers", + "name":"Computers & Accessories" + }, + { + "id":"CreditCards", + "name":"Credit Cards" + }, + { + "id":"DigitalMusic", + "name":"Digital Music" + }, + { + "id":"Electronics", + "name":"Electronics & Cameras" + }, + { + "id":"EverythingElse", + "name":"Everything Else" + }, + { + "id":"Fashion", + "name":"Fashion" + }, + { + "id":"FashionBaby", + "name":"Kids & Baby" + }, + { + "id":"FashionMen", + "name":"Men" + }, + { + "id":"FashionWomen", + "name":"Women" + }, + { + "id":"ForeignBooks", + "name":"English Books" + }, + { + "id":"GiftCards", + "name":"Gift Cards" + }, + { + "id":"GroceryAndGourmetFood", + "name":"Food & Beverage" + }, + { + "id":"HealthPersonalCare", + "name":"Health & Personal Care" + }, + { + "id":"Hobbies", + "name":"Hobby" + }, + { + "id":"HomeAndKitchen", + "name":"Kitchen & Housewares" + }, + { + "id":"Industrial", + "name":"Industrial & Scientific" + }, + { + "id":"Jewelry", + "name":"Jewelry" + }, + { + "id":"KindleStore", + "name":"Kindle Store" + }, + { + "id":"MobileApps", + "name":"Apps & Games" + }, + { + "id":"MoviesAndTV", + "name":"Movies & TV" + }, + { + "id":"Music", + "name":"Music" + }, + { + "id":"MusicalInstruments", + "name":"Musical Instruments" + }, + { + "id":"OfficeProducts", + "name":"Stationery and Office Products" + }, + { + "id":"PetSupplies", + "name":"Pet Supplies" + }, + { + "id":"Shoes", + "name":"Shoes & Bags" + }, + { + "id":"Software", + "name":"Software" + }, + { + "id":"SportsAndOutdoors", + "name":"Sports" + }, + { + "id":"ToolsAndHomeImprovement", + "name":"DIY, Tools & Garden" + }, + { + "id":"Toys", + "name":"Toys" + }, + { + "id":"VideoGames", + "name":"Computer & Video Games" + }, + { + "id":"Watches", + "name":"Watches" + } + ], + "com.mx":[ + { + "id":"Automotive", + "name":"Auto" + }, + { + "id":"Baby", + "name":"Beb\u00e9" + }, + { + "id":"Books", + "name":"Libros" + }, + { + "id":"Electronics", + "name":"Electr\u00f3nicos" + }, + { + "id":"Fashion", + "name":"Ropa, Zapatos y Accesorios" + }, + { + "id":"FashionBaby", + "name":"Ropa, Zapatos y Accesorios Beb\u00e9" + }, + { + "id":"FashionBoys", + "name":"Ropa, Zapatos y Accesorios Ni\u00f1os" + }, + { + "id":"FashionGirls", + "name":"Ropa, Zapatos y Accesorios Ni\u00f1as" + }, + { + "id":"FashionMen", + "name":"Ropa, Zapatos y Accesorios Hombres" + }, + { + "id":"FashionWomen", + "name":"Ropa, Zapatos y Accesorios Mujeres" + }, + { + "id":"GroceryAndGourmetFood", + "name":"Alimentos y Bebidas" + }, + { + "id":"Handmade", + "name":"Productos Handmade" + }, + { + "id":"HealthPersonalCare", + "name":"Salud, Belleza y Cuidado Personal" + }, + { + "id":"HomeAndKitchen", + "name":"Hogar y Cocina" + }, + { + "id":"IndustrialAndScientific", + "name":"Industria y ciencia" + }, + { + "id":"KindleStore", + "name":"Tienda Kindle" + }, + { + "id":"MoviesAndTV", + "name":"Pel\u00edculas y Series de TV" + }, + { + "id":"Music", + "name":"M\u00fasica" + }, + { + "id":"MusicalInstruments", + "name":"Instrumentos musicales" + }, + { + "id":"OfficeProducts", + "name":"Oficina y Papeler\u00eda" + }, + { + "id":"PetSupplies", + "name":"Mascotas" + }, + { + "id":"Software", + "name":"Software" + }, + { + "id":"SportsAndOutdoors", + "name":"Deportes y Aire Libre" + }, + { + "id":"ToolsAndHomeImprovement", + "name":"Herramientas y Mejoras del Hogar" + }, + { + "id":"ToysAndGames", + "name":"Juegos y juguetes" + }, + { + "id":"VideoGames", + "name":"Videojuegos" + }, + { + "id":"Watches", + "name":"Relojes" + } + ], + "nl":[ + { + "id":"Automotive", + "name":"Auto en motor" + }, + { + "id":"Baby", + "name":"Babyproducten" + }, + { + "id":"Beauty", + "name":"Beauty en persoonlijke verzorging" + }, + { + "id":"Books", + "name":"Boeken" + }, + { + "id":"Electronics", + "name":"Elektronica" + }, + { + "id":"EverythingElse", + "name":"Overig" + }, + { + "id":"Fashion", + "name":"Kleding, schoenen en sieraden" + }, + { + "id":"GardenAndOutdoor", + "name":"Tuin, terras en gazon" + }, + { + "id":"GiftCards", + "name":"Cadeaubonnen" + }, + { + "id":"GroceryAndGourmetFood", + "name":"Levensmiddelen" + }, + { + "id":"HealthPersonalCare", + "name":"Gezondheid en persoonlijke verzorging" + }, + { + "id":"HomeAndKitchen", + "name":"Wonen en keuken" + }, + { + "id":"Industrial", + "name":"Zakelijk, industrie en wetenschap" + }, + { + "id":"KindleStore", + "name":"Kindle Store" + }, + { + "id":"MoviesAndTV", + "name":"Films en tv" + }, + { + "id":"Music", + "name":"Cd's en lp's" + }, + { + "id":"MusicalInstruments", + "name":"Muziekinstrumenten" + }, + { + "id":"OfficeProducts", + "name":"Kantoorproducten" + }, + { + "id":"PetSupplies", + "name":"Huisdierbenodigdheden" + }, + { + "id":"Software", + "name":"Software" + }, + { + "id":"SportsAndOutdoors", + "name":"Sport en outdoor" + }, + { + "id":"ToolsAndHomeImprovement", + "name":"Klussen en gereedschap" + }, + { + "id":"ToysAndGames", + "name":"Speelgoed en spellen" + }, + { + "id":"VideoGames", + "name":"Videogames" + } + ], + "pl":[ + { + "id":"ArtsAndCrafts", + "name":"Arts & crafts" + }, + { + "id":"Automotive", + "name":"Motoryzacja" + }, + { + "id":"Baby", + "name":"Dziecko" + }, + { + "id":"Beauty", + "name":"Uroda" + }, + { + "id":"Books", + "name":"Ksi\u0105\u017cki" + }, + { + "id":"Electronics", + "name":"Elektronika" + }, + { + "id":"Fashion", + "name":"Odzie\u017c, obuwie i akcesoria" + }, + { + "id":"GardenAndOutdoor", + "name":"Ogr\u00f3d" + }, + { + "id":"GiftCards", + "name":"Karty podarunkowe" + }, + { + "id":"HealthPersonalCare", + "name":"Zdrowie i gospodarstwo domowe" + }, + { + "id":"HomeAndKitchen", + "name":"Dom i kuchnia" + }, + { + "id":"Industrial", + "name":"Biznes, przemys\u0142 i nauka" + }, + { + "id":"MoviesAndTV", + "name":"Filmy i programy TV" + }, + { + "id":"Music", + "name":"Muzyka" + }, + { + "id":"MusicalInstruments", + "name":"Instrumenty muzyczne" + }, + { + "id":"OfficeProducts", + "name":"Biuro" + }, + { + "id":"PetSupplies", + "name":"Zwierz\u0119ta" + }, + { + "id":"Software", + "name":"Oprogramowanie" + }, + { + "id":"SportsAndOutdoors", + "name":"Sport i turystyka" + }, + { + "id":"ToolsAndHomeImprovement", + "name":"Renowacja domu" + }, + { + "id":"ToysAndGames", + "name":"Zabawki i gry" + }, + { + "id":"VideoGames", + "name":"Gry wideo" + } + ], + "sg":[ + { + "id":"Automotive", + "name":"Automotive" + }, + { + "id":"Baby", + "name":"Baby" + }, + { + "id":"Beauty", + "name":"Beauty & Personal Care" + }, + { + "id":"Computers", + "name":"Computers" + }, + { + "id":"Electronics", + "name":"Electronics" + }, + { + "id":"GroceryAndGourmetFood", + "name":"Grocery" + }, + { + "id":"HealthPersonalCare", + "name":"Health, Household & Personal Care" + }, + { + "id":"HomeAndKitchen", + "name":"Home, Kitchen & Dining" + }, + { + "id":"OfficeProducts", + "name":"Office Products" + }, + { + "id":"PetSupplies", + "name":"Pet Supplies" + }, + { + "id":"SportsAndOutdoors", + "name":"Sports & Outdoors" + }, + { + "id":"ToolsAndHomeImprovement", + "name":"Tools & Home Improvement" + }, + { + "id":"ToysAndGames", + "name":"Toys & Games" + }, + { + "id":"VideoGames", + "name":"Video Games" + } + ], + "sa":[ + { + "id":"ArtsAndCrafts", + "name":"Arts, Crafts & Sewing" + }, + { + "id":"Automotive", + "name":"Automotive Parts & Accessories" + }, + { + "id":"Baby", + "name":"Baby" + }, + { + "id":"Beauty", + "name":"Beauty & Personal Care" + }, + { + "id":"Books", + "name":"Books" + }, + { + "id":"Computers", + "name":"Computer & Accessories" + }, + { + "id":"Electronics", + "name":"Electronics" + }, + { + "id":"Fashion", + "name":"Clothing, Shoes & Jewelry" + }, + { + "id":"GardenAndOutdoor", + "name":"Home & Garden" + }, + { + "id":"GiftCards", + "name":"Gift Cards" + }, + { + "id":"GroceryAndGourmetFood", + "name":"Grocery & Gourmet Food" + }, + { + "id":"HealthPersonalCare", + "name":"Health, Household & Baby Care" + }, + { + "id":"HomeAndKitchen", + "name":"Kitchen & Dining" + }, + { + "id":"Industrial", + "name":"Industrial & Scientific" + }, + { + "id":"KindleStore", + "name":"Kindle Store" + }, + { + "id":"Miscellaneous", + "name":"Everything Else" + }, + { + "id":"MoviesAndTV", + "name":"Movies & TV" + }, + { + "id":"Music", + "name":"CDs & Vinyl" + }, + { + "id":"MusicalInstruments", + "name":"Musical Instruments" + }, + { + "id":"OfficeProducts", + "name":"Office Products" + }, + { + "id":"PetSupplies", + "name":"Pet Supplies" + }, + { + "id":"Software", + "name":"Software" + }, + { + "id":"SportsAndOutdoors", + "name":"Sports" + }, + { + "id":"ToolsAndHomeImprovement", + "name":"Tools & Home Improvement" + }, + { + "id":"ToysAndGames", + "name":"Toys & Games" + }, + { + "id":"VideoGames", + "name":"Video Games" + } + ], + "es":[ + { + "id":"AmazonVideo", + "name":"Prime Video" + }, + { + "id":"Apparel", + "name":"Ropa y accesorios" + }, + { + "id":"Appliances", + "name":"Grandes electrodom\u00e9sticos" + }, + { + "id":"Automotive", + "name":"Coche y moto" + }, + { + "id":"Baby", + "name":"Beb\u00e9" + }, + { + "id":"Beauty", + "name":"Belleza" + }, + { + "id":"Books", + "name":"Libros" + }, + { + "id":"Computers", + "name":"Inform\u00e1tica" + }, + { + "id":"DigitalMusic", + "name":"M\u00fasica Digital" + }, + { + "id":"Electronics", + "name":"Electr\u00f3nica" + }, + { + "id":"EverythingElse", + "name":"Otros Productos" + }, + { + "id":"Fashion", + "name":"Moda" + }, + { + "id":"ForeignBooks", + "name":"Libros en idiomas extranjeros" + }, + { + "id":"GardenAndOutdoor", + "name":"Jard\u00edn" + }, + { + "id":"GiftCards", + "name":"Cheques regalo" + }, + { + "id":"GroceryAndGourmetFood", + "name":"Alimentaci\u00f3n y bebidas" + }, + { + "id":"Handmade", + "name":"Handmade" + }, + { + "id":"HealthPersonalCare", + "name":"Salud y cuidado personal" + }, + { + "id":"HomeAndKitchen", + "name":"Hogar y cocina" + }, + { + "id":"Industrial", + "name":"Industria y ciencia" + }, + { + "id":"Jewelry", + "name":"Joyer\u00eda" + }, + { + "id":"KindleStore", + "name":"Tienda Kindle" + }, + { + "id":"Lighting", + "name":"Iluminaci\u00f3n" + }, + { + "id":"Luggage", + "name":"Equipaje" + }, + { + "id":"MobileApps", + "name":"Appstore para Android" + }, + { + "id":"MoviesAndTV", + "name":"Pel\u00edculas y TV" + }, + { + "id":"Music", + "name":"M\u00fasica: CDs y vinilos" + }, + { + "id":"MusicalInstruments", + "name":"Instrumentos musicales" + }, + { + "id":"OfficeProducts", + "name":"Oficina y papeler\u00eda" + }, + { + "id":"PetSupplies", + "name":"Productos para mascotas" + }, + { + "id":"Shoes", + "name":"Zapatos y complementos" + }, + { + "id":"Software", + "name":"Software" + }, + { + "id":"SportsAndOutdoors", + "name":"Deportes y aire libre" + }, + { + "id":"ToolsAndHomeImprovement", + "name":"Bricolaje y herramientas" + }, + { + "id":"ToysAndGames", + "name":"Juguetes y juegos" + }, + { + "id":"Vehicles", + "name":"Coche - renting" + }, + { + "id":"VideoGames", + "name":"Videojuegos" + }, + { + "id":"Watches", + "name":"Relojes" + } + ], + "se":[ + { + "id":"Automotive", + "name":"Delar och tillbeh\u00f6r till bilar" + }, + { + "id":"Baby", + "name":"Baby" + }, + { + "id":"Beauty", + "name":"Sk\u00f6nhet och kroppsv\u00e5rd" + }, + { + "id":"Books", + "name":"B\u00f6cker" + }, + { + "id":"Electronics", + "name":"Elektronik" + }, + { + "id":"Fashion", + "name":"Kl\u00e4der, skor och smycken" + }, + { + "id":"GroceryAndGourmetFood", + "name":"Livsmedel och gourmetmat" + }, + { + "id":"HealthPersonalCare", + "name":"H\u00e4lsa, hush\u00e5ll och barnv\u00e5rd" + }, + { + "id":"HomeAndKitchen", + "name":"Hem" + }, + { + "id":"MoviesAndTV", + "name":"Filmer och TV" + }, + { + "id":"Music", + "name":"CD och vinyl" + }, + { + "id":"OfficeProducts", + "name":"Kontorsprodukter" + }, + { + "id":"PetSupplies", + "name":"Husdjursprodukter" + }, + { + "id":"SportsAndOutdoors", + "name":"Sport och outdoor" + }, + { + "id":"ToolsAndHomeImprovement", + "name":"Verktyg och husrenovering" + }, + { + "id":"ToysAndGames", + "name":"Leksaker och spel" + }, + { + "id":"VideoGames", + "name":"Videospel" + } + ], + "com.tr":[ + { + "id":"Baby", + "name":"Bebek" + }, + { + "id":"Books", + "name":"Kitaplar" + }, + { + "id":"Computers", + "name":"Bilgisayarlar" + }, + { + "id":"Electronics", + "name":"Elektronik" + }, + { + "id":"EverythingElse", + "name":"Di\u011fer Her \u015eey" + }, + { + "id":"Fashion", + "name":"Moda" + }, + { + "id":"HomeAndKitchen", + "name":"Ev ve Mutfak" + }, + { + "id":"OfficeProducts", + "name":"Ofis \u00dcr\u00fcnleri" + }, + { + "id":"SportsAndOutdoors", + "name":"Spor" + }, + { + "id":"ToolsAndHomeImprovement", + "name":"Yap\u0131 Market" + }, + { + "id":"ToysAndGames", + "name":"Oyuncaklar ve Oyunlar" + }, + { + "id":"VideoGames", + "name":"PC ve Video Oyunlar\u0131" + } + ], + "ae":[ + { + "id":"Appliances", + "name":"Appliances" + }, + { + "id":"ArtsAndCrafts", + "name":"Arts, Crafts & Sewing" + }, + { + "id":"Automotive", + "name":"Automotive Parts & Accessories" + }, + { + "id":"Baby", + "name":"Baby" + }, + { + "id":"Beauty", + "name":"Beauty & Personal Care" + }, + { + "id":"Books", + "name":"Books" + }, + { + "id":"Computers", + "name":"Computers" + }, + { + "id":"Electronics", + "name":"Electronics" + }, + { + "id":"EverythingElse", + "name":"Everything Else" + }, + { + "id":"Fashion", + "name":"Clothing, Shoes & Jewelry" + }, + { + "id":"GardenAndOutdoor", + "name":"Home & Garden" + }, + { + "id":"GroceryAndGourmetFood", + "name":"Grocery & Gourmet Food" + }, + { + "id":"HealthPersonalCare", + "name":"Health, Household & Baby Care" + }, + { + "id":"HomeAndKitchen", + "name":"Home & Kitchen" + }, + { + "id":"Industrial", + "name":"Industrial & Scientific" + }, + { + "id":"Lighting", + "name":"Lighting" + }, + { + "id":"MusicalInstruments", + "name":"Musical Instruments" + }, + { + "id":"OfficeProducts", + "name":"Office Products" + }, + { + "id":"PetSupplies", + "name":"Pet Supplies" + }, + { + "id":"Software", + "name":"Software" + }, + { + "id":"SportsAndOutdoors", + "name":"Sports" + }, + { + "id":"ToolsAndHomeImprovement", + "name":"Tools & Home Improvement" + }, + { + "id":"ToysAndGames", + "name":"Toys & Games" + }, + { + "id":"VideoGames", + "name":"Video Games" + } + ], + "co.uk":[ + { + "id":"AmazonVideo", + "name":"Amazon Video" + }, + { + "id":"Apparel", + "name":"Clothing" + }, + { + "id":"Appliances", + "name":"Large Appliances" + }, + { + "id":"Automotive", + "name":"Car & Motorbike" + }, + { + "id":"Baby", + "name":"Baby" + }, + { + "id":"Beauty", + "name":"Beauty" + }, + { + "id":"Books", + "name":"Books" + }, + { + "id":"Classical", + "name":"Classical Music" + }, + { + "id":"Computers", + "name":"Computers & Accessories" + }, + { + "id":"DigitalMusic", + "name":"Digital Music" + }, + { + "id":"Electronics", + "name":"Electronics & Photo" + }, + { + "id":"EverythingElse", + "name":"Everything Else" + }, + { + "id":"Fashion", + "name":"Fashion" + }, + { + "id":"GardenAndOutdoor", + "name":"Garden & Outdoors" + }, + { + "id":"GiftCards", + "name":"Gift Cards" + }, + { + "id":"GroceryAndGourmetFood", + "name":"Grocery" + }, + { + "id":"Handmade", + "name":"Handmade" + }, + { + "id":"HealthPersonalCare", + "name":"Health & Personal Care" + }, + { + "id":"HomeAndKitchen", + "name":"Home & Kitchen" + }, + { + "id":"Industrial", + "name":"Industrial & Scientific" + }, + { + "id":"Jewelry", + "name":"Jewellery" + }, + { + "id":"KindleStore", + "name":"Kindle Store" + }, + { + "id":"Lighting", + "name":"Lighting" + }, + { + "id":"Luggage", + "name":"Luggage" + }, + { + "id":"LuxuryBeauty", + "name":"Luxury Beauty" + }, + { + "id":"MobileApps", + "name":"Apps & Games" + }, + { + "id":"MoviesAndTV", + "name":"DVD & Blu-ray" + }, + { + "id":"Music", + "name":"CDs & Vinyl" + }, + { + "id":"MusicalInstruments", + "name":"Musical Instruments & DJ" + }, + { + "id":"OfficeProducts", + "name":"Stationery & Office Supplies" + }, + { + "id":"PetSupplies", + "name":"Pet Supplies" + }, + { + "id":"Shoes", + "name":"Shoes & Bags" + }, + { + "id":"Software", + "name":"Software" + }, + { + "id":"SportsAndOutdoors", + "name":"Sports & Outdoors" + }, + { + "id":"ToolsAndHomeImprovement", + "name":"DIY & Tools" + }, + { + "id":"ToysAndGames", + "name":"Toys & Games" + }, + { + "id":"VHS", + "name":"VHS" + }, + { + "id":"VideoGames", + "name":"PC & Video Games" + }, + { + "id":"Watches", + "name":"Watches" + } + ], + "com":[ + { + "id":"AmazonVideo", + "name":"Prime Video" + }, + { + "id":"Apparel", + "name":"Clothing & Accessories" + }, + { + "id":"Appliances", + "name":"Appliances" + }, + { + "id":"ArtsAndCrafts", + "name":"Arts, Crafts & Sewing" + }, + { + "id":"Automotive", + "name":"Automotive Parts & Accessories" + }, + { + "id":"Baby", + "name":"Baby" + }, + { + "id":"Beauty", + "name":"Beauty & Personal Care" + }, + { + "id":"Books", + "name":"Books" + }, + { + "id":"Classical", + "name":"Classical" + }, + { + "id":"Collectibles", + "name":"Collectibles & Fine Art" + }, + { + "id":"Computers", + "name":"Computers" + }, + { + "id":"DigitalMusic", + "name":"Digital Music" + }, + { + "id":"DigitalEducationalResources", + "name":"Digital Educational Resources" + }, + { + "id":"Electronics", + "name":"Electronics" + }, + { + "id":"EverythingElse", + "name":"Everything Else" + }, + { + "id":"Fashion", + "name":"Clothing, Shoes & Jewelry" + }, + { + "id":"FashionBaby", + "name":"Clothing, Shoes & Jewelry Baby" + }, + { + "id":"FashionBoys", + "name":"Clothing, Shoes & Jewelry Boys" + }, + { + "id":"FashionGirls", + "name":"Clothing, Shoes & Jewelry Girls" + }, + { + "id":"FashionMen", + "name":"Clothing, Shoes & Jewelry Men" + }, + { + "id":"FashionWomen", + "name":"Clothing, Shoes & Jewelry Women" + }, + { + "id":"GardenAndOutdoor", + "name":"Garden & Outdoor" + }, + { + "id":"GiftCards", + "name":"Gift Cards" + }, + { + "id":"GroceryAndGourmetFood", + "name":"Grocery & Gourmet Food" + }, + { + "id":"Handmade", + "name":"Handmade" + }, + { + "id":"HealthPersonalCare", + "name":"Health, Household & Baby Care" + }, + { + "id":"HomeAndKitchen", + "name":"Home & Kitchen" + }, + { + "id":"Industrial", + "name":"Industrial & Scientific" + }, + { + "id":"Jewelry", + "name":"Jewelry" + }, + { + "id":"KindleStore", + "name":"Kindle Store" + }, + { + "id":"LocalServices", + "name":"Home & Business Services" + }, + { + "id":"Luggage", + "name":"Luggage & Travel Gear" + }, + { + "id":"LuxuryBeauty", + "name":"Luxury Beauty" + }, + { + "id":"Magazines", + "name":"Magazine Subscriptions" + }, + { + "id":"MobileAndAccessories", + "name":"Cell Phones & Accessories" + }, + { + "id":"MobileApps", + "name":"Apps & Games" + }, + { + "id":"MoviesAndTV", + "name":"Movies & TV" + }, + { + "id":"Music", + "name":"CDs & Vinyl" + }, + { + "id":"MusicalInstruments", + "name":"Musical Instruments" + }, + { + "id":"OfficeProducts", + "name":"Office Products" + }, + { + "id":"PetSupplies", + "name":"Pet Supplies" + }, + { + "id":"Photo", + "name":"Camera & Photo" + }, + { + "id":"Shoes", + "name":"Shoes" + }, + { + "id":"Software", + "name":"Software" + }, + { + "id":"SportsAndOutdoors", + "name":"Sports & Outdoors" + }, + { + "id":"ToolsAndHomeImprovement", + "name":"Tools & Home Improvement" + }, + { + "id":"ToysAndGames", + "name":"Toys & Games" + }, + { + "id":"VHS", + "name":"VHS" + }, + { + "id":"VideoGames", + "name":"Video Games" + }, + { + "id":"Watches", + "name":"Watches" + } + ] +} \ No newline at end of file diff --git a/src/includes/bin/countries.json b/src/includes/bin/countries.json new file mode 100644 index 0000000..6210018 --- /dev/null +++ b/src/includes/bin/countries.json @@ -0,0 +1,23 @@ +{ + "com.au":"Australia", + "com.be":"Belgium", + "com.br":"Brazil", + "ca":"Canada", + "eg":"Egypt", + "fr":"France", + "de":"Germany", + "in":"India", + "it":"Italy", + "co.jp":"Japan", + "com.mx":"Mexico", + "nl":"Netherlands", + "pl":"Poland", + "sa":"Saudi Arabia", + "sg":"Singapore", + "es":"Spain", + "se":"Sweden", + "com.tr":"Turkey", + "ae":"United Arab Emirates", + "co.uk":"United Kingdom", + "com":"United States" +} \ No newline at end of file diff --git a/src/includes/bin/currencies.json b/src/includes/bin/currencies.json new file mode 100644 index 0000000..a5080f9 --- /dev/null +++ b/src/includes/bin/currencies.json @@ -0,0 +1,129 @@ +{ + "com.au":[ + "AUD" + ], + "com.be":[ + "EUR" + ], + "com.br":[ + "BRL" + ], + "ca":[ + "CAD" + ], + "eg":[ + "EGP" + ], + "fr":[ + "EUR" + ], + "de":[ + "EUR" + ], + "in":[ + "INR" + ], + "it":[ + "EUR" + ], + "co.jp":[ + "JPY" + ], + "com.mx":[ + "MXN" + ], + "nl":[ + "EUR" + ], + "pl":[ + "PLN" + ], + "sg":[ + "SGD" + ], + "sa":[ + "SAR" + ], + "es":[ + "EUR" + ], + "se":[ + "SEK" + ], + "com.tr":[ + "TRY" + ], + "ae":[ + "AED" + ], + "co.uk":[ + "GBP" + ], + "com":[ + "AED", + "AMD", + "ARS", + "AUD", + "AWG", + "AZN", + "BGN", + "BND", + "BOB", + "BRL", + "BSD", + "BZD", + "CAD", + "CLP", + "CNY", + "COP", + "CRC", + "DOP", + "EGP", + "EUR", + "GBP", + "GHS", + "GTQ", + "HKD", + "HNL", + "HUF", + "IDR", + "ILS", + "INR", + "JMD", + "JPY", + "KES", + "KHR", + "KRW", + "KYD", + "KZT", + "LBP", + "MAD", + "MNT", + "MOP", + "MUR", + "MXN", + "MYR", + "NAD", + "NGN", + "NOK", + "NZD", + "PAB", + "PEN", + "PHP", + "PYG", + "QAR", + "RUB", + "SAR", + "SGD", + "THB", + "TRY", + "TTD", + "TWD", + "TZS", + "USD", + "UYU", + "VND", + "XCD", + "ZAR" + ] +} \ No newline at end of file diff --git a/src/includes/bin/languages.json b/src/includes/bin/languages.json new file mode 100644 index 0000000..f121949 --- /dev/null +++ b/src/includes/bin/languages.json @@ -0,0 +1,89 @@ +{ + "com.au": [ + "en_AU" + ], + "com.be": [ + "fr_BE", + "nl_BE", + "en_GB" + ], + "com.br": [ + "pt_BR" + ], + "ca": [ + "en_CA", + "fr_CA" + ], + "eg": [ + "en_AE", + "ar_AE" + ], + "fr": [ + "fr_FR" + ], + "de": [ + "cs_CZ", + "de_DE", + "en_GB", + "nl_NL", + "pl_PL", + "tr_TR" + ], + "in": [ + "en_IN", + "hi_IN", + "kn_IN", + "ml_IN", + "ta_IN", + "te_IN" + ], + "it": [ + "it_IT" + ], + "co.jp": [ + "en_US", + "ja_JP", + "zh_CN" + ], + "com.mx": [ + "es_MX" + ], + "nl": [ + "nl_NL" + ], + "pl": [ + "pl_PL" + ], + "sg": [ + "en_SG" + ], + "sa": [ + "en_AE", + "ar_AE" + ], + "es": [ + "es_ES" + ], + "se": [ + "sv_SE" + ], + "com.tr": [ + "tr_TR" + ], + "ae": [ + "en_AE", + "ar_AE" + ], + "co.uk": [ + "en_GB" + ], + "com": [ + "de_DE", + "en_US", + "es_US", + "ko_KR", + "pt_BR", + "zh_CN", + "zh_TW" + ] +} \ No newline at end of file diff --git a/src/includes/bin/regions.json b/src/includes/bin/regions.json new file mode 100644 index 0000000..01fb6c5 --- /dev/null +++ b/src/includes/bin/regions.json @@ -0,0 +1,29 @@ +{ + "eu-west-1":[ + "ae", + "co.uk", + "com.be", + "com.tr", + "de", + "eg", + "es", + "fr", + "in", + "it", + "nl", + "pl", + "sa", + "se" + ], + "us-east-1":[ + "ca", + "com", + "com.br", + "com.mx" + ], + "us-west-2":[ + "co.jp", + "com.au", + "sg" + ] +} \ No newline at end of file diff --git a/src/includes/bin/symbols.json b/src/includes/bin/symbols.json new file mode 100644 index 0000000..d5a8657 --- /dev/null +++ b/src/includes/bin/symbols.json @@ -0,0 +1,18 @@ +{ + "AED":"DH", + "AUD":"$", + "BRL":"R$", + "CAD":"$", + "EGP":"\u00a3E", + "EUR":"\u20ac", + "GBP":"\u00a3", + "INR":"\u20b9", + "JPY":"\u00a5", + "MXN":"$", + "PLN":"z\u0142", + "SAR":"SR", + "SEK":"kr", + "SGD":"S$", + "TRY":"\u20ba", + "USD":"$" +} \ No newline at end of file diff --git a/src/interfaces/CartInterface.php b/src/interfaces/CartInterface.php new file mode 100644 index 0000000..28209b3 --- /dev/null +++ b/src/interfaces/CartInterface.php @@ -0,0 +1,44 @@ + + * @link : https://jakiboy.github.io/apaapi/ + * @license : MIT + * + * This file if a part of Apaapi Lib. + */ + +namespace Apaapi\interfaces; + +/** + * Cart interface. + */ +interface CartInterface +{ + /** + * Set request locale. + * + * @param string $locale + * @return object + * @throws RequestException + */ + function setLocale(string $locale) : object; + + /** + * Set partner tag. + * + * @param string $tag + * @return object + */ + function setPartnerTag(string $tag) : object; + + /** + * Set cart items. + * + * @param array $items + * @return string + */ + function set(array $items) : string; +} diff --git a/src/interfaces/ClientInterface.php b/src/interfaces/ClientInterface.php new file mode 100644 index 0000000..cc927f5 --- /dev/null +++ b/src/interfaces/ClientInterface.php @@ -0,0 +1,80 @@ + + * @link : https://jakiboy.github.io/apaapi/ + * @license : MIT + * + * This file if a part of Apaapi Lib. + */ + +namespace Apaapi\interfaces; + +/** + * Apaapi request client interface. + */ +interface ClientInterface +{ + /** + * Setup client. + * + * @param string $endpoint + * @param array $params + */ + function __construct(string $endpoint, array $params = []); + + /** + * Set HTTP request method. + * + * @param string $method + * @return object + */ + function setMethod(string $method) : self; + + /** + * Set HTTP response timeout. + * + * @param int $timeout + * @return object + */ + function setTimeout(int $timeout) : self; + + /** + * Set HTTP redirect location. + * + * @param int $redirect + * @return object + */ + function setRedirect(int $redirect) : self; + + /** + * Set HTTP encoding. + * + * @param string $encoding + * @return object + */ + function setEncoding(string $encoding) : self; + + /** + * Get HTTP response content. + * + * @return string + */ + function getResponse() : string; + + /** + * Get HTTP response code. + * + * @return int + */ + function getCode() : int; + + /** + * Close request handler. + * + * @return void + */ + function close(); +} diff --git a/src/interfaces/ItemOperationInterface.php b/src/interfaces/ItemOperationInterface.php index 66fd44c..452f5f3 100644 --- a/src/interfaces/ItemOperationInterface.php +++ b/src/interfaces/ItemOperationInterface.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -13,37 +13,47 @@ namespace Apaapi\interfaces; /** - * Interface Group Items Operations. + * Group items operations. */ interface ItemOperationInterface { /** + * Set condition. + * * @param string $condition * @return object */ - function setCondition($condition); + function setCondition(string $condition) : object; /** - * @param string $currencyOfPreference + * Set currency. + * + * @param string $currency * @return object */ - function setCurrency($currencyOfPreference); + function setCurrency(string $currency) : object; /** + * Set merchant. + * * @param string $merchant * @return object */ - function setMerchant($merchant); + function setMerchant(string $merchant) : object; /** - * @param string $offerCount + * Set offer count. + * + * @param int $count * @return object */ - function setOfferCount($offerCount); + function setOfferCount(int $count) : object; /** + * Set properties. + * * @param string $properties * @return object */ - function setProperties($properties); + function setProperties(string $properties) : object; } diff --git a/src/interfaces/OperationInterface.php b/src/interfaces/OperationInterface.php index 2c32057..77fb0e0 100644 --- a/src/interfaces/OperationInterface.php +++ b/src/interfaces/OperationInterface.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -13,37 +13,49 @@ namespace Apaapi\interfaces; /** - * Interface Group All Operations. + * Group all operations. */ interface OperationInterface extends ParsableInterface { - /** - * @param string $type - * @return object - */ - function setPartnerType($type); - /** - * @param string $tag + * Set partner type. + * + * @param string $type * @return object */ - function setPartnerTag($tag); + function setPartnerType(string $type) : object; + + /** + * Set partner tag. + * + * @param string $tag + * @return object + */ + function setPartnerTag(string $tag) : object; /** + * Set resources. + * * @param array $resources + * @param bool $throwable * @return object + * @throws OperationException */ - function setResources($resources); + function setResources(array $resources, bool $throwable = true) : object; /** + * Set languages of preference. + * * @param array $languagesOfPreference * @return object */ - function setLanguages($languagesOfPreference); + function setLanguages(array $languagesOfPreference) : object; /** + * Set marketplace. + * * @param string $marketplace * @return object */ - function setMarketplace($marketplace); + function setMarketplace(string $marketplace) : object; } diff --git a/src/interfaces/ParsableInterface.php b/src/interfaces/ParsableInterface.php index b071396..5c3d12b 100644 --- a/src/interfaces/ParsableInterface.php +++ b/src/interfaces/ParsableInterface.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -13,6 +13,6 @@ namespace Apaapi\interfaces; /** - * Basic Apaapi Resource Interface. + * Apaapi parsable interface. */ interface ParsableInterface {} diff --git a/src/interfaces/RequestClientInterface.php b/src/interfaces/RequestClientInterface.php deleted file mode 100644 index 215ef5e..0000000 --- a/src/interfaces/RequestClientInterface.php +++ /dev/null @@ -1,43 +0,0 @@ - - * @link : https://jakiboy.github.io/apaapi/ - * @license : MIT - * - * This file if a part of Apaapi Lib. - */ - -namespace Apaapi\interfaces; - -/** - * Basic Apaapi Request Client Interface. - */ -interface RequestClientInterface -{ - /** - * @param string $endpoint - * @param array $params - */ - function __construct($endpoint, $params); - - /** - * @param void - * @return mixed - */ - function getResponse(); - - /** - * @param void - * @return int - */ - function getCode(); - - /** - * @param void - * @return void - */ - function close(); -} diff --git a/src/interfaces/RequestInterface.php b/src/interfaces/RequestInterface.php index 69b78c3..b5fd1c6 100644 --- a/src/interfaces/RequestInterface.php +++ b/src/interfaces/RequestInterface.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -13,50 +13,63 @@ namespace Apaapi\interfaces; /** - * Basic Apaapi Request Interface. + * Apaapi request interface. */ interface RequestInterface { - /** - * @param string $accessKeyID - * @param string $secretAccessKey - */ - function __construct($accessKeyID = '', $secretAccessKey = ''); + /** + * Prepare request. + * @see https://webservices.amazon.com/paapi5/scratchpad/index.html + * + * @param string $accessKeyID + * @param string $secretAccessKey + */ + function __construct(string $accessKeyID, string $secretAccessKey); /** - * @param void + * Get request client. + * * @return object */ - function getClient(); + function getClient() : object; /** - * @param void + * Get request endpoint. + * * @return string */ - function getEndpoint(); + function getEndpoint() : string; /** - * @param void + * Get request parameters. + * * @return array */ - function getParams(); + function getParams() : array; /** - * @param void + * Get request operation. + * * @return string */ - function getOperation(); + function getOperation() : string; /** + * Set request header. + * * @param string $name - * @param string $value + * @param mixed $value * @return void */ - function setRequestHeader($name, $value); + function setRequestHeader(string $name, $value); /** + * Set request locale. + * @see https://webservices.amazon.fr/paapi5/documentation/locale-reference.html + * * @param string $locale * @return object + * @throws RequestException */ - function setLocale($locale); + function setLocale(string $locale) : object; } diff --git a/src/interfaces/ResourceInterface.php b/src/interfaces/ResourceInterface.php index 58c10ab..1adfe81 100644 --- a/src/interfaces/ResourceInterface.php +++ b/src/interfaces/ResourceInterface.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -13,6 +13,6 @@ namespace Apaapi\interfaces; /** - * Basic Apaapi Resource Interface. + * Apaapi resource interface. */ interface ResourceInterface extends ParsableInterface {} diff --git a/src/interfaces/ResponseInterface.php b/src/interfaces/ResponseInterface.php index 2a3a9b1..fa0b6ba 100644 --- a/src/interfaces/ResponseInterface.php +++ b/src/interfaces/ResponseInterface.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -13,14 +13,45 @@ namespace Apaapi\interfaces; /** - * Basic Apaapi Response Interface. + * Apaapi response interface. */ interface ResponseInterface { /** + * Send request then get response. + * * @param RequestInterface $request - * @param ResponseTypeInterface $type - * @param bool $parse + * @param bool $normalize + * @param bool $cache */ - public function __construct(RequestInterface $request, ResponseTypeInterface $type = null, $parse = null); + function __construct(RequestInterface $request, bool $normalize = false, bool $cache = true); + + /** + * Get response data. + * + * @param array $geo + * @return array + */ + function get(?array $geo = null) : array; + + /** + * Get response body. + * + * @return string + */ + function getBody() : string; + + /** + * Get response error. + * + * @return string + */ + function getError() : string; + + /** + * Check response error. + * + * @return bool + */ + function hasError() : bool; } diff --git a/src/interfaces/ResponseParseInterface.php b/src/interfaces/ResponseParseInterface.php deleted file mode 100644 index a37e556..0000000 --- a/src/interfaces/ResponseParseInterface.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @link : https://jakiboy.github.io/apaapi/ - * @license : MIT - * - * This file if a part of Apaapi Lib. - */ - -namespace Apaapi\interfaces; - -/** - * Basic Apaapi Response Interface. - */ -interface ResponseParseInterface -{ - /** - * @param RequestInterface $request - * @param array $params - */ - function __construct(RequestInterface $request, $params = []); -} diff --git a/src/interfaces/ResponseTypeInterface.php b/src/interfaces/ResponseTypeInterface.php deleted file mode 100644 index e231cae..0000000 --- a/src/interfaces/ResponseTypeInterface.php +++ /dev/null @@ -1,37 +0,0 @@ - - * @link : https://jakiboy.github.io/apaapi/ - * @license : MIT - * - * This file if a part of Apaapi Lib. - */ - -namespace Apaapi\interfaces; - -/** - * Basic Apaapi Response Interface. - */ -interface ResponseTypeInterface -{ - /** - * @param string $type - */ - function __construct($type = 'Object'); - - /** - * @param string $response - * @return mixed - */ - function format($response); - - /** - * @param object $response - * @param string $operation - * @return mixed - */ - function parse($response, $operation); -} diff --git a/src/lib/Cart.php b/src/lib/Cart.php index db9997a..502ed45 100644 --- a/src/lib/Cart.php +++ b/src/lib/Cart.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -13,74 +13,67 @@ namespace Apaapi\lib; use Apaapi\exceptions\RequestException; +use Apaapi\interfaces\CartInterface; +use Apaapi\includes\{ + Provider, + Normalizer +}; /** - * Basic Apaapi Cart Wrapper Class. + * Basic Apaapi cart wrapper class. * @see https://webservices.amazon.com/paapi5/documentation/add-to-cart-form.html */ -final class Cart +class Cart implements CartInterface { /** * @access public - * @var string HOST, API Host - * @var string ENDPOINT, API Endpoint + * @var string ENDPOINT, Dynamic API endpoint */ - const HOST = 'https://www.amazon.{locale}'; - const ENDPOINT = '/gp/aws/cart/add.html?AssociateTag={tag}'; + public const ENDPOINT = '/gp/aws/cart/add.html?AssociateTag={tag}'; /** - * @access private + * @access protected * @var mixed $locale * @var string $tag + * @var int $limit, Items limit */ - private $locale = false; - private $tag; + protected $locale = false; + protected $tag; + protected static $limit = 5; /** - * Set request locale. - * - * @access public - * @param string $locale - * @return object - * @throws RequestException + * @inheritdoc */ - public function setLocale($locale) + public function setLocale(string $locale) : object { - $locale = strtolower($locale); - if ( in_array($locale, $this->getRegions()) ) { - $this->locale = $locale; - } + $this->locale = Normalizer::formatLocale($locale); if ( !$this->locale ) { throw new RequestException( - RequestException::invalidRequestLocaleMessage($locale) + RequestException::invalidLocale($locale) ); } return $this; } /** - * Set request tag. - * - * @access public - * @param string $tag - * @return object + * @inheritdoc */ - public function setPartnerTag($tag) + public function setPartnerTag(string $tag) : object { $this->tag = $tag; return $this; } /** - * Add items to cart. - * - * @access public - * @param array $items - * @return string + * @inheritdoc */ - public function add($items = []) + public function set(array $items) : string { - $url = self::HOST . self::ENDPOINT; + if ( static::$limit ) { + $items = array_slice($items, 0, static::$limit); + } + + $url = Provider::HOST . self::ENDPOINT; $url = str_replace('{locale}', $this->locale, $url); $url = str_replace('{tag}', $this->tag, $url); $i = 0; @@ -92,18 +85,14 @@ public function add($items = []) } /** - * Get request regions. - * - * @access private - * @param void - * @return array - */ - private function getRegions() - { - return [ - 'fr','com.be','de','in','it','es','nl','pl','com.tr','ae','sa','co.uk','se','eg', - 'com','com.br','ca','com.mx', - 'com.au','co.jp','sg' - ]; - } + * Set limit. + * + * @access public + * @param int $limit + * @return void + */ + public static function limit(int $limit) + { + self::$limit = $limit; + } } diff --git a/src/lib/ItemOperation.php b/src/lib/ItemOperation.php index aba24bb..9b9de82 100644 --- a/src/lib/ItemOperation.php +++ b/src/lib/ItemOperation.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -15,10 +15,9 @@ use Apaapi\interfaces\ItemOperationInterface; /** - * Basic Apaapi Grouped Item Operation Wrapper Class. + * Apaapi item operation wrapper class. */ -class ItemOperation extends Operation -implements ItemOperationInterface +class ItemOperation extends Operation implements ItemOperationInterface { /** * @access public @@ -35,55 +34,45 @@ class ItemOperation extends Operation public $properties = null; /** - * @access public - * @param string $condition - * @return object + * @inheritdoc */ - public function setCondition($condition) + public function setCondition(string $condition) : object { $this->condition = $condition; return $this; } /** - * @access public - * @param string $currencyOfPreference - * @return object + * @inheritdoc */ - public function setCurrency($currencyOfPreference) + public function setCurrency(string $currency) : object { - $this->currencyOfPreference = $currencyOfPreference; + $this->currencyOfPreference = $currency; return $this; } /** - * @access public - * @param string $merchant - * @return object + * @inheritdoc */ - public function setMerchant($merchant) + public function setMerchant(string $merchant) : object { $this->merchant = $merchant; return $this; } /** - * @access public - * @param string $offerCount - * @return object + * @inheritdoc */ - public function setOfferCount($offerCount) + public function setOfferCount(int $count) : object { - $this->offerCount = $offerCount; + $this->offerCount = $count; return $this; } /** - * @access public - * @param string $properties - * @return object + * @inheritdoc */ - public function setProperties($properties) + public function setProperties(string $properties) : object { $this->properties = $properties; return $this; diff --git a/src/lib/Operation.php b/src/lib/Operation.php index 5765fe8..46badfc 100644 --- a/src/lib/Operation.php +++ b/src/lib/Operation.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -12,99 +12,95 @@ namespace Apaapi\lib; -use Apaapi\exceptions\OperationException; use Apaapi\interfaces\OperationInterface; +use Apaapi\exceptions\OperationException; /** - * Basic Apaapi All Operation Wrapper Class. + * Apaapi operation wrapper class. */ class Operation implements OperationInterface { /** * @access public - * @var string $partnerType * @var array $resources + * @var string $partnerType * @var string $partnerTag - * @var array $marketplace + * @var string $marketplace * @var array $languagesOfPreference */ - public $partnerType = 'Associates'; public $resources = []; + public $partnerType = 'Associates'; public $partnerTag = null; public $marketplace = null; public $languagesOfPreference = []; - /** - * @access public - * @param string $type - * @return object - */ - public function setPartnerType($type) + /** + * @inheritdoc + */ + public function setPartnerType(string $type) : object { $this->partnerType = $type; return $this; } /** - * @access public - * @param string $tag - * @return object + * @inheritdoc */ - public function setPartnerTag($tag) + public function setPartnerTag(string $tag) : object { $this->partnerTag = $tag; return $this; } - /** - * @access public - * @param array $resources - * @return object - * @throws OperationException - */ - public function setResources($resources = []) + /** + * @inheritdoc + */ + public function setResources(array $resources, bool $throwable = true) : object { - if ( ($ressource = $this->isValidResources($resources)) !== true ) { - throw new OperationException( - OperationException::invalidOperationRessource($ressource) - ); + if ( $throwable ) { + if ( ($ressource = $this->isValidResources($resources)) !== true ) { + throw new OperationException( + OperationException::invalidRessources($ressource) + ); + } } - $this->resources = !empty($resources) - ? $resources : $this->resources; + + if ( !empty($resources) ) { + $this->resources = $resources; + } + return $this; } /** - * @access public - * @param array $languagesOfPreference - * @return object + * @inheritdoc */ - public function setLanguages($languagesOfPreference) + public function setLanguages(array $languages) : object { - $this->languagesOfPreference = (array)$languagesOfPreference; + $this->languagesOfPreference = $languages; return $this; } /** - * @access public - * @param string $marketplace - * @return object + * @inheritdoc */ - public function setMarketplace($marketplace) + public function setMarketplace(string $marketplace) : object { $this->marketplace = $marketplace; return $this; } /** + * Check valid resources. + * * @access private * @param array $resources * @return mixed */ - private function isValidResources($resources = []) + private function isValidResources(array $resources) { - foreach ((array)$resources as $resource) { - if ( !in_array($resource,$this->resources) ) { + foreach ($resources as $resource) { + if ( !in_array($resource, $this->resources) ) { return $resource; } } diff --git a/src/lib/Request.php b/src/lib/Request.php index ac9dbf2..c98bfc3 100644 --- a/src/lib/Request.php +++ b/src/lib/Request.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -12,64 +12,67 @@ namespace Apaapi\lib; -use Apaapi\interfaces\RequestInterface; -use Apaapi\interfaces\ParsableInterface; -use Apaapi\interfaces\RequestClientInterface; -use Apaapi\includes\OperationParser; -use Apaapi\includes\RequestClient; +use Apaapi\interfaces\{ + RequestInterface, + ClientInterface, + OperationInterface +}; +use Apaapi\includes\{ + Client, + Parser, + Provider, + Normalizer +}; use Apaapi\exceptions\RequestException; /** - * Basic Apaapi Request Wrapper Class. + * Apaapi request wrapper class. */ -final class Request extends SignatureRequest -implements RequestInterface +final class Request extends Signature implements RequestInterface { /** * @access public * @var string HOST, API Host */ - const HOST = 'webservices.amazon'; + public const HOST = 'webservices.amazon'; /** * @access private * @var array $params - * @var array $endpoint - * @var RequestClient $client + * @var string $endpoint + * @var Client $client * @var string $operation */ private $params = []; - private $endpoint = []; + private $endpoint; private $client; private $operation; /** - * @param string $accessKeyID - * @param string $secretAccessKey - * @see https://webservices.amazon.com/paapi5/scratchpad/index.html + * @inheritdoc */ - public function __construct($accessKeyID = '', $secretAccessKey = '') + public function __construct(string $accessKeyID, string $secretAccessKey) { $this->accessKeyID = $accessKeyID; $this->secretAccessKey = $secretAccessKey; - $this->init(); + + $this->setTimeStamp(); + $this->setDate(); + $this->setRequestHeader('content-encoding', 'amz-1.0'); + $this->setRequestHeader('content-type', 'application/json; charset=utf-8'); } /** - * Set request payload. - * - * @access public - * @param ParsableInterface $operation - * @return void + * @inheritdoc */ - public function setPayload(ParsableInterface $operation) + public function setPayload(OperationInterface $operation) { // Setup params - $this->operation = OperationParser::getName($operation); - $this->path = $this->path . strtolower($this->operation); - $this->target = "{$this->target}.{$this->operation}"; + $this->operation = Parser::getName($operation); + $this->path = $this->path . strtolower($this->operation); + $this->target = "{$this->target}.{$this->operation}"; + $this->payload = Parser::convert($operation); $host = self::HOST . ".{$this->locale}"; - $this->payload = OperationParser::toString($operation); // Setup headers $this->setRequestHeader('host', $host); @@ -77,33 +80,27 @@ public function setPayload(ParsableInterface $operation) $this->setRequestHeader('x-amz-date', $this->timestamp); $headers = $this->getHeaders(); - $headerString = ''; + $header = ''; foreach ( $headers as $key => $value ) { - $headerString .= "{$key}:{$value}\r\n"; + $header .= "{$key}:{$value}\r\n"; } $this->endpoint = "https://{$host}{$this->path}"; $this->params = [ - 'http' => [ - 'method' => 'POST', - 'header' => $headerString, - 'content' => $this->payload - ] + 'method' => 'POST', + 'header' => $header, + 'payload' => $this->payload ]; } /** - * Set request client. - * - * @access public - * @param RequestClientInterface $client - * @return void + * @inheritdoc */ - public function setClient(RequestClientInterface $client = null) + public function setClient(?ClientInterface $client = null) { if ( !($this->client = $client) ) { - $this->client = new RequestClient( + $this->client = new Client( $this->getEndpoint(), $this->getParams() ); @@ -111,25 +108,9 @@ public function setClient(RequestClientInterface $client = null) } /** - * Check request has valid client. - * - * @access public - * @param void - * @return bool - */ - public function hasClient() - { - return is_object($this->client); - } - - /** - * Set request timestamp. - * - * @access public - * @param string $timestamp - * @return object + * @inheritdoc */ - public function setTimeStamp($timestamp = null) + public function setTimeStamp(?string $timestamp = null) : object { $this->timestamp = ($timestamp) ? $timestamp : gmdate('Ymd\THis\Z'); @@ -137,13 +118,9 @@ public function setTimeStamp($timestamp = null) } /** - * Set request date. - * - * @access public - * @param string $date - * @return object + * @inheritdoc */ - public function setDate($date = null) + public function setDate(?string $date = null) : object { $this->currentDate = ($date) ? $date : gmdate('Ymd'); @@ -151,123 +128,71 @@ public function setDate($date = null) } /** - * Get request client. + * Check valid client. * * @access public - * @param void - * @return object + * @return bool + */ + public function hasClient() : bool + { + $interface = 'Apaapi\interfaces\RequestInterface'; + return is_subclass_of($this->client, $interface); + } + + /** + * @inheritdoc */ - public function getClient() + public function getClient() : object { return $this->client; } /** - * Get request endpoint. - * - * @access public - * @param void - * @return string + * @inheritdoc */ - public function getEndpoint() + public function getEndpoint() : string { return $this->endpoint; } /** - * Get request parameters. - * - * @access public - * @param void - * @return array + * @inheritdoc */ - public function getParams() + public function getParams() : array { return $this->params; } /** - * Get request operation. - * - * @access public - * @param void - * @return string + * @inheritdoc */ - public function getOperation() + public function getOperation() : string { return $this->operation; } /** - * Set request header. - * - * @access public - * @param string $name - * @param string $value - * @return void + * @inheritdoc */ - public function setRequestHeader($name, $value) + public function setRequestHeader(string $name, $value) { $this->headers[$name] = $value; } /** - * Set request locale. - * - * @access public - * @param string $locale - * @return object - * @throws RequestException - * @see https://webservices.amazon.fr/paapi5/documentation/locale-reference.html + * @inheritdoc */ - public function setLocale($locale) + public function setLocale(string $locale) : object { - $locale = strtolower($locale); - foreach ($this->getRegions() as $name => $value) { - if ( in_array($locale,$value) ) { - $this->locale = $locale; - $this->region = $name; - break; - } else { - $this->locale = false; - } - } + $this->locale = Normalizer::formatLocale($locale); + $this->region = Provider::getRegion($locale); + if ( !$this->locale ) { throw new RequestException( - RequestException::invalidRequestLocaleMessage($locale) + RequestException::invalidLocale($locale) ); } + return $this; } - - /** - * Init request. - * - * @access private - * @param void - * @return void - */ - private function init() - { - $this->setTimeStamp(); - $this->setDate(); - $this->setRequestHeader('content-encoding', 'amz-1.0'); - $this->setRequestHeader('content-type', 'application/json; charset=utf-8'); - } - - /** - * Get request regions. - * - * @access private - * @param void - * @return array - */ - private function getRegions() - { - return [ - 'eu-west-1' => ['fr','com.be','de','in','it','es','nl','pl','com.tr','ae','sa','co.uk','se','eg'], - 'us-east-1' => ['com','com.br','ca','com.mx'], - 'us-west-2' => ['com.au','co.jp','sg'] - ]; - } } diff --git a/src/lib/Resource.php b/src/lib/Resource.php index 336c0e4..ab60d1f 100644 --- a/src/lib/Resource.php +++ b/src/lib/Resource.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -15,7 +15,7 @@ use Apaapi\interfaces\ResourceInterface; /** - * Basic Apaapi Resource Wrapper Class. + * Apaapi resource wrapper class. */ class Resource implements ResourceInterface { diff --git a/src/lib/Response.php b/src/lib/Response.php index 746402e..6817809 100644 --- a/src/lib/Response.php +++ b/src/lib/Response.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -12,136 +12,139 @@ namespace Apaapi\lib; -use Apaapi\interfaces\ResponseInterface; -use Apaapi\interfaces\RequestInterface; -use Apaapi\interfaces\ResponseTypeInterface; -use Apaapi\includes\ResponseType; +use Apaapi\interfaces\{ + ResponseInterface, + RequestInterface +}; +use Apaapi\includes\{ + Cache, + Geotargeting, + Normalizer +}; /** - * Basic Apaapi Response Wrapper Class. - * Based on the Product Advertising API 5.0 Scratchpad. + * Basic Apaapi response wrapper class. * @see https://webservices.amazon.com/paapi5/scratchpad/index.html */ -final class Response implements ResponseInterface +class Response implements ResponseInterface { /** * @access public */ - const PARSE = true; + public const NORMALIZE = true; + public const NOCACHE = false; /** - * @access private - * @var int $code, Response status code - * @var mixed $body, Response body - * @var bool $error, Data error + * @access protected + * @var int $code, Response code + * @var string $body, Response body + * @var array $data, Response data */ - private $code = 200; - private $body = false; - private $error = false; + protected $code = 200; + protected $body = false; + protected $data = []; /** - * @param RequestInterface $request - * @param ResponseTypeInterface $type - * @param bool $parse + * @inheritdoc */ - public function __construct(RequestInterface $request, ResponseTypeInterface $type = null, $parse = null) + public function __construct(RequestInterface $request, bool $normalize = false, bool $cache = true) { - // Set HTTP client - if ( !$request->hasClient() ) { - // Set default HTTP Client - $request->setClient(); - } - $client = $request->getClient(); - - // Set response body - $this->body = $client->getResponse(); + ($cache) ? $this->getCached($request) : $this->send($request); - // Set response status code - $this->code = $client->getCode(); + if ( !$this->hasError() ) { - // Close HTTP client - $client->close(); + $this->data = Normalizer::decode($this->body); + + if ( $normalize ) { + $this->data = Normalizer::get($this->data, $request->getOperation()); + } - // Set data error on status 200 - if ( $this->hasDataError() ) { - $this->error = true; } + } - // Apply response format on success - if ( !$this->hasError() && $type ) { - if ( $parse ) { - $this->body = $type->parse($this->body,$request->getOperation()); - } - $this->body = $type->format($this->body); + /** + * @inheritdoc + */ + public function get(?array $geo = null) : array + { + if ( $geo ) { + return (new Geotargeting($geo))->get($this->data); } + return $this->data; } /** - * Get response body. - * - * @access public - * @param void - * @return mixed + * @inheritdoc */ - public function get() + public function getBody() : string { - return $this->body; + return (string)$this->body; } /** - * Get response error. - * - * @see https://webservices.amazon.com/paapi5/documentation/troubleshooting/error-messages.html - * @access public - * @param bool $single - * @return mixed + * @inheritdoc */ - public function getError($single = false) + public function getError() : string { - $error = false; - if ( $this->hasError() ) { - if ( ($response = ResponseType::decode((string)$this->body)) ) { - foreach ($response->Errors as $err) { - if ( $single ) { - return $err->Message; - } - $error[] = $err->Message; - } - } - } - return $error; + return Normalizer::formatError( + $this->getBody() + ); } /** - * Check if response has any error (>=400). - * - * @access public - * @param void - * @return bool + * @inheritdoc */ - public function hasError() + public function hasError() : bool { if ( !$this->code || $this->code >= 400 ) { return true; + } - } elseif ( $this->code == 200 && $this->error ) { - return true; + if ( $this->code == 200 ) { + return (strpos($this->body, '#ErrorData') !== false); } + return false; } /** - * Check if response has data error (==200). + * Get cached response. * - * @access private - * @param void - * @return bool + * @access protected + * @param RequestInterface $request + * @return void */ - private function hasDataError() + protected function getCached(RequestInterface $request) { - if ( strpos($this->body, '#ErrorData') !== false ) { - return true; + $key = Cache::getKey($request); + if ( ($cached = Cache::get($key)) ) { + $this->body = $cached; + + } else { + $this->send($request); + if ( !$this->hasError() ) { + Cache::set($key, $this->body); + } } - return false; + } + + /** + * Send request. + * + * @access protected + * @param RequestInterface $request + * @return void + */ + protected function send(RequestInterface $request) + { + if ( !$request->hasClient() ) { + $request->setClient(); + } + $client = $request->getClient(); + + $this->body = $client->getResponse(); + $this->code = $client->getCode(); + + $client->close(); } } diff --git a/src/lib/Signature.php b/src/lib/Signature.php new file mode 100644 index 0000000..82734e9 --- /dev/null +++ b/src/lib/Signature.php @@ -0,0 +1,241 @@ + + * @link : https://jakiboy.github.io/apaapi/ + * @license : MIT + * + * This file if a part of Apaapi Lib. + */ + +namespace Apaapi\lib; + +use Apaapi\interfaces\{ + OperationInterface, + ClientInterface +}; + +/** + * Apaapi Amazon signature request wrapper class. + * @see https://webservices.amazon.com/paapi5/documentation/without-sdk.html + */ +abstract class Signature +{ + /** + * @access protected + * @var string $path, API path + * @var string $locale, API region locale + * @var string $region, API region name + * @var string $target, API request target + * @var array $headers, HTTP request Headers + * @var string $payload, HTTP request payload + * @var string $accessKeyID, Amazon access key Id + * @var string $secretAccessKey, Amazon secret access key + * @var string $timestamp, API request timestamp + * @var string $currentDate, API request current date + */ + protected $path = '/paapi5/'; + protected $locale = 'com'; + protected $region = 'us-east-1'; + protected $target = 'com.amazon.paapi5.v1.ProductAdvertisingAPIv1'; + protected $headers = []; + protected $payload; + protected $accessKeyID; + protected $secretAccessKey; + protected $timestamp; + protected $currentDate; + + /** + * @access private + * @var string $service, API service + * @var string $method, HTTP request method + * @var string $hmac, HTTP request HMAC algorithm + * @var string $request, HTTP request + * @var string $algo, Hash algorithm + * @var string $signedHeaders, HTTP request signed headers + */ + private $service = 'ProductAdvertisingAPI'; + private $method = 'POST'; + private $hmac = 'AWS4-HMAC-SHA256'; + private $request = 'aws4_request'; + private $algo = 'sha256'; + private $signedHeaders; + + /** + * Set request payload. + * + * @access public + * @param OperationInterface $operation + * @return void + */ + abstract public function setPayload(OperationInterface $operation); + + /** + * Set request client. + * + * @access public + * @param ClientInterface $client + * @return void + */ + abstract public function setClient(?ClientInterface $client = null); + + /** + * Set request timestamp. + * + * @access public + * @param string $timestamp + * @return object + */ + abstract public function setTimeStamp(?string $timestamp = null) : object; + + /** + * Set request date. + * + * @access public + * @param string $date + * @return object + */ + abstract public function setDate(?string $date = null) : object; + + /** + * Get request headers. + * + * @access protected + * @return array + */ + protected function getHeaders() : array + { + ksort($this->headers); + $canonicalUrl = $this->prepareCanonicalRequest(); + $stringToSign = $this->prepareStringToSign($canonicalUrl); + if ( ($signature = $this->calculateSignature($stringToSign)) ) { + $this->headers['Authorization'] = $this->buildAuthorizationString($signature); + } + return $this->headers; + } + + /** + * Prepare canonical request. + * + * @access private + * @return string + */ + private function prepareCanonicalRequest() : string + { + $url = "{$this->method}\n"; + $url .= "{$this->path}\n\n"; + $signedHeaders = ''; + foreach ( $this->headers as $key => $value ) { + $signedHeaders .= "{$key};"; + $url .= "{$key}:{$value}\n"; + } + $url .= "\n"; + $this->signedHeaders = substr($signedHeaders, 0, -1); + $url .= "{$this->signedHeaders}\n"; + $url .= $this->generateHex($this->payload); + return $url; + } + + /** + * Prepare string to be sign. + * + * @access private + * @param string $url + * @return string + */ + private function prepareStringToSign(string $url) : string + { + $string = "{$this->hmac}\n"; + $string .= "{$this->timestamp}\n"; + $string .= "{$this->currentDate}/{$this->region}/"; + $string .= "{$this->service}/{$this->request}\n"; + $string .= $this->generateHex($url); + return $string; + } + + /** + * Calculate signature. + * + * @access private + * @param string $data + * @return string + */ + private function calculateSignature(string $data) : string + { + $key = $this->getSignatureKey( + $this->secretAccessKey, + $this->currentDate, + $this->region, + $this->service + ); + $signature = $this->hash($data, $key); + return strtolower(bin2hex($signature)); + } + + /** + * Build authorization string. + * + * @access private + * @param string $signature + * @return string + */ + private function buildAuthorizationString(string $signature) : string + { + $auth = "{$this->hmac} "; + $auth .= "Credential={$this->accessKeyID}/"; + $auth .= "{$this->currentDate}/"; + $auth .= "{$this->region}/"; + $auth .= "{$this->service}/"; + $auth .= "{$this->request},"; + $auth .= "SignedHeaders={$this->signedHeaders},"; + $auth .= "Signature={$signature}"; + return $auth; + } + + /** + * Generate hex. + * + * @access private + * @param string $date + * @return string + */ + private function generateHex(string $date) : string + { + $hex = bin2hex(hash($this->algo, $date, true)); + return strtolower($hex); + } + + /** + * Get signature key. + * + * @access private + * @param string $key + * @param string $date + * @param string $region + * @param string $service + * @return string + */ + private function getSignatureKey(string $key, string $date, string $region, string $service) : string + { + $secret = "AWS4{$key}"; + $date = $this->hash($date, $secret); + $region = $this->hash($region, $date); + $service = $this->hash($service, $region); + return $this->hash($this->request, $service); + } + + /** + * Hash data. + * + * @access private + * @param string $data + * @param string $key + * @return string + */ + private function hash(string $data, string $key) : string + { + return hash_hmac($this->algo, $data, $key, true); + } +} diff --git a/src/lib/SignatureRequest.php b/src/lib/SignatureRequest.php deleted file mode 100644 index 911f77f..0000000 --- a/src/lib/SignatureRequest.php +++ /dev/null @@ -1,212 +0,0 @@ - - * @link : https://jakiboy.github.io/apaapi/ - * @license : MIT - * - * This file if a part of Apaapi Lib. - */ - -namespace Apaapi\lib; - -use Apaapi\interfaces\ParsableInterface; -use Apaapi\interfaces\RequestClientInterface; - -/** - * Basic Apaapi Amazon Signature Request Wrapper Class. - * @see https://webservices.amazon.com/paapi5/documentation/without-sdk.html - */ -abstract class SignatureRequest -{ - /** - * @access protected - * @var string $path, API path - * @var string $locale, API region locale - * @var string $region, API region name - * @var string $target, API request target - * @var array $headers, HTTP Headers - * @var string $payload, HTTP request content - * @var string $accessKeyID, Amazon API Key ID - * @var string $secretAccessKey, API Secret Key - * @var string $timestamp, API request timestamp - * @var string $currentDate, API request current date - */ - protected $path = '/paapi5/'; - protected $locale = 'com'; - protected $region = 'us-east-1'; - protected $target = 'com.amazon.paapi5.v1.ProductAdvertisingAPIv1'; - protected $headers = []; - protected $payload; - protected $accessKeyID; - protected $secretAccessKey; - protected $timestamp; - protected $currentDate; - - /** - * @access private - * @var string $serviceName, API service name - * @var string $httpMethodName, HTTP method - * @var string $HMACAlgorithm, HTTP Request Hash - * @var string $request, HTTP Request Method - * @var string $strSignedHeader - */ - private $serviceName = 'ProductAdvertisingAPI'; - private $httpMethodName = 'POST'; - private $HMACAlgorithm = 'AWS4-HMAC-SHA256'; - private $request = 'aws4_request'; - private $strSignedHeader; - - /** - * @access public - * @param ParsableInterface $operation - * @return void - */ - abstract public function setPayload(ParsableInterface $operation); - - /** - * @access public - * @param RequestClientInterface $client - * @return void - */ - abstract public function setClient(RequestClientInterface $client = null); - - /** - * @access public - * @param string $timestamp - * @return object - */ - abstract public function setTimeStamp($timestamp = null); - - /** - * @access public - * @param string $date - * @return object - */ - abstract public function setDate($date = null); - - /** - * @access protected - * @param void - * @return array - */ - protected function getHeaders() - { - ksort($this->headers); - $canonicalUrl = $this->prepareCanonicalRequest(); - $stringToSign = $this->prepareStringToSign($canonicalUrl); - if ( ($signature = $this->calculateSignature($stringToSign)) ) { - $this->headers['Authorization'] = $this->buildAuthorizationString($signature); - } - return $this->headers; - } - - /** - * Prepare canonical request - * - * @access private - * @param void - * @return string - */ - private function prepareCanonicalRequest() - { - $canonicalUrl = "{$this->httpMethodName}\n"; - $canonicalUrl .= "{$this->path}\n\n"; - $signedHeaders = ''; - foreach ( $this->headers as $key => $value ) { - $signedHeaders .= "{$key};"; - $canonicalUrl .= "{$key}:{$value}\n"; - } - $canonicalUrl .= "\n"; - $this->strSignedHeader = substr($signedHeaders, 0, -1); - $canonicalUrl .= "{$this->strSignedHeader}\n"; - $canonicalUrl .= $this->generateHex($this->payload); - return $canonicalUrl; - } - - /** - * Prepare string to be signed - * - * @access private - * @param string $canonicalUrl - * @return string - */ - private function prepareStringToSign($canonicalUrl) - { - $string = "{$this->HMACAlgorithm}\n"; - $string .= "{$this->timestamp}\n"; - $string .= "{$this->currentDate}/{$this->region}/"; - $string .= "{$this->serviceName}/{$this->request}\n"; - $string .= $this->generateHex($canonicalUrl); - return $string; - } - - /** - * Calculate signature - * - * @access private - * @param string $stringToSign - * @return string - */ - private function calculateSignature($stringToSign) - { - $signatureKey = $this->getSignatureKey( - $this->secretAccessKey, - $this->currentDate, - $this->region, - $this->serviceName - ); - $signature = hash_hmac('sha256', $stringToSign, $signatureKey, true); - $strHexSignature = strtolower(bin2hex($signature)); - return $strHexSignature; - } - - /** - * @access private - * @param string $strSignature - * @return string - */ - private function buildAuthorizationString($strSignature) - { - $auth = "{$this->HMACAlgorithm} "; - $auth .= "Credential={$this->accessKeyID}/"; - $auth .= "{$this->currentDate}/"; - $auth .= "{$this->region}/"; - $auth .= "{$this->serviceName}/"; - $auth .= "{$this->request},"; - $auth .= "SignedHeaders={$this->strSignedHeader},"; - $auth .= "Signature={$strSignature}"; - return $auth; - } - - /** - * Generate Hex - * - * @access private - * @param string $data - * @return string - */ - private function generateHex($data) - { - return strtolower(bin2hex(hash('sha256', $data, true))); - } - - /** - * @access private - * @param string $key - * @param string $date - * @param string $region - * @param string $serviceName - * @return string - */ - private function getSignatureKey($key, $date, $region, $serviceName) - { - $kSecret = "AWS4{$key}"; - $kDate = hash_hmac('sha256', $date, $kSecret, true); - $kRegion = hash_hmac('sha256', $region, $kDate, true); - $kService = hash_hmac('sha256', $serviceName, $kRegion, true); - return hash_hmac('sha256', $this->request, $kService, true); - } -} diff --git a/src/operations/GetBrowseNodes.php b/src/operations/GetBrowseNodes.php index 2b2ce23..cec5831 100644 --- a/src/operations/GetBrowseNodes.php +++ b/src/operations/GetBrowseNodes.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -14,10 +14,10 @@ use Apaapi\lib\Operation; use Apaapi\resources\BrowseNodes; -use Apaapi\includes\ResourceParser; +use Apaapi\includes\Parser; /** - * Basic Apaapi GetBrowseNodes Operation. + * Apaapi operation. * @see https://webservices.amazon.com/paapi5/documentation/getbrowsenodes.html */ final class GetBrowseNodes extends Operation @@ -29,23 +29,25 @@ final class GetBrowseNodes extends Operation public $browseNodeIds = []; /** - * @param void + * Set resources. */ public function __construct() { - $this->resources = ResourceParser::toString([ + $this->resources = Parser::convert([ new BrowseNodes ]); } /** + * Set browsing node Ids. + * * @access public - * @param array|string $browseNodeIds + * @param array $ids * @return object */ - public function setBrowseNodeIds($browseNodeIds) + public function setBrowseNodeIds(array $ids) : object { - $this->browseNodeIds = (array)$browseNodeIds; + $this->browseNodeIds = $ids; return $this; } } diff --git a/src/operations/GetItems.php b/src/operations/GetItems.php index 12d897f..57cf845 100644 --- a/src/operations/GetItems.php +++ b/src/operations/GetItems.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -13,17 +13,19 @@ namespace Apaapi\operations; use Apaapi\lib\ItemOperation; -use Apaapi\resources\BrowseNodeInfo; -use Apaapi\resources\Images; -use Apaapi\resources\ItemInfo; -use Apaapi\resources\Offers; -use Apaapi\resources\RentalOffers; -use Apaapi\resources\CustomerReviews; -use Apaapi\resources\ParentASIN; -use Apaapi\includes\ResourceParser; +use Apaapi\resources\{ + BrowseNodeInfo, + Images, + ItemInfo, + Offers, + RentalOffers, + CustomerReviews, + ParentASIN +}; +use Apaapi\includes\Parser; /** - * Basic Apaapi GetItems Operation. + * Apaapi operation. * @see https://webservices.amazon.com/paapi5/documentation/get-items.html */ final class GetItems extends ItemOperation @@ -37,11 +39,11 @@ final class GetItems extends ItemOperation public $itemIds = []; /** - * @param void + * Set resources. */ public function __construct() { - $this->resources = ResourceParser::toString([ + $this->resources = Parser::convert([ new BrowseNodeInfo, new Images, new ItemInfo, @@ -53,24 +55,28 @@ public function __construct() } /** + * Set item Id type. + * * @access public - * @param array|string $type + * @param string $type * @return object */ - public function setItemIdType($type) + public function setItemIdType(string $type) : object { - $this->itemIdType = (array)$type; + $this->itemIdType = $type; return $this; } /** + * Set item Ids. + * * @access public - * @param array|string $itemIds + * @param array $ids * @return object */ - public function setItemIds($itemIds) + public function setItemIds(array $ids) : object { - $this->itemIds = (array)$itemIds; + $this->itemIds = $ids; return $this; } } diff --git a/src/operations/GetVariations.php b/src/operations/GetVariations.php index b25f4db..57d4931 100644 --- a/src/operations/GetVariations.php +++ b/src/operations/GetVariations.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -13,17 +13,19 @@ namespace Apaapi\operations; use Apaapi\lib\ItemOperation; -use Apaapi\resources\BrowseNodeInfo; -use Apaapi\resources\Images; -use Apaapi\resources\ItemInfo; -use Apaapi\resources\Offers; -use Apaapi\resources\RentalOffers; -use Apaapi\resources\VariationSummary; -use Apaapi\resources\ParentASIN; -use Apaapi\includes\ResourceParser; +use Apaapi\resources\{ + BrowseNodeInfo, + Images, + ItemInfo, + Offers, + RentalOffers, + VariationSummary, + ParentASIN +}; +use Apaapi\includes\Parser; /** - * Basic Apaapi GetVariations Operation. + * Apaapi operation. * @see https://webservices.amazon.com/paapi5/documentation/get-variations.html */ final class GetVariations extends ItemOperation @@ -39,11 +41,11 @@ final class GetVariations extends ItemOperation public $variationPage = 1; /** - * @param void + * Set resources. */ public function __construct() { - $this->resources = ResourceParser::toString([ + $this->resources = Parser::convert([ new BrowseNodeInfo, new Images, new ItemInfo, @@ -55,35 +57,41 @@ public function __construct() } /** + * Set ASIN. + * * @access public * @param string $ASIN * @return object */ - public function setASIN($ASIN) + public function setASIN(string $ASIN) : object { $this->ASIN = $ASIN; return $this; } /** + * Set variation count. + * * @access public - * @param int $variationCount + * @param int $count * @return object */ - public function setVariationCount($variationCount) + public function setVariationCount(int $count) : object { - $this->variationCount = $variationCount; + $this->variationCount = $count; return $this; } /** + * Set variation page. + * * @access public - * @param int $variationPage + * @param int $page * @return object */ - public function setVariationPage($variationPage) + public function setVariationPage(int $page) : object { - $this->variationPage = $variationPage; + $this->variationPage = $page; return $this; } } diff --git a/src/operations/SearchItems.php b/src/operations/SearchItems.php index afe7726..99d3e79 100644 --- a/src/operations/SearchItems.php +++ b/src/operations/SearchItems.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -13,17 +13,19 @@ namespace Apaapi\operations; use Apaapi\lib\ItemOperation; -use Apaapi\resources\BrowseNodeInfo; -use Apaapi\resources\Images; -use Apaapi\resources\ItemInfo; -use Apaapi\resources\Offers; -use Apaapi\resources\RentalOffers; -use Apaapi\resources\SearchRefinements; -use Apaapi\resources\ParentASIN; -use Apaapi\includes\ResourceParser; +use Apaapi\resources\{ + BrowseNodeInfo, + Images, + ItemInfo, + Offers, + RentalOffers, + SearchRefinements, + ParentASIN +}; +use Apaapi\includes\Parser; /** - * Basic Apaapi SearchItems Operation. + * Apaapi operation. * @see https://webservices.amazon.com/paapi5/documentation/search-items.html */ final class SearchItems extends ItemOperation @@ -66,12 +68,12 @@ final class SearchItems extends ItemOperation public $sortBy = null; public $title = null; - /** - * @param void - */ + /** + * Set resources. + */ public function __construct() { - $this->resources = ResourceParser::toString([ + $this->resources = Parser::convert([ new BrowseNodeInfo, new Images, new ItemInfo, @@ -83,190 +85,221 @@ public function __construct() } /** + * Set keywords. + * * @access public - * @param string|array $keywords + * @param string $keywords * @return object */ - public function setKeywords($keywords) + public function setKeywords(string $keywords) : object { - if ( is_array($keywords) ) { - $keywords = implode(',', $keywords); - } $this->keywords = $keywords; return $this; } /** + * Set actor. + * * @access public * @param string $actor * @return object */ - public function setActor($actor) + public function setActor(string $actor) : object { $this->actor = $actor; return $this; } /** + * Set artist. + * * @access public * @param string $artist * @return object */ - public function setArtist($artist) + public function setArtist(string $artist) : object { $this->artist = $artist; return $this; } /** + * Set author. + * * @access public * @param string $author * @return object */ - public function setAuthor($author) + public function setAuthor(string $author) : object { $this->author = $author; return $this; } /** + * Set availability. + * * @access public * @param string $availability * @return object */ - public function setAvailability($availability) + public function setAvailability(string $availability) : object { $this->availability = $availability; return $this; } /** + * Set brand. + * * @access public * @param string $brand * @return object */ - public function setBrand($brand) + public function setBrand(string $brand) : object { $this->brand = $brand; return $this; } /** + * Set browse node id. + * * @access public - * @param string $browseNodeId + * @param string $id * @return object */ - public function setBrowseNodeId($browseNodeId) + public function setBrowseNodeId(string $id) : object { - $this->browseNodeId = $browseNodeId; + $this->browseNodeId = $id; return $this; } /** + * Set delivery flags. + * * @access public - * @param array $deliveryFlags + * @param array $flags * @return object */ - public function setDeliveryFlags($deliveryFlags) + public function setDeliveryFlags(array $flags) : object { - $this->deliveryFlags = (array)$deliveryFlags; + $this->deliveryFlags = $flags; return $this; } /** + * Set item count. + * * @access public - * @param int $itemCount + * @param int $count * @return object */ - public function setItemCount($itemCount) + public function setItemCount(int $count) : object { - $this->itemCount = (int)$itemCount; + $this->itemCount = $count; return $this; } /** + * Set item page. + * * @access public - * @param int $itemPage + * @param int $page * @return object */ - public function setItemPage($itemPage) + public function setItemPage(int $page) : object { - $this->itemPage = (int)$itemPage; + $this->itemPage = $page; return $this; } /** + * Set max price. + * * @access public - * @param int $maxPrice + * @param int $price * @return object */ - public function setMaxPrice($maxPrice) + public function setMaxPrice(int $price) : object { - $this->maxPrice = (int)$maxPrice; + $this->maxPrice = $price; return $this; } /** + * Set min price. + * * @access public - * @param int $minPrice + * @param int $price * @return object */ - public function setMinPrice($minPrice) + public function setMinPrice(int $price) : object { - $this->minPrice = (int)$minPrice; + $this->minPrice = $price; return $this; } /** + * Set min reviews rating. + * * @access public - * @param int $minReviewsRating + * @param int $rating * @return object */ - public function setMinReviewsRating($minReviewsRating) + public function setMinReviewsRating(int $rating) : object { - $this->minReviewsRating = (int)$minReviewsRating; + $this->minReviewsRating = $rating; return $this; } /** + * Set min saving percent. + * * @access public - * @param int $minSavingPercent + * @param int $percent * @return object */ - public function setMinSavingPercent($minSavingPercent) + public function setMinSavingPercent(int $percent) : object { - $this->minSavingPercent = (int)$minSavingPercent; + $this->minSavingPercent = $percent; return $this; } /** + * Set sort by. + * * @access public * @param string $sortBy * @return object */ - public function setSortBy($sortBy) + public function setSortBy(string $sortBy) : object { $this->sortBy = $sortBy; return $this; } /** + * Set title. + * * @access public * @param string $title * @return object */ - public function setTitle($title) + public function setTitle(string $title) : object { $this->title = $title; return $this; } /** + * Set search index. + * * @access public * @param string $index * @return object */ - public function setSearchIndex($index) + public function setSearchIndex(string $index) : object { $this->searchIndex = $index; return $this; diff --git a/src/resources/BrowseNodeInfo.php b/src/resources/BrowseNodeInfo.php index f11c90a..74bbc11 100644 --- a/src/resources/BrowseNodeInfo.php +++ b/src/resources/BrowseNodeInfo.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -15,13 +15,13 @@ use Apaapi\lib\Resource; /** - * BrowseNodeInfo : High Level Resource. + * Apaapi : High level resource. * @see https://webservices.amazon.com/paapi5/documentation/browsenodeinfo.html */ final class BrowseNodeInfo extends Resource { /** - * @param void + * Set items. */ public function __construct() { diff --git a/src/resources/BrowseNodes.php b/src/resources/BrowseNodes.php index be4dc65..e6c208a 100644 --- a/src/resources/BrowseNodes.php +++ b/src/resources/BrowseNodes.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -15,13 +15,13 @@ use Apaapi\lib\Resource; /** - * BrowseNodes : High Level Resource. + * Apaapi : High level resource. * @see https://webservices.amazon.com/paapi5/documentation/browsenodes.html */ final class BrowseNodes extends Resource { /** - * @param void + * Set items. */ public function __construct() { diff --git a/src/resources/CustomerReviews.php b/src/resources/CustomerReviews.php index a51f359..c586b9b 100644 --- a/src/resources/CustomerReviews.php +++ b/src/resources/CustomerReviews.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -15,12 +15,12 @@ use Apaapi\lib\Resource; /** - * Offers : High Level Resource. + * Apaapi : Sub-level resource. */ final class CustomerReviews extends Resource { /** - * @param void + * Set items. */ public function __construct() { diff --git a/src/resources/Images.php b/src/resources/Images.php index 82e5f1d..4f838ae 100644 --- a/src/resources/Images.php +++ b/src/resources/Images.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -15,13 +15,13 @@ use Apaapi\lib\Resource; /** - * Images : High Level Resource. + * Apaapi : High level resource. * @see https://webservices.amazon.com/paapi5/documentation/images.html */ final class Images extends Resource { /** - * @param void + * Set items. */ public function __construct() { diff --git a/src/resources/ItemInfo.php b/src/resources/ItemInfo.php index 9548df5..e610f55 100644 --- a/src/resources/ItemInfo.php +++ b/src/resources/ItemInfo.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -15,13 +15,13 @@ use Apaapi\lib\Resource; /** - * ItemInfo : High Level Resource. + * Apaapi : High level resource. * @see https://webservices.amazon.com/paapi5/documentation/item-info.html */ final class ItemInfo extends Resource { /** - * @param void + * Set items. */ public function __construct() { diff --git a/src/resources/Offers.php b/src/resources/Offers.php index bcdbf87..911fd33 100644 --- a/src/resources/Offers.php +++ b/src/resources/Offers.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -15,13 +15,13 @@ use Apaapi\lib\Resource; /** - * Offers : High Level Resource. + * Apaapi : High level resource. * @see https://webservices.amazon.com/paapi5/documentation/offers.html */ final class Offers extends Resource { /** - * @param void + * Set items. */ public function __construct() { diff --git a/src/resources/ParentASIN.php b/src/resources/ParentASIN.php index 280750b..e44f5cb 100644 --- a/src/resources/ParentASIN.php +++ b/src/resources/ParentASIN.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -15,7 +15,7 @@ use Apaapi\lib\Resource; /** - * ParentASIN : High Level Resource. + * Apaapi : High level resource. * @see https://webservices.amazon.com/paapi5/documentation/parent-asin.html */ final class ParentASIN extends Resource {} diff --git a/src/resources/RentalOffers.php b/src/resources/RentalOffers.php index df989e2..e1a238f 100644 --- a/src/resources/RentalOffers.php +++ b/src/resources/RentalOffers.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -15,12 +15,12 @@ use Apaapi\lib\Resource; /** - * Offers : High Level Resource. + * Apaapi : Sub-level resource. */ final class RentalOffers extends Resource { /** - * @param void + * Set items. */ public function __construct() { diff --git a/src/resources/SearchRefinements.php b/src/resources/SearchRefinements.php index d39ad9b..2e5c0bc 100644 --- a/src/resources/SearchRefinements.php +++ b/src/resources/SearchRefinements.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -15,7 +15,7 @@ use Apaapi\lib\Resource; /** - * SearchRefinements : High Level Resource. + * Apaapi : High level resource. * @see https://webservices.amazon.com/paapi5/documentation/search-refinements.html */ final class SearchRefinements extends Resource {} diff --git a/src/resources/VariationSummary.php b/src/resources/VariationSummary.php index 21fcc66..add689b 100644 --- a/src/resources/VariationSummary.php +++ b/src/resources/VariationSummary.php @@ -1,9 +1,9 @@ + * @author : Jakiboy + * @package : Amazon Product Advertising API Library (v5) + * @version : 1.2.0 + * @copyright : (c) 2019 - 2024 Jihad Sinnaour * @link : https://jakiboy.github.io/apaapi/ * @license : MIT * @@ -15,13 +15,13 @@ use Apaapi\lib\Resource; /** - * VariationSummary : High Level Resource. + * Apaapi : High level resource. * @see https://webservices.amazon.com/paapi5/documentation/variation-summary.html */ final class VariationSummary extends Resource { /** - * @param void + * Set items. */ public function __construct() { diff --git a/tests/CacheTest.php b/tests/CacheTest.php new file mode 100644 index 0000000..5c50d4b --- /dev/null +++ b/tests/CacheTest.php @@ -0,0 +1,55 @@ +assertEquals($ttl, Cache::getTtl()); + } + + public function testSetAndGetSalt() + { + $salt = 'newSalt'; + Cache::setSalt($salt); + $this->assertEquals($salt, Cache::getSalt()); + } + + public function testSetAndGetExt() + { + $ext = 'newExt'; + Cache::setExt($ext); + $this->assertEquals($ext, Cache::getExt()); + } + + public function testGetKey() + { + $request = $this->createMock(RequestInterface::class); + $request->method('getParams') + ->willReturn(['http' => ['content' => 'testContent']]); + + $key = Cache::getKey($request); + $this->assertIsString($key); + } + + public function testGenerateKey() + { + $item = 'testItem'; + $key = Cache::generateKey($item); + $this->assertIsString($key); + } + + public function testSetAndGet() + { + $key = 'testKey'; + $value = 'testValue'; + + Cache::set($key, $value); + $this->assertEquals($value, Cache::get($key)); + } +} \ No newline at end of file