From 55eb3444b56c16aa895852b183dd1a3ce2c24044 Mon Sep 17 00:00:00 2001 From: Jakiboy Date: Wed, 27 Mar 2024 12:23:40 +0000 Subject: [PATCH 01/12] Dev --- README.md | 86 +- examples/add-to-cart.php | 26 +- examples/basic.php | 22 +- examples/custom-client.php | 57 +- examples/custom-error-handling.php | 11 +- examples/custom-response.php | 20 +- examples/custom-ressources.php | 27 +- examples/disable-client-exception.php | 64 - examples/simple.php | 29 + src/Autoloader.php | 57 +- src/exceptions/OperationException.php | 15 +- src/exceptions/RequestException.php | 17 +- src/exceptions/ResponseTypeException.php | 24 - src/includes/Builder.php | 881 ++++++++ src/includes/Cache.php | 198 ++ src/includes/Client.php | 391 ++++ src/includes/Geotargeting.php | 359 +++ src/includes/Keyword.php | 171 ++ src/includes/Normalizer.php | 1115 ++++++++++ src/includes/OperationParser.php | 37 - src/includes/Parser.php | 77 +- src/includes/Provider.php | 267 +++ src/includes/Rating.php | 161 ++ src/includes/RequestClient.php | 326 --- src/includes/ResourceParser.php | 40 - src/includes/ResponseType.php | 129 -- src/includes/bin/categories.json | 2408 +++++++++++++++++++++ src/includes/bin/countries.json | 23 + src/includes/bin/currencies.json | 129 ++ src/includes/bin/languages.json | 89 + src/includes/bin/regions.json | 29 + src/includes/bin/symbols.json | 18 + src/interfaces/CartInterface.php | 44 + src/interfaces/ClientInterface.php | 80 + src/interfaces/ItemOperationInterface.php | 34 +- src/interfaces/OperationInterface.php | 44 +- src/interfaces/ParsableInterface.php | 10 +- src/interfaces/RequestClientInterface.php | 43 - src/interfaces/RequestInterface.php | 55 +- src/interfaces/ResourceInterface.php | 10 +- src/interfaces/ResponseInterface.php | 47 +- src/interfaces/ResponseParseInterface.php | 25 - src/interfaces/ResponseTypeInterface.php | 37 - src/lib/Cart.php | 93 +- src/lib/ItemOperation.php | 47 +- src/lib/Operation.php | 84 +- src/lib/Request.php | 221 +- src/lib/Resource.php | 10 +- src/lib/Response.php | 177 +- src/lib/Signature.php | 241 +++ src/lib/SignatureRequest.php | 212 -- src/operations/GetBrowseNodes.php | 24 +- src/operations/GetItems.php | 48 +- src/operations/GetVariations.php | 52 +- src/operations/SearchItems.php | 141 +- src/resources/BrowseNodeInfo.php | 12 +- src/resources/BrowseNodes.php | 12 +- src/resources/CustomerReviews.php | 12 +- src/resources/Images.php | 12 +- src/resources/ItemInfo.php | 12 +- src/resources/Offers.php | 12 +- src/resources/ParentASIN.php | 10 +- src/resources/RentalOffers.php | 12 +- src/resources/SearchRefinements.php | 10 +- src/resources/VariationSummary.php | 12 +- tests/CacheTest.php | 55 + 66 files changed, 7555 insertions(+), 1698 deletions(-) delete mode 100644 examples/disable-client-exception.php create mode 100644 examples/simple.php delete mode 100644 src/exceptions/ResponseTypeException.php create mode 100644 src/includes/Builder.php create mode 100644 src/includes/Cache.php create mode 100644 src/includes/Client.php create mode 100644 src/includes/Geotargeting.php create mode 100644 src/includes/Keyword.php create mode 100644 src/includes/Normalizer.php delete mode 100644 src/includes/OperationParser.php create mode 100644 src/includes/Provider.php create mode 100644 src/includes/Rating.php delete mode 100644 src/includes/RequestClient.php delete mode 100644 src/includes/ResourceParser.php delete mode 100644 src/includes/ResponseType.php create mode 100644 src/includes/bin/categories.json create mode 100644 src/includes/bin/countries.json create mode 100644 src/includes/bin/currencies.json create mode 100644 src/includes/bin/languages.json create mode 100644 src/includes/bin/regions.json create mode 100644 src/includes/bin/symbols.json create mode 100644 src/interfaces/CartInterface.php create mode 100644 src/interfaces/ClientInterface.php delete mode 100644 src/interfaces/RequestClientInterface.php delete mode 100644 src/interfaces/ResponseParseInterface.php delete mode 100644 src/interfaces/ResponseTypeInterface.php create mode 100644 src/lib/Signature.php delete mode 100644 src/lib/SignatureRequest.php create mode 100644 tests/CacheTest.php 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 From 541ea9d420d0c6be0d326e8d9db3fcfb9c44bb2c Mon Sep 17 00:00:00 2001 From: Jakiboy Date: Fri, 29 Mar 2024 08:02:14 +0000 Subject: [PATCH 02/12] Dev --- README.md | 251 +++----- docs/builder.md | 567 ++++++++++++++++++ docs/provider.md | 68 +++ docs/ressources.md | 290 +++------ docs/tlds.md | 76 +-- examples/advanced/custom-client.php | 82 +++ examples/{ => advanced}/custom-ressources.php | 20 +- examples/advanced/error-handling.php | 38 ++ examples/advanced/response-normalizer.php | 36 ++ examples/basic.php | 16 +- examples/{simple.php => builder.php} | 12 +- examples/{add-to-cart.php => cart.php} | 19 +- examples/custom-client.php | 84 --- examples/custom-error-handling.php | 43 -- examples/custom-response.php | 37 -- examples/rating.php | 23 + src/Autoloader.php | 63 +- src/includes/Builder.php | 101 +++- src/includes/Client.php | 27 +- src/includes/Geotargeting.php | 2 +- src/includes/Normalizer.php | 65 +- src/includes/Parser.php | 1 + src/includes/Provider.php | 24 +- src/lib/Request.php | 4 +- 24 files changed, 1210 insertions(+), 739 deletions(-) create mode 100644 docs/builder.md create mode 100644 docs/provider.md create mode 100644 examples/advanced/custom-client.php rename examples/{ => advanced}/custom-ressources.php (62%) create mode 100644 examples/advanced/error-handling.php create mode 100644 examples/advanced/response-normalizer.php rename examples/{simple.php => builder.php} (59%) rename examples/{add-to-cart.php => cart.php} (56%) delete mode 100644 examples/custom-client.php delete mode 100644 examples/custom-error-handling.php delete mode 100644 examples/custom-response.php create mode 100644 examples/rating.php diff --git a/README.md b/README.md index c371b40..6258f46 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # APAAPI -Amazon Product Advertising API PHP +Amazon Product Advertising API PHP Amazon Product Advertising API V5.0 (**Without Amazon SDK**). -This repository contains a PHP Lightweight (155 Ko) Wrapper Library, +This repository contains a lightweight PHP (155 KB) 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 -- @@ -19,13 +19,12 @@ composer require jakiboy/apaapi #### Without Composer? -* **1** - [Download repository ZIP](https://github.com/Jakiboy/apaapi/archive/refs/heads/master.zip) (*Latest version*). -* **2** - Extract ZIP (*apaapi-master*). +* **1** - [Download repository ZIP](https://github.com/Jakiboy/apaapi/archive/refs/heads/dev.zip) (*Latest version*). +* **2** - Extract ZIP (*apaapi-dev*). * **3** - Include this lines beelow (*apaapi self-autoloader*). - ``` -include('apaapi-master/src/Autoloader.php'); +include('apaapi-dev/src/Autoloader.php'); \apaapi\Autoloader::init(); ``` @@ -37,31 +36,32 @@ include('apaapi-master/src/Autoloader.php'); This version includes: -* 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). +* **Rating Stars** (Lagacy). * **Keyword Converter** (ASIN, ISBN, EAN, Node, Root). +* **Caching System** (Basic built-in cache to reduce API calls). [Full Changelog](#). - ## ⚡ Getting Started: -### Variables (Basics): - -* "_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). +### Variables: +* "\_KEY\_" : From your Amazon Associates (*your locale*), [More](https://affiliate-program.amazon.com/help/node/topic/GTPNVFFUV2GQ8AZV). +* "\_SECRET\_" : From your Amazon Associates (*your locale*), [More](https://affiliate-program.amazon.com/help/node/topic/GTPNVFFUV2GQ8AZV). +* "\_TAG\_" : From your Amazon Associates (*your locale*), [More](https://webservices.amazon.com/paapi5/documentation/troubleshooting/sign-up-as-an-associate.html). +* "\_LOCALE\_" : **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). +* "\_KEYWORDS\_" : What you are looking for (*Products*), [More](https://webservices.amazon.com/paapi5/documentation/search-items.html). +* "\_ASIN\_" : Accepts (ISBN), Amazon Standard Identification Number (*your locale*), [More](https://webservices.amazon.com/paapi5/documentation/get-items.html#ItemLookup-rp). +* "\_NODE\_" : Browse Node ID (*your locale*), [More](https://webservices.amazon.com/paapi5/documentation/use-cases/organization-of-items-on-amazon/browse-nodes/browse-node-properties.html#browse-node-ids). ### Quickstart: +Using Apaapi builder is recommended. + ```php /** @@ -71,216 +71,155 @@ This version includes: use Apaapi\includes\Builder; -// Init request builder -$builder = new Builder('_KEY_', '_SECRET_', '_TAG_', '_REGION_'); +// (1) Init request builder +$builder = new Builder('_KEY_', '_SECRET_', '_TAG_', '_LOCALE_'); -// Get response -$data = $builder->searchOne('Sony Xperia Pro-I') // Normalized array +// (2) Get response (Search) +$data = $builder->searchOne('Sony Xperia Pro-I'); // Normalized array ``` -### Quickstart (OLD): +* *See advanced builder usage at [/docs/builder.md](https://github.com/Jakiboy/apaapi/tree/dev/docs/builder.md)* +### Basic (Search): -```php - +Extensible search method. -/** - * @see You can use Composer, - * Or include Apaapi Autoloader Here. - */ +```php use Apaapi\operations\SearchItems; use Apaapi\lib\Request; use Apaapi\lib\Response; -/** - * @see With Three Easy Steps, - * You can Achieve Quick Connection to Amazon Affiliate Program, - * Via Amazon Product Advertising API Library. - */ - -// (1) Set Operation +// (1) Set operation $operation = new SearchItems(); $operation->setPartnerTag('_TAG_')->setKeywords('_KEYWORDS_'); -// (2) Prapere Request -$request = new Request('_KEY_','_SECRET_'); -$request->setLocale('_REGION_')->setPayload($operation); +// (2) Prapere request +$request = new Request('_KEY_', '_SECRET_'); +$request->setLocale('_LOCALE_')->setPayload($operation); -// (3) Get Response +// (3) Get response $response = new Response($request); -echo $response->get(); // JSON ready for parsing +$data = $response->get(); // Array ``` -* *See all available TLDs used by setLocale() at [/docs/tlds.md](https://github.com/Jakiboy/apaapi/tree/master/docs/tlds.md)* +* *See all available TLDs used by setLocale() at [/docs/tlds.md](https://github.com/Jakiboy/apaapi/tree/dev/docs/tlds.md)* -### Operations: + +### Basic (Get): + +Extensible get method. ```php use Apaapi\operations\GetItems; -use Apaapi\operations\SearchItems; -use Apaapi\operations\GetVariations; -use Apaapi\operations\GetBrowseNodes; - -/** - * @see 4 Operations. - * @see https://webservices.amazon.com/paapi5/documentation/operations.html - */ +use Apaapi\lib\Request; +use Apaapi\lib\Response; -// GetItems +// Set operation $operation = new GetItems(); -$operation->setPartnerTag('_TAG_') -->setItemIds(['_ASIN_']); // Array|String - -// SearchItems -$operation = new SearchItems(); -$operation->setPartnerTag('_TAG_') -->setKeywords('_KEYWORDS_'); // Array|String - -// GetVariations -$operation = new GetVariations(); -$operation->setPartnerTag('_TAG_') -->setASIN('_ASIN_'); // String - -// GetBrowseNodes -$operation = new GetBrowseNodes(); -$operation->setPartnerTag('_TAG_') -->setBrowseNodeIds(['{NodeId}']); // Array|String - -``` - -### Advanced (Custom ressources): - -```php +$operation->setPartnerTag('_TAG_')->setItemIds(['_ASIN_']); -/** - * @see Using setResources() method to set custom ressources, - * Instead of default ressources, - * This can improve response time. - */ +// Prapere request +$request = new Request('_KEY_', '_SECRET_'); +$request->setLocale('_LOCALE_')->setPayload($operation); -// Set Operation -$operation->setPartnerTag('_TAG_')->setKeywords('_KEYWORDS_') -->setResources(['Images.Primary.Small','ItemInfo.Title','Offers.Listings.Price']); +// Get response +$response = new Response($request); +$data = $response->get(); // Array ``` -* *See all available ressources used by setResources() at [/docs/ressources.md](https://github.com/Jakiboy/apaapi/tree/master/docs/ressources.md)* +### Operations: -### Advanced (Custom HTTP Request Client): +All available operations. ```php use Apaapi\operations\GetItems; -use Apaapi\lib\Request; -use Apaapi\lib\Response; -use Apaapi\includes\RequestClient; - -/** - * @see Extending RequestClient: Allows Overriding cURL|Stream Settings, - * Or Using Other Stream Instead. - */ -class MyRequestClient extends RequestClient -{ - // ... -} +use Apaapi\operations\SearchItems; +use Apaapi\operations\GetVariations; +use Apaapi\operations\GetBrowseNodes; -// Set Operation +// (1) GetItems $operation = new GetItems(); -$operation->setPartnerTag('_TAG_')->setItemIds('_ASIN_'); +$operation->setPartnerTag('_TAG_'); +$operation->setItemIds(['_ASIN_']); // Array -// Prapere Request -$request = new Request('_KEY_','_SECRET_'); -$request->setLocale('{your-region}')->setPayload($operation); +// (2) SearchItems +$operation = new SearchItems(); +$operation->setPartnerTag('_TAG_'); +$operation->setKeywords('_KEYWORDS_'); // String -// Set Custom Client After Payload -$request->setClient( - new MyRequestClient($request->getEndpoint(), $request->getParams()) -); +// (3) GetVariations +$operation = new GetVariations(); +$operation->setPartnerTag('_TAG_'); +$operation->setASIN('_ASIN_'); // String -// Get Response -$response = new Response($request); -echo $response->get(); // JSON ready for parsing +// (4) GetBrowseNodes +$operation = new GetBrowseNodes(); +$operation->setPartnerTag('_TAG_'); +$operation->setBrowseNodeIds(['_NODE_']); // Array ``` -### Advanced (Response Type Helper): -```php +### Ressources: -use Apaapi\includes\ResponseType; +Optimize response time by setting only the needed resources. -/** - * @see Helps generating quick decoded response. - * @param object|array|serialized - */ +```php -// Get Response -$response = new Response($request, new ResponseType('array')); -return $response->get(); // Array ready to be used +use Apaapi\operations\SearchItems; -``` +// Set Operation +$operation = new SearchItems(); +$operation->setPartnerTag('_TAG_')->setKeywords('_KEYWORDS_'); -```php +// Set Ressources (3) +$operation->setResources(['Images.Primary.Small', 'ItemInfo.Title', 'Offers.Listings.Price']); -use Apaapi\includes\ResponseType; +``` -/** - * @see Helps parsing response. - * @param Response::PARSE - */ +* *See all available ressources used by setResources() at [/docs/ressources.md](https://github.com/Jakiboy/apaapi/tree/dev/docs/ressources.md)* -// Get Response -$response = new Response($request, new ResponseType('object'), Response::PARSE); -return $response->get(); // Object ready to be used +### Cart: -``` -### Advanced (Response Errors): +Get affiliate cart URL. ```php -/** - * @see Error catching. - */ +use Apaapi\lib\Cart; + +// Init Cart +$cart = new Cart(); +$cart->setLocale('_LOCALE_')->setPartnerTag('_TAG_'); // Get Response -$response = new Response($request); -$data = $response->get(); // JSON error ready for parsing -if ( $response->hasError() ) { - /** - * @param bool $single error - * @return mixed - */ - echo $response->getError(true); // Parsed error -} +$data = $cart->set(['_ASIN_' => 3]); // String ``` -### Add to cart: +### Rating: + +Get product average rating and count (Legacy). ```php -// Set Cart -$cart = new Cart(); -$cart->setLocale('{Your-locale}'); -$cart->setPartnerTag('_TAG_'); +use Apaapi\includes\Rating; -// Set Items -$items = [ - '{ASIN1}' => '3', // (_ASIN_ => {Quantity}) - '{ASIN2}' => '5' -]; +// Init Rating +$rating = new Rating('_ASIN_', '_LOCALE_'); // Get Response -return $cart->add($items); // String URL +$data = $rating->get(); // Array ``` ## Contributing: -Please read [CONTRIBUTING.md](https://github.com/Jakiboy/apaapi/blob/master/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. +Please read [CONTRIBUTING.md](https://github.com/Jakiboy/apaapi/blob/dev/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. ## Versioning: @@ -294,7 +233,7 @@ See also the full list of [contributors](https://github.com/Jakiboy/apaapi/contr ## License: -This project is licensed under the MIT License - see the [LICENSE](https://github.com/Jakiboy/apaapi/blob/master/LICENSE) file for details. +This project is licensed under the MIT License - see the [LICENSE](https://github.com/Jakiboy/apaapi/blob/dev/LICENSE) file for details. ## ⭐ Support: @@ -303,4 +242,4 @@ Please give it a Star if you like the project. ## 💡 Notice: * *The Amazon logo included in top of this page refers only to the [Amazon Product Advertising API V5](https://webservices.amazon.com/paapi5/documentation/index.html)*. -* *All available use case examples located in [/examples](https://github.com/Jakiboy/apaapi/tree/master/examples)*. +* *All available use case examples located in [/examples](https://github.com/Jakiboy/apaapi/tree/dev/examples)*. diff --git a/docs/builder.md b/docs/builder.md new file mode 100644 index 0000000..311fce2 --- /dev/null +++ b/docs/builder.md @@ -0,0 +1,567 @@ +# Apaapi Builder Documentation + +This documentation provides a comprehensive guide on how to use the Apaapi Builder. + +### ⚡ Initialization: + +To start using the Apaapi Builder, you need to initialize it with your API credentials. + +```php + +use Apaapi\includes\Builder; + +$builder = new Builder('_KEY_', '_SECRET_', '_TAG_', '_LOCALE_'); + +``` + +### ⚡ Authorization: + +You can verify if your builder credentials are authorized by using the `isAuthorized()` method. + +```php + +if ( $builder->isAuthorized() ) { + // ... +} + +``` + +### ⚡ Search: + +The **search** methods allows you to search for products using a variety of keywords. + +#### Single search + +To search for a single product, use the `searchOne()` method. + +* Accept: `String` Keyword, ASIN, ISBN, EAN, URL +* Include: `Filter` + +```php + +$data = $builder->searchOne('Sony Xperia Pro-I'); + +``` + +#### Multiple search + +To search for multiple products, use the `search()` method. + +* Accept: `String|Array` Keyword, ASIN, ISBN, EAN, URL +* Include: `Filter`, `Order`, `Count`, `Page` + +```php + +$data = $builder->search('Sony Xperia'); + +``` + +#### Search with filters + +You can also apply filters to your search. + +```php + +$data = $builder->search('SONY Xperia 5', 3, 1, [ + 'min' => 500.50, + 'max' => 800, + 'available' => true, + 'saving' => 10, + 'sort' => 'highest', + 'category' => 'Electronics', + 'brand' => 'Sony Mobile', + 'title' => 'SONY Xperia', + 'rank' => true, + 'reviews' => 4, + 'condition' => 'new', + 'node' => '218193031', + 'delivery' => 'any' +]); + +``` + +#### Search with order + +You can specify the order of the search results. + +```php + +$data = $builder->order(['price' => 'asc'])->search('SONY Xperia 1', 5, 1, [ + 'min' => 400, + 'sort' => 'highest', + 'category' => 'Electronics', + 'brand' => 'Sony Mobile', + 'title' => 'SONY Xperia' +]); + +``` + +### ⚡ Get: + +The **get** methods is designed to retrieve product(s) by specific IDs. + +#### Single get + +To get a single product, use the `getOne()` method. +* Accept: `String` ASIN, ISBN, URL +* Include: `Filter` + +```php + +$data = $builder->getOne('B09LPB9SQH'); + +``` + +#### Multiple get + +To get multiple products, use the `get()` method. +* Accept: `String|Array` ASIN, ISBN, URL +* Include: `Filter (Extra)`, `Order` + +```php + +$data = $builder->get([ + 'B09LPB9SQH', + 'b09g8xnw16', + '1718501870', + 'https://www.amazon.com/dp/B08BHTDLJR/' +]); + +``` + +#### Get with filters + +You can also apply filters when getting products. + +```php + +$data = $builder->get('B09LPB9SQH, 1718501870', [ + 'condition' => 'new' +]); + +``` + +#### Get with order + +You can specify the order of the retrieved products. + +```php + +$data = $builder->order(['title' => 'asc'])->get([ + 'B09LPB9SQH', + 'b09g8xnw16', + '1718501870' +]); + +``` + +### ⚡ Variation: + +The **variation** methods are designed to get product variations. + +#### Get variation + +The `getVariation()` method retrieve variations by specific IDs. + +* Accept: `String` ASIN, ISBN, URL +* Include: `Filter`, `Order`, `Count`, `Page` + +```php + +$data = $builder->getVariation('B09G8XNW16'); + +``` + +#### Search variation + +The `searchVariation()` method retrieve variations using a variety of keywords. + +* Accept: `String` Keyword, ASIN, ISBN, EAN, URL +* Include: `Filter`, `Order`, `Count`, `Page` +* Note: *This methode is expensive* + +```php + +$data = $builder->searchVariation('Apple iPhone 13 Mini Silicone'); + +``` + +### ⚡ Bestseller: + +The **bestseller** methods are designed to get bestseller product(s). + +#### Get bestseller + +The `getBestseller()` method retrieve bestseller by specific IDs. + +* Accept: `String` NodeId, URL (node) +* Include: `Filter`, `Order`, `Count`, `Page` +* Note: *This methode is expensive & unstable* + +```php + +$data = $builder->getBestseller('1455795031', 3, 1, [ + 'category' => 'Electronics' +]); + +``` + +#### Search bestseller + +The `searchBestseller()` method retrieve bestseller using a variety of keywords. + +* Accept: `String` Keyword, ASIN, ISBN, EAN, URL +* Include: `Filter`, `Order`, `Count`, `Page` +* Note: *This methode is expensive & unstable* + +```php + +$data = $builder->searchBestseller('Amazon Fire TV', 5, 1, [ + 'category' => 'Electronics' +]); + +``` + +### ⚡ Newest: + +The `getNewest()` method is designed to get the newest products. + +* Accept: `String` Keyword, ASIN, ISBN, EAN, URL +* Include: `Filter`, `Order`, `Count`, `Page` +* Note: *This methode is unstable* + +```php + +$data = $builder->getNewest('Amazon Fire TV', 3, 1, [ + 'category' => 'Electronics' +]); + +``` + +### ⚡ Category: + +This section covers operations related to product categories (**Search Index**). + +#### Get category + +The `getCategory()` method allows you to get product category using a variety of keywords. + +* Accept: `String` Keyword, ASIN, ISBN, EAN, URL +* Include: `Filter` + +```php + +$data = $builder->getCategory('B09G8XNW16'); + +``` + +#### Search category + +The `getCategory()` method allows you to get a detailed product category[] using keywords. + +* Accept: `String` Keyword, ASIN, ISBN, EAN, URL +* Include: `Filter` + +```php + +$data = $builder->searchCategory('B09G8XNW16'); + +``` + +### ⚡ Node: + +This section covers operations related to nodes (Categories). + +#### Get node + +The `getNode()` method allows you to retrieve a node using specific IDs. + +* Accept: `String` NodeId, URL + +```php + +$data = $builder->getNode('218193031'); + +``` + +#### Search node + +The `searchNode()` method allows you to search for a node using a variety of keywords. + +* Accept: `String` NodeId, Keyword, ASIN, URL +* Include: `Filter` +* Note: *This methode is expensive* + +```php + +$data = $builder->searchNode('218193031', [ + 'category' => 'Electronics' +]); + +``` + +### ⚡ Convert: + +This section covers operations related to converting keywords. + +#### Convert to EAN + +The `toEAN()` method allows you to convert keywords to EAN. + +* Accept: `String` Keyword, ASIN, ISBN, URL +* Include: `Filter` + +```php + +$data = $builder->toEAN('B09G8XNW16'); + +``` + +#### Convert to ASIN + +The `toASIN()` method allows you to convert keywords to ASIN or ISBN. + +* Accept: `String` Keyword, EAN, URL +* Include: `Filter` + +```php + +$data = $builder->toASIN('0194252780657'); + +``` + +#### Convert to NodeId + +The `toNodeId()` method allows you to convert keywords to NodeId. + +* Accept: `String` Keyword, ASIN, ISBN, EAN, URL +* Include: `Filter` + +```php + +$data = $builder->toNodeId('B09G8XNW16'); + +``` + +#### Convert to RootId + +The `toRootId()` method allows you to convert keywords to root NodeId. + +* Accept: `String` Keyword, ASIN, ISBN, EAN, URL +* Include: `Filter` + +```php + +$data = $builder->toRootId('B09G8XNW16'); + +``` + +### ⚡ Cart: + +This section covers operations related to the Amazon cart. + +#### Get cart + +The `getCart()` method allows you to generate an "Add to Cart" URL using specific IDs, +You can specify the quantity for each individual ID. + +* Accept: `String|Array` ASIN, ISBN, URL +* Note: No authentication is required for this operation. + +```php + +$data = $builder->getCart([ + 'B09LPB9SQH' => 3, + 'B09G8XNW16' => 1 +]); + +``` + +### ⚡ Rating: + +This section covers operations related to product ratings (**Legacy**). + +#### Get rating + +The `getRating()` method allows you to get the average rating and count for a product using specific IDs. + +* Accept: `String` ASIN, ISBN, URL +* Note: No authentication is required for this operation. + +```php + +$data = $builder->getRating('B09G8XNW16'); + +``` + +#### Search rating + +The `searchRating()` method allows you to search for the rating of a product using many keywords. + +* Accept: `String` Keyword, ASIN, ISBN, EAN, URL + +```php + +$data = $builder->searchRating('Sony Xperia Pro'); + +``` + +### ⚡ Filters: + +This section covers the filters that can be applied when performing a **search** operations. +The **get** operations only accept extra parameters. + +#### Search parameters + +* `sort`: Sort order [ featured | newest | relevance | reviews | highest | lowest ]. (string) +* `condition`: Condition [ any | new | used | refurbished | collectible ]. (string) +* `delivery`: Delivery type [ global | free | fulfilled | prime ]. (string) +* `category`: Category [ see provider categories ]. (string) +* `min`: Minimum price. (float|integer|string) +* `max`: Maximum price. (float|integer|string) +* `available`: Availability. (boolean) +* `saving`: Minimum saving percentage. (integer|string) +* `brand`: Brand. (string) +* `title`: Title. (string) +* `rank`: Include rank in results. (boolean) +* `reviews`: Minimum number of reviews. (integer|string) +* `node`: Node Id. (string) + +#### Extra parameters + +* `lang`: Language [ see provider languages ]. (string) +* `currency`: Currency [ see provider currencies ]. (string) +* `merchant`: Merchant name. (string) +* `marketplace`: Marketplace URL. (string) +* `actor`: Actor name. (string) +* `artist`: Artist name. (string) +* `author`: Author name. (string) + +### ⚡ Geotargeting: + +The Geotargeting feature allows you to redirect the product URL based on the visitor's geographical location. This feature is useful when you want to serve different content to users based on their location. It uses the `redirect()` method, where the `target` parameter matches the detected country code. + +#### Parameters + +The **redirect** method accepts an array with the following keys: + +* `code`: The country code obtained from an external API. +* `target`: An array mapping country codes to Amazon affiliate tags. +* `api`: An array containing information about the external IP and GEO APIs. + +#### Using external country code + +In this example, the country code is obtained from an external API like [MaxMind GeoIP](https://dev.maxmind.com/geoip). + +```php + +$data = $builder->redirect([ + 'code' => 'us', // Country code from API + 'target' => [ + 'us' => 'affiliate-tag-us', + 'es' => 'affiliate-tag-es', + 'fr' => 'affiliate-tag-fr', + 'ca' => 'affiliate-tag-ca' + ] +])->searchOne('Sony Xperia Pro'); + +``` + +#### Using external GEO API + +In this example, an external GEO API is used for IP detection. Note that internal IP detection may not always be reliable. + +```php + +$data = $builder->redirect([ + 'api' => [ + 'geo' => [ + 'address' => 'http://ip-api.com/json/{ip}', + 'param' => 'countryCode' + ] + ], + 'target' => [ + 'us' => 'affiliate-tag-us' + ] +])->searchOne('Sony Xperia Pro'); + +``` + +#### External GEO & IP APIs + +In this example, both external GEO and IP APIs are used for better accuracy. + +```php + +$data = $builder->redirect([ + 'api' => [ + 'ip' => [ + 'address' => 'https://api.ipify.org/?format=json', + 'param' => 'ip' + ], + 'geo' => [ + 'address' => 'http://ip-api.com/json/{ip}', + 'param' => 'countryCode' + ] + ], + 'target' => [ + 'us' => 'affiliate-tag-us' + ] +])->searchOne('Sony Xperia Pro'); + +``` + +Remember to replace 'affiliate-tag-us' etc. with your actual Amazon affiliate tags. + +#### Advanced usage + +You can customize the behavior of the Geotargeting using the following methods before calling the `redirect()` method. + +```php + +// Redirect 404 page to search page +Geotargeting::redirectNotFound(); + +// Set custom visitor key (Caching) +Geotargeting::setVisitorKey('visitor-id'); + +// Exclude IPs from geotargeting +Geotargeting::setException(['127.0.0.1']); + +``` + +### ⚡ Error handling: + +The Builder class provides methods to handle HTTP errors that may occur during API calls or HTTP client operations. +Including semantic errors with HTTP code `200`. + +#### Check error + +The `hasError()` method returns a boolean indicating whether an error has occurred. + +```php + +if ( $builder->hasError() ) { + // ... +} + +``` + +#### Get error + +If an error has occurred, you can retrieve the error message using the `getError()` method. + +```php + +if ( $builder->hasError() ) { + $error = $builder->getError(); +} + +``` + +### 💡 Note: + +* **Keyword** : Keywords like ASIN differ from marktplace to other, Whiche can cause 404. +* **Filter** : Performing product search with a keyword without using any filters may yield undesired results. +* **Expensive** : The operation may consume more resources or take longer to execute. +* **Unstable** : The operation may yield unstable results. diff --git a/docs/provider.md b/docs/provider.md new file mode 100644 index 0000000..708cc13 --- /dev/null +++ b/docs/provider.md @@ -0,0 +1,68 @@ +# Apaapi Provider Documentation + +This document guide you on how to utilize the Apaapi static Provider. + +### ⚡ Initialization: + +To use Apaapi Provider, it needs to be imported into your project. + +```php + +use Apaapi\includes\Provider; + +``` + +### ⚡ Data: + +The Apaapi Provider offers various methods to retrieve different types of static data. + +#### Retrieving Host, and other static values + +```php + +$data = Provider::HOST; // Retrieve the host variable +$data = Provider::CONDITION; // Retrieve the condition filter +$data = Provider::SORT; // Retrieve the sort filter +$data = Provider::SHIPPING; // Retrieve the delivery filter + +``` + +#### Retrieving Regions and Countries + +```php + +$data = Provider::getRegions(); // Retrieve all regions +$data = Provider::getRegion('com'); // Retrieve a specific region +$data = Provider::getCountries(); // Retrieve all countries +$data = Provider::getCountry('com'); // Retrieve a specific country + +``` + +#### Retrieving Locales and Languages + +```php + +$data = Provider::getLocales(); // Retrieve all locales +$data = Provider::getLanguages(); // Retrieve all languages +$data = Provider::getLanguages('com'); // Retrieve languages for a specific region + +``` + +#### Retrieving Categories + +```php + +$data = Provider::getCategories(); // Retrieve all categories +$data = Provider::getCategories('com'); // Retrieve categories for a specific region +$data = Provider::getCategoryId('Apps & Games', 'com'); // Retrieve the ID of a specific category in a specific region + +``` + +#### Retrieving Currency Symbols + +```php + +$data = Provider::getSymbols(); // Retrieve all currency symbols +$data = Provider::getSymbol('USD'); // Retrieve a specific currency symbol + +``` diff --git a/docs/ressources.md b/docs/ressources.md index 18f639e..ee38dc2 100644 --- a/docs/ressources.md +++ b/docs/ressources.md @@ -1,228 +1,102 @@ # Ressources -Amazon Product Advertising API V5.0 all ressources for each operation. +This document provides a comprehensive list of resources for each operation in the Amazon Product Advertising API V5.0. -## GetItems operation (59): +### ⚡ GetItems operation: -``` - "BrowseNodeInfo.BrowseNodes", - "BrowseNodeInfo.BrowseNodes.Ancestor", - "BrowseNodeInfo.BrowseNodes.SalesRank", - "BrowseNodeInfo.WebsiteSalesRank", - "CustomerReviews.Count", - "CustomerReviews.StarRating", - "Images.Primary.Small", - "Images.Primary.Medium", - "Images.Primary.Large", - "Images.Variants.Small", - "Images.Variants.Medium", - "Images.Variants.Large", - "ItemInfo.ByLineInfo", - "ItemInfo.ContentInfo", - "ItemInfo.ContentRating", - "ItemInfo.Classifications", - "ItemInfo.ExternalIds", - "ItemInfo.Features", - "ItemInfo.ManufactureInfo", - "ItemInfo.ProductInfo", - "ItemInfo.TechnicalInfo", - "ItemInfo.Title", - "ItemInfo.TradeInInfo", - "Offers.Listings.Availability.MaxOrderQuantity", - "Offers.Listings.Availability.Message", - "Offers.Listings.Availability.MinOrderQuantity", - "Offers.Listings.Availability.Type", - "Offers.Listings.Condition", - "Offers.Listings.Condition.ConditionNote", - "Offers.Listings.Condition.SubCondition", - "Offers.Listings.DeliveryInfo.IsAmazonFulfilled", - "Offers.Listings.DeliveryInfo.IsFreeShippingEligible", - "Offers.Listings.DeliveryInfo.IsPrimeEligible", - "Offers.Listings.DeliveryInfo.ShippingCharges", - "Offers.Listings.IsBuyBoxWinner", - "Offers.Listings.LoyaltyPoints.Points", - "Offers.Listings.MerchantInfo", - "Offers.Listings.Price", - "Offers.Listings.ProgramEligibility.IsPrimeExclusive", - "Offers.Listings.ProgramEligibility.IsPrimePantry", - "Offers.Listings.Promotions", - "Offers.Listings.SavingBasis", - "Offers.Summaries.HighestPrice", - "Offers.Summaries.LowestPrice", - "Offers.Summaries.OfferCount", - "ParentASIN", - "RentalOffers.Listings.Availability.MaxOrderQuantity", - "RentalOffers.Listings.Availability.Message", - "RentalOffers.Listings.Availability.MinOrderQuantity", - "RentalOffers.Listings.Availability.Type", - "RentalOffers.Listings.BasePrice", - "RentalOffers.Listings.Condition", - "RentalOffers.Listings.Condition.ConditionNote", - "RentalOffers.Listings.Condition.SubCondition", - "RentalOffers.Listings.DeliveryInfo.IsAmazonFulfilled", - "RentalOffers.Listings.DeliveryInfo.IsFreeShippingEligible", - "RentalOffers.Listings.DeliveryInfo.IsPrimeEligible", - "RentalOffers.Listings.DeliveryInfo.ShippingCharges", - "RentalOffers.Listings.MerchantInfo" -``` - -## SearchItems operation (60): +The GetItems operation provides the following resources (59): ``` - "BrowseNodeInfo.BrowseNodes", - "BrowseNodeInfo.BrowseNodes.Ancestor", - "BrowseNodeInfo.BrowseNodes.SalesRank", - "BrowseNodeInfo.WebsiteSalesRank", - "CustomerReviews.Count", - "CustomerReviews.StarRating", - "Images.Primary.Small", - "Images.Primary.Medium", - "Images.Primary.Large", - "Images.Variants.Small", - "Images.Variants.Medium", - "Images.Variants.Large", - "ItemInfo.ByLineInfo", - "ItemInfo.ContentInfo", - "ItemInfo.ContentRating", - "ItemInfo.Classifications", - "ItemInfo.ExternalIds", - "ItemInfo.Features", - "ItemInfo.ManufactureInfo", - "ItemInfo.ProductInfo", - "ItemInfo.TechnicalInfo", - "ItemInfo.Title", - "ItemInfo.TradeInInfo", - "Offers.Listings.Availability.MaxOrderQuantity", - "Offers.Listings.Availability.Message", - "Offers.Listings.Availability.MinOrderQuantity", - "Offers.Listings.Availability.Type", - "Offers.Listings.Condition", - "Offers.Listings.Condition.ConditionNote", - "Offers.Listings.Condition.SubCondition", - "Offers.Listings.DeliveryInfo.IsAmazonFulfilled", - "Offers.Listings.DeliveryInfo.IsFreeShippingEligible", - "Offers.Listings.DeliveryInfo.IsPrimeEligible", - "Offers.Listings.DeliveryInfo.ShippingCharges", - "Offers.Listings.IsBuyBoxWinner", - "Offers.Listings.LoyaltyPoints.Points", - "Offers.Listings.MerchantInfo", - "Offers.Listings.Price", - "Offers.Listings.ProgramEligibility.IsPrimeExclusive", - "Offers.Listings.ProgramEligibility.IsPrimePantry", - "Offers.Listings.Promotions", - "Offers.Listings.SavingBasis", - "Offers.Summaries.HighestPrice", - "Offers.Summaries.LowestPrice", - "Offers.Summaries.OfferCount", - "ParentASIN", - "RentalOffers.Listings.Availability.MaxOrderQuantity", - "RentalOffers.Listings.Availability.Message", - "RentalOffers.Listings.Availability.MinOrderQuantity", - "RentalOffers.Listings.Availability.Type", - "RentalOffers.Listings.BasePrice", - "RentalOffers.Listings.Condition", - "RentalOffers.Listings.Condition.ConditionNote", - "RentalOffers.Listings.Condition.SubCondition", - "RentalOffers.Listings.DeliveryInfo.IsAmazonFulfilled", - "RentalOffers.Listings.DeliveryInfo.IsFreeShippingEligible", - "RentalOffers.Listings.DeliveryInfo.IsPrimeEligible", - "RentalOffers.Listings.DeliveryInfo.ShippingCharges", - "RentalOffers.Listings.MerchantInfo", - "SearchRefinements" +- BrowseNodeInfo.BrowseNodes +- BrowseNodeInfo.BrowseNodes.Ancestor +- BrowseNodeInfo.BrowseNodes.SalesRank +- BrowseNodeInfo.WebsiteSalesRank +- CustomerReviews.Count +- CustomerReviews.StarRating +- Images.Primary.Small +- Images.Primary.Medium +- Images.Primary.Large +- Images.Variants.Small +- Images.Variants.Medium +- Images.Variants.Large +- ItemInfo.ByLineInfo +- ItemInfo.ContentInfo +- ItemInfo.ContentRating +- ItemInfo.Classifications +- ItemInfo.ExternalIds +- ItemInfo.Features +- ItemInfo.ManufactureInfo +- ItemInfo.ProductInfo +- ItemInfo.TechnicalInfo +- ItemInfo.Title +- ItemInfo.TradeInInfo +- Offers.Listings.Availability.MaxOrderQuantity +- Offers.Listings.Availability.Message +- Offers.Listings.Availability.MinOrderQuantity +- Offers.Listings.Availability.Type +- Offers.Listings.Condition +- Offers.Listings.Condition.ConditionNote +- Offers.Listings.Condition.SubCondition +- Offers.Listings.DeliveryInfo.IsAmazonFulfilled +- Offers.Listings.DeliveryInfo.IsFreeShippingEligible +- Offers.Listings.DeliveryInfo.IsPrimeEligible +- Offers.Listings.DeliveryInfo.ShippingCharges +- Offers.Listings.IsBuyBoxWinner +- Offers.Listings.LoyaltyPoints.Points +- Offers.Listings.MerchantInfo +- Offers.Listings.Price +- Offers.Listings.ProgramEligibility.IsPrimeExclusive +- Offers.Listings.ProgramEligibility.IsPrimePantry +- Offers.Listings.Promotions +- Offers.Listings.SavingBasis +- Offers.Summaries.HighestPrice +- Offers.Summaries.LowestPrice +- Offers.Summaries.OfferCount +- ParentASIN +- RentalOffers.Listings.Availability.MaxOrderQuantity +- RentalOffers.Listings.Availability.Message +- RentalOffers.Listings.Availability.MinOrderQuantity +- RentalOffers.Listings.Availability.Type +- RentalOffers.Listings.BasePrice +- RentalOffers.Listings.Condition +- RentalOffers.Listings.Condition.ConditionNote +- RentalOffers.Listings.Condition.SubCondition +- RentalOffers.Listings.DeliveryInfo.IsAmazonFulfilled +- RentalOffers.Listings.DeliveryInfo.IsFreeShippingEligible +- RentalOffers.Listings.DeliveryInfo.IsPrimeEligible +- RentalOffers.Listings.DeliveryInfo.ShippingCharges +- RentalOffers.Listings.MerchantInfo ``` -## GetVariations operation (62): +### ⚡ SearchItems operation: -``` - "BrowseNodeInfo.BrowseNodes", - "BrowseNodeInfo.BrowseNodes.Ancestor", - "BrowseNodeInfo.BrowseNodes.SalesRank", - "BrowseNodeInfo.WebsiteSalesRank", - "CustomerReviews.Count", - "CustomerReviews.StarRating", - "Images.Primary.Small", - "Images.Primary.Medium", - "Images.Primary.Large", - "Images.Variants.Small", - "Images.Variants.Medium", - "Images.Variants.Large", - "ItemInfo.ByLineInfo", - "ItemInfo.ContentInfo", - "ItemInfo.ContentRating", - "ItemInfo.Classifications", - "ItemInfo.ExternalIds", - "ItemInfo.Features", - "ItemInfo.ManufactureInfo", - "ItemInfo.ProductInfo", - "ItemInfo.TechnicalInfo", - "ItemInfo.Title", - "ItemInfo.TradeInInfo", - "Offers.Listings.Availability.MaxOrderQuantity", - "Offers.Listings.Availability.Message", - "Offers.Listings.Availability.MinOrderQuantity", - "Offers.Listings.Availability.Type", - "Offers.Listings.Condition", - "Offers.Listings.Condition.ConditionNote", - "Offers.Listings.Condition.SubCondition", - "Offers.Listings.DeliveryInfo.IsAmazonFulfilled", - "Offers.Listings.DeliveryInfo.IsFreeShippingEligible", - "Offers.Listings.DeliveryInfo.IsPrimeEligible", - "Offers.Listings.DeliveryInfo.ShippingCharges", - "Offers.Listings.IsBuyBoxWinner", - "Offers.Listings.LoyaltyPoints.Points", - "Offers.Listings.MerchantInfo", - "Offers.Listings.Price", - "Offers.Listings.ProgramEligibility.IsPrimeExclusive", - "Offers.Listings.ProgramEligibility.IsPrimePantry", - "Offers.Listings.Promotions", - "Offers.Listings.SavingBasis", - "Offers.Summaries.HighestPrice", - "Offers.Summaries.LowestPrice", - "Offers.Summaries.OfferCount", - "ParentASIN", - "RentalOffers.Listings.Availability.MaxOrderQuantity", - "RentalOffers.Listings.Availability.Message", - "RentalOffers.Listings.Availability.MinOrderQuantity", - "RentalOffers.Listings.Availability.Type", - "RentalOffers.Listings.BasePrice", - "RentalOffers.Listings.Condition", - "RentalOffers.Listings.Condition.ConditionNote", - "RentalOffers.Listings.Condition.SubCondition", - "RentalOffers.Listings.DeliveryInfo.IsAmazonFulfilled", - "RentalOffers.Listings.DeliveryInfo.IsFreeShippingEligible", - "RentalOffers.Listings.DeliveryInfo.IsPrimeEligible", - "RentalOffers.Listings.DeliveryInfo.ShippingCharges", - "RentalOffers.Listings.MerchantInfo", - "VariationSummary.Price.HighestPrice", - "VariationSummary.Price.LowestPrice", - "VariationSummary.VariationDimension" -``` - -## GetBrowseNodes operation (2): +The SearchItems operation provides the following resources (60): ``` - "BrowseNodes.Ancestor", - "BrowseNodes.Children" +- Same as operation resources +- SearchRefinements ``` -### Contributing: - -Please read [CONTRIBUTING.md](https://github.com/Jakiboy/apaapi/blob/master/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. +### ⚡ GetVariations operation: -### Versioning: +The GetVariations operation provides the following resources (62): -We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/Jakiboy/apaapi/tags). - -### Authors: - -* **Jihad Sinnaour** - [Jakiboy](https://github.com/Jakiboy) (*Initial work*) +``` +- Same as operation resources +- VariationSummary.Price.HighestPrice +- VariationSummary.Price.LowestPrice +- VariationSummary.VariationDimension +``` -See also the full list of [contributors](https://github.com/Jakiboy/apaapi/contributors) who participated in this project. Any suggestions (Pull requests) are welcome! +### ⚡ GetBrowseNodes operation: -### License: +The GetBrowseNodes operation provides the following resources (2): -This project is licensed under the MIT License - see the [LICENSE](https://github.com/Jakiboy/apaapi/blob/master/LICENSE) file for details. +``` +- BrowseNodes.Ancestor +- BrowseNodes.Children +``` -### ⭐ Support: +### 💡 Note: -Please give it a Star if you like the project. +Each resource provides specific information about the product. For example, ItemInfo.Title provides the title of the product, CustomerReviews.Count provides the number of customer reviews, and so on. \ No newline at end of file diff --git a/docs/tlds.md b/docs/tlds.md index 9a288c4..b116bd5 100644 --- a/docs/tlds.md +++ b/docs/tlds.md @@ -1,49 +1,27 @@ -# TLDs of locales - -Amazon Product Advertising API V5.0 all TLDs by locales (regions). - -| Locale | TLD | -|----------------------|---------| -| Australia | au | -| Belgium | be | -| Brazil | com.br | -| Canada | ca | -| Egypt | eg | -| France | fr | -| Germany | de | -| India | in | -| Italy | it | -| Japan | co.jp | -| Mexico | com.mx | -| Netherlands | nl | -| Poland | pl | -| Singapore | sg | -| Saudi Arabia | sa | -| Spain | es | -| Sweden | se | -| Turkey | com.tr | -| United Arab Emirates | ae | -| United Kingdom | co.uk | -| United States | com | - -### Contributing: - -Please read [CONTRIBUTING.md](https://github.com/Jakiboy/apaapi/blob/master/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. - -### Versioning: - -We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/Jakiboy/apaapi/tags). - -### Authors: - -* **Jihad Sinnaour** - [Jakiboy](https://github.com/Jakiboy) (*Initial work*) - -See also the full list of [contributors](https://github.com/Jakiboy/apaapi/contributors) who participated in this project. Any suggestions (Pull requests) are welcome! - -### License: - -This project is licensed under the MIT License - see the [LICENSE](https://github.com/Jakiboy/apaapi/blob/master/LICENSE) file for details. - -### ⭐ Support: - -Please give it a Star if you like the project. +# TLDs (Locales) + +This document provides a comprehensive list of the top-level domains (TLDs) used by Amazon Product Advertising API V5.0 for different locales (regions). Each locale corresponds to a specific Amazon marketplace. + +| Locale (Region) | TLD (Marketplace) | +|----------------------|-------------------| +| Australia | au | +| Belgium | be | +| Brazil | com.br | +| Canada | ca | +| Egypt | eg | +| France | fr | +| Germany | de | +| India | in | +| Italy | it | +| Japan | co.jp | +| Mexico | com.mx | +| Netherlands | nl | +| Poland | pl | +| Singapore | sg | +| Saudi Arabia | sa | +| Spain | es | +| Sweden | se | +| Turkey | com.tr | +| United Arab Emirates | ae | +| United Kingdom | co.uk | +| United States | com | diff --git a/examples/advanced/custom-client.php b/examples/advanced/custom-client.php new file mode 100644 index 0000000..f80359f --- /dev/null +++ b/examples/advanced/custom-client.php @@ -0,0 +1,82 @@ + + * @link : https://jakiboy.github.io/apaapi/ + * @license : MIT + * + * This file if a part of Apaapi Lib. + */ + +include('../../src/Autoloader.php'); +\apaapi\Autoloader::init(); + +use Apaapi\operations\SearchItems; +use Apaapi\lib\Request; +use Apaapi\lib\Response; +use Apaapi\includes\Client; + +/** + * Custom request client class. + */ +class MyClient extends Client +{ + /** + * Override parent constructor. + * + * @inheritdoc + */ + public function __construct(string $endpoint, array $params = []) + { + // Enable request client exception + parent::__construct($endpoint, $params, true); + + $this->timeout = 5; // Custom timeout + $this->redirect = 3; // Custom redirection + $this->encoding = 'gzip'; // Custom encoding + } + + /** + * Override handler behavior. + * + * @inheritdoc + */ + protected function setHandler() + { + $this->handler = curl_init(); + curl_setopt_array($this->handler, [ + CURLOPT_URL => $this->endpoint, + CURLOPT_HTTPHEADER => $this->getRequestHeader(), + CURLOPT_POSTFIELDS => $this->getRequestPayload(), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, // Force POST + CURLOPT_SSL_VERIFYPEER => true, // Force SSL + CURLOPT_FOLLOWLOCATION => true, // Force follow location + CURLOPT_TIMEOUT => $this->timeout, + CURLOPT_ENCODING => $this->encoding, + CURLOPT_MAXREDIRS => $this->redirect + ]); + } +} + +// Set operation +$operation = new SearchItems(); +$operation->setPartnerTag('_TAG_')->setKeywords('_KEYWORDS_'); + +// Prapere request +$request = new Request('_KEY_', '_SECRET_'); +$request->setLocale('_LOCALE_')->setPayload($operation); + +// Set custom client after payload +$request->setClient( + new MyClient($request->getEndpoint(), $request->getParams()) +); + +// Get response +$response = new Response($request); +$data = $response->get(); // Array +var_dump($data); + +// Any PR is welcome! diff --git a/examples/custom-ressources.php b/examples/advanced/custom-ressources.php similarity index 62% rename from examples/custom-ressources.php rename to examples/advanced/custom-ressources.php index 9ce6854..3cc1fce 100644 --- a/examples/custom-ressources.php +++ b/examples/advanced/custom-ressources.php @@ -10,11 +10,7 @@ * This file if a part of Apaapi Lib. */ -/** - * @see You can use Composer, - * Or include Apaapi standalone autoloader here. - */ -include('../src/Autoloader.php'); +include('../../src/Autoloader.php'); \apaapi\Autoloader::init(); use Apaapi\operations\SearchItems; @@ -23,20 +19,22 @@ // Set operation $operation = new SearchItems(); -$operation->setPartnerTag('{Your-partner-tag}')->setKeywords('{Your-keywords}') -->setResources([ +$operation->setPartnerTag('_TAG_')->setKeywords('_KEYWORDS_'); + +// Set ressources (3) +$operation->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); +$request = new Request('_KEY_', '_SECRET_'); +$request->setLocale('_LOCALE_')->setPayload($operation); // Get response $response = new Response($request); $data = $response->get(); // Array -print_r($data); +var_dump($data); -// Any suggestions (PR) are welcome! +// Any PR is welcome! diff --git a/examples/advanced/error-handling.php b/examples/advanced/error-handling.php new file mode 100644 index 0000000..8ac4393 --- /dev/null +++ b/examples/advanced/error-handling.php @@ -0,0 +1,38 @@ + + * @link : https://jakiboy.github.io/apaapi/ + * @license : MIT + * + * This file if a part of Apaapi Lib. + */ + +include('../../src/Autoloader.php'); +\apaapi\Autoloader::init(); + +use Apaapi\operations\SearchItems; +use Apaapi\lib\Request; +use Apaapi\lib\Response; + +// Set operation +$operation = new SearchItems(); +$operation->setPartnerTag('_TAG_')->setKeywords('_KEYWORDS_'); + +// Prapere request +$request = new Request('_KEY_', '_SECRET_'); +$request->setLocale('_LOCALE_')->setPayload($operation); + +// Get response +$response = new Response($request); +$data = $response->get(); // Array + +// Handle response error +if ( $response->hasError() ) { + echo $response->getError(); // String +} +var_dump($data); + +// Any PR is welcome! diff --git a/examples/advanced/response-normalizer.php b/examples/advanced/response-normalizer.php new file mode 100644 index 0000000..94ae61b --- /dev/null +++ b/examples/advanced/response-normalizer.php @@ -0,0 +1,36 @@ + + * @link : https://jakiboy.github.io/apaapi/ + * @license : MIT + * + * This file if a part of Apaapi Lib. + */ + +include('../../src/Autoloader.php'); +\apaapi\Autoloader::init(); + +use Apaapi\operations\SearchItems; +use Apaapi\lib\Request; +use Apaapi\lib\Response; + +// Set operation +$operation = new SearchItems(); +$operation->setPartnerTag('_TAG_')->setKeywords('_KEYWORDS_'); + +// Set items (3) +$operation->setItemCount(3); + +// Prapere request +$request = new Request('_KEY_', '_SECRET_'); +$request->setLocale('_LOCALE_')->setPayload($operation); + +// Get response +$response = new Response($request, Response::NORMALIZE); +$data = $response->get(); // Normalized Array +var_dump($data); + +// Any PR is welcome! diff --git a/examples/basic.php b/examples/basic.php index 0d1188b..49a9969 100644 --- a/examples/basic.php +++ b/examples/basic.php @@ -10,10 +10,6 @@ * 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(); @@ -23,16 +19,16 @@ // Set operation $operation = new SearchItems(); -$operation->setPartnerTag('{Your-partner-tag}')->setKeywords('{Your-keywords}'); +$operation->setPartnerTag('_TAG_')->setKeywords('_KEYWORDS_'); // Prapere request -$request = new Request('{Your-key-id}','{Your-secrect-key}'); -$request->setLocale('{Your-locale}')->setPayload($operation); +$request = new Request('_KEY_', '_SECRET_'); +$request->setLocale('_LOCALE_')->setPayload($operation); // Get response $response = new Response($request); -$data = $response->get(); // Array $body = $response->getBody(); // String -print_r($data); +$data = $response->get(); // Array +var_dump($data); -// Any suggestions (PR) are welcome! +// Any PR is welcome! diff --git a/examples/simple.php b/examples/builder.php similarity index 59% rename from examples/simple.php rename to examples/builder.php index 8c55a5b..bed3dd8 100644 --- a/examples/simple.php +++ b/examples/builder.php @@ -10,20 +10,16 @@ * 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}'); +$builder = new Builder('_KEY_', '_SECRET_', '_TAG_', '_LOCALE_'); // Get response -$data = $builder->search('{Your-keywords}'); // Array -print_r($data); +$data = $builder->search('_KEYWORDS_'); // Normalized Array +var_dump($data); -// Any suggestions (PR) are welcome! +// Any PR is welcome! diff --git a/examples/add-to-cart.php b/examples/cart.php similarity index 56% rename from examples/add-to-cart.php rename to examples/cart.php index 77cdf68..6334266 100644 --- a/examples/add-to-cart.php +++ b/examples/cart.php @@ -10,28 +10,23 @@ * 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\lib\Cart; -// init cart +// Init cart $cart = new Cart(); -$cart->setLocale('{Your-locale}'); -$cart->setPartnerTag('{Your-partner-tag}'); +$cart->setLocale('_LOCALE_')->setPartnerTag('_TAG_'); // Set items $items = [ - '{ASIN1|ISBN1}' => '3', // ({ASIN|ISBN} => {Quantity}) - '{ASIN2|ISBN2}' => '5' + '_ASIN_' => '3', // ({_ASIN_|_ISBN_} => {Quantity}) + '_ISBN_' => '5' ]; // Get response -$url = $cart->set($items); -echo $url; // String +$url = $cart->set($items); // String +var_dump($url); -// Any suggestions (PR) are welcome! +// Any PR is welcome! diff --git a/examples/custom-client.php b/examples/custom-client.php deleted file mode 100644 index 4793a64..0000000 --- a/examples/custom-client.php +++ /dev/null @@ -1,84 +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\Client; - -/** - * Custom request client class. - */ -class MyClient extends Client -{ - // 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() ) { - - // 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 - curl_setopt($this->handler, CURLOPT_TIMEOUT, 10); // Set custom timeout - - } elseif ( self::hasStream() ) { - - // Override stream - $this->handler = stream_context_create([ - 'http' => [ - 'method' => 'POST', - 'header' => $this->getRequestHeader(), - 'content' => $this->getRequestContent(), - 'timeout' => $this->timeout // Set custom timeout - ] - ]); - } - } -} - -// Set operation -$operation = new SearchItems(); -$operation->setPartnerTag('{Your-partner-tag}')->setKeywords('{Your-keywords}'); - -// 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 MyClient($request->getEndpoint(), $request->getParams()) -); - -// Get response -$response = new Response($request); -$data = $response->get(); // Array -print_r($data); - -// Any suggestions (PR) are welcome! diff --git a/examples/custom-error-handling.php b/examples/custom-error-handling.php deleted file mode 100644 index f720312..0000000 --- a/examples/custom-error-handling.php +++ /dev/null @@ -1,43 +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; - -// Set Operation -$operation = new SearchItems(); -$operation->setPartnerTag('{Your-partner-tag}')->setKeywords('{Your-keywords}'); - -// Prapere Request -$request = new Request('{Your-key-id}','{Your-secrect-key}'); -$request->setLocale('{Your-locale}')->setPayload($operation); - -// Get Response -$response = new Response($request); -$data = $response->get(); // JSON error ready for parsing -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/custom-response.php b/examples/custom-response.php deleted file mode 100644 index c2d788d..0000000 --- a/examples/custom-response.php +++ /dev/null @@ -1,37 +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; - -// Set operation -$operation = new SearchItems(); -$operation->setPartnerTag('{Your-partner-tag}')->setKeywords('{Your-keywords}'); - -// Prapere request -$request = new Request('{Your-key-id}','{Your-secrect-key}'); -$request->setLocale('{Your-locale}')->setPayload($operation); - -// Get response -$response = new Response($request, new ResponseType('array'), Response::PARSE); -$body = $response->getBody(); // String -print_r($body); - -// Hope you found this useful, any suggestions (Pull requests) are welcome! diff --git a/examples/rating.php b/examples/rating.php new file mode 100644 index 0000000..ef652aa --- /dev/null +++ b/examples/rating.php @@ -0,0 +1,23 @@ + + * @link : https://jakiboy.github.io/apaapi/ + * @license : MIT + * + * This file if a part of Apaapi Lib. + */ + +include('../src/Autoloader.php'); +\apaapi\Autoloader::init(); + +use Apaapi\includes\Rating; + +// Init Rating +$rating = new Rating('_ASIN_', '_LOCALE_'); +$data = $rating->get(); // Array +var_dump($data); + +// Any PR is welcome! diff --git a/src/Autoloader.php b/src/Autoloader.php index d89eb7b..2f7b4d0 100644 --- a/src/Autoloader.php +++ b/src/Autoloader.php @@ -25,13 +25,13 @@ final class Autoloader /** * Register autoloader. + * + * @access private */ - public function __construct() + private function __construct() { - if ( !static::$initialized ) { - spl_autoload_register([__CLASS__, 'autoload']); - static::$initialized = true; - } + spl_autoload_register([__CLASS__, 'autoload']); + static::$initialized = true; } /** @@ -43,50 +43,35 @@ public function __destruct() } /** - * Restrict object clone. - */ - public function __clone() - { - die(__METHOD__.': Clone denied'); - } - - /** - * Restrict object unserialize. + * Autoloader method. + * + * @access private + * @param string $class + * @return void */ - public function __wakeup() - { - die(__METHOD__.': Unserialize denied'); - } + private function autoload(string $class) + { + $namespace = __NAMESPACE__ . '\\'; + if ( strpos($class, $namespace) === 0 ) { + $class = str_replace($namespace, '', $class); + $class = dirname(__DIR__) . "/src/{$class}.php"; + $class = str_replace('\\', '/', $class); + if ( file_exists($class) ) { + require_once $class; + } + } + } /** * Initialize autoloader. - * + * * @access public * @return void */ public static function init() { if ( !static::$initialized ) { - new static; + new self; } } - - /** - * Autoloader method. - * - * @access private - * @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(string $class) - { - if ( strpos($class, __NAMESPACE__ . '\\') === 0 ) { - $class = str_replace(__NAMESPACE__ . '\\', '', $class); - $class = str_replace('\\', '/', $class); - $root = str_replace('\\', '/', dirname(__DIR__)); - require_once("{$root}/src/{$class}.php"); - } - } } diff --git a/src/includes/Builder.php b/src/includes/Builder.php index 760af96..564324c 100644 --- a/src/includes/Builder.php +++ b/src/includes/Builder.php @@ -53,6 +53,8 @@ final class Builder private $redirect; private $order = ['title', 'price']; + public static $isCategory = false; + /** * Set request authentication. * @@ -107,7 +109,9 @@ public function get($ids, array $filter = []) : array $this->setup('get')->filter($filter); $this->operation->setResources( - $this->getDefaultResources() + $this->getDefaultResources( + $this->getFilterResources($filter) + ) ); $this->operation->setItemIds( @@ -131,7 +135,9 @@ public function getOne(string $id, array $filter = []) : array $this->setup('get')->filter($filter); $this->operation->setResources( - $this->getDefaultResources() + $this->getDefaultResources( + $this->getFilterResources($filter) + ) ); $this->operation->setItemIds([ @@ -157,7 +163,9 @@ public function search($keywords, int $count = 5, int $page = 1, array $filter = $this->setup('search')->filter($filter); $this->operation->setResources( - $this->getDefaultResources() + $this->getDefaultResources( + $this->getFilterResources($filter) + ) ); $this->operation->setItemCount($count)->setItemPage($page); @@ -182,7 +190,9 @@ public function searchOne(string $keyword, array $filter = []) : array $this->setup('search')->filter($filter); $this->operation->setResources( - $this->getDefaultResources() + $this->getDefaultResources( + $this->getFilterResources($filter) + ) ); $this->operation->setItemCount(1); @@ -209,7 +219,9 @@ public function getVariation(string $id, int $count = 5, int $page = 1, array $f $this->setup('variation')->filter($filter); $this->operation->setResources( - $this->getDefaultResources() + $this->getDefaultResources( + $this->getFilterResources($filter) + ) ); $this->operation->setVariationCount($count)->setVariationPage($page); @@ -292,7 +304,7 @@ public function toEAN(string $keyword, array $filter = []) : string } /** - * Convert keyword to node Id. + * Convert keyword to NodeId. * * @access public * @param string $keyword @@ -300,7 +312,7 @@ public function toEAN(string $keyword, array $filter = []) : string * @param bool $root * @return string */ - public function toNode(string $keyword, array $filter = [], bool $root = false) : string + public function toNodeId(string $keyword, array $filter = [], bool $root = false) : string { $this->setup('search')->filter($filter); @@ -323,16 +335,16 @@ public function toNode(string $keyword, array $filter = [], bool $root = false) } /** - * Convert keyword to node Id (root). + * Convert keyword to NodeId (root). * * @access public * @param string $keyword * @param array $filter * @return string */ - public function toRoot(string $keyword, array $filter = []) : string + public function toRootId(string $keyword, array $filter = []) : string { - return $this->toNode($keyword, $filter, true); + return $this->toNodeId($keyword, $filter, true); } /** @@ -366,12 +378,13 @@ public function getNode(string $id) : array * @param string $keyword * @param array $filter * @return array + * @todo Add ISBN & EAN compatibility */ public function searchNode(string $keyword, array $filter = []) : array { $id = Normalizer::formatId($keyword); if ( Keyword::isASIN($id) || !Keyword::isBarcode($id) ) { - $id = $this->toNode($id, $filter); + $id = $this->toNodeId($id, $filter); } return $this->getNode($id); } @@ -415,6 +428,7 @@ public function getCategory(string $keyword, array $filter = []) : string */ public function searchCategory(string $keyword, array $filter = []) : array { + self::$isCategory = true; $this->setup('search')->filter($filter); $this->operation->setResources([ @@ -439,6 +453,7 @@ public function searchCategory(string $keyword, array $filter = []) : array * @param int $page * @param array $filter * @return array + * @todo Improve */ public function getBestseller(string $id, int $count = 5, int $page = 1, array $filter = []) : array { @@ -452,7 +467,7 @@ public function getBestseller(string $id, int $count = 5, int $page = 1, array $ $node = $this->searchNode($id); if ( !$this->hasError() ) { - $id = $node['name']; + $id = $node['name']; } $this->setup('search')->filter($filter); @@ -479,15 +494,25 @@ public function getBestseller(string $id, int $count = 5, int $page = 1, array $ * @param int $page * @param array $filter * @return array + * @todo Improve */ public function searchBestseller(string $keyword, int $count = 5, int $page = 1, array $filter = []) : array { $keyword = Normalizer::formatKeyword($keyword); - if ( Keyword::isBarcode($keyword) ) { - return []; + $id = Normalizer::formatId($keyword); + + if ( Keyword::isBarcode($id) ) { + $item = $this->searchOne($id, $filter); + if ( $this->hasError() ) { + return []; + } + $keyword = Normalizer::parseKeyword($item['title']); } - $filter['title'] = $keyword; + if ( !isset($filter['title']) ) { + $filter['title'] = $keyword; + } + $this->setup('search')->filter($filter); $this->operation->setResources( @@ -512,6 +537,7 @@ public function searchBestseller(string $keyword, int $count = 5, int $page = 1, * @param int $page * @param array $filter * @return array + * @todo Improve */ public function getNewest(string $keyword, int $count = 5, int $page = 1, array $filter = []) : array { @@ -568,6 +594,19 @@ public function searchRating(string $keyword, array $filter = []) : array } return $this->getRating($keyword); } + + /** + * Set order. + * + * @access public + * @param mixed $order + * @return self + */ + public function order($order) : self + { + $this->order = $order; + return $this; + } /** * Enable geotargeting. @@ -666,6 +705,22 @@ private function getDefaultResources(array $resources = []) : array ], $resources); } + /** + * Get filter ressources. + * + * @access private + * @param array $resources + * @return array + */ + private function getFilterResources(array $filter = []) : array + { + $resources = []; + if ( isset($filter['rank']) && $filter['rank'] === true ) { + $resources[] = 'BrowseNodeInfo.BrowseNodes.SalesRank'; + } + return $resources; + } + /** * Filter request. * @@ -808,19 +863,6 @@ private function filter(array $filter) : self return $this; } - - /** - * Set order. - * - * @access public - * @param mixed $order - * @return self - */ - public function order($order) : self - { - $this->order = $order; - return $this; - } /** * Check search operation. @@ -839,6 +881,7 @@ private function isSearch() : bool * * @access private * @return bool + * @todo Implementation */ private function isNode() : bool { @@ -848,7 +891,7 @@ private function isNode() : bool /** * Prepare request. - * + * * @access private * @return object */ diff --git a/src/includes/Client.php b/src/includes/Client.php index d117caa..696ea7e 100644 --- a/src/includes/Client.php +++ b/src/includes/Client.php @@ -23,10 +23,10 @@ class Client implements ClientInterface { /** - * @access private + * @access protected */ - private const CURL = 'curl'; - private const STREAM = 'stream'; + protected const CURL = 'curl'; + protected const STREAM = 'stream'; /** * @access protected @@ -45,8 +45,8 @@ class Client implements ClientInterface */ protected $endpoint; protected $method = 'POST'; - protected $timeout = 30; - protected $redirect = false; + protected $timeout = 10; + protected $redirect = 1; protected $encoding = false; protected $params; protected $handler; @@ -192,6 +192,9 @@ protected function setHandler() if ( $this->isPost() ) { curl_setopt($this->handler, CURLOPT_POSTFIELDS, $this->getRequestPayload()); curl_setopt($this->handler, CURLOPT_POST, true); + + } else { + curl_setopt($this->handler, CURLOPT_CUSTOMREQUEST, $this->method); } } elseif ( $this->isStream() ) { @@ -305,7 +308,7 @@ protected function getRequestHeader() : array if ( is_array($header)) { return $header; } - $header = explode("\r\n", (string)$header); + $header = explode("\n", (string)$header); return ($header) ? $header : []; } @@ -359,10 +362,10 @@ protected function sendStream() /** * Check curl gateway. * - * @access private + * @access protected * @return bool */ - private function isCurl() : bool + protected function isCurl() : bool { return ($this->gateway == self::CURL); } @@ -370,10 +373,10 @@ private function isCurl() : bool /** * Check stream gateway. * - * @access private + * @access protected * @return bool */ - private function isStream() : bool + protected function isStream() : bool { return ($this->gateway == self::STREAM); } @@ -381,10 +384,10 @@ private function isStream() : bool /** * Check post method. * - * @access private + * @access protected * @return bool */ - private function isPost() : bool + protected function isPost() : bool { return ($this->method == 'POST'); } diff --git a/src/includes/Geotargeting.php b/src/includes/Geotargeting.php index 2eaf13b..b98a069 100644 --- a/src/includes/Geotargeting.php +++ b/src/includes/Geotargeting.php @@ -163,7 +163,7 @@ private function redirect(array $item) : array if ( $client->getCode() == 404 ) { $title = $item['title'] ?? ''; - $keyword = substr($title, 0, 20); + $keyword = substr($title, 0, 30); $keyword = preg_replace('/[^a-zA-Z0-9\s]/', '', $keyword); $keyword = urlencode($keyword); diff --git a/src/includes/Normalizer.php b/src/includes/Normalizer.php index afe8bc7..98ddc49 100644 --- a/src/includes/Normalizer.php +++ b/src/includes/Normalizer.php @@ -62,7 +62,7 @@ public static function noOrder() { self::$order = false; } - + /** * Get normalized response data. * @@ -79,7 +79,6 @@ public static function get(array $data, string $operation) : array } if ( isset($data['category']) ) { return self::normalizeCategory($data['category']); - return $data['category']; } return array_map(function($item) { return self::normalize($item); @@ -276,18 +275,10 @@ public static function formatDelivery($delivery) : array return trim($item); }, $delivery); + $shipping = Provider::SHIPPING; 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'; + if ( isset($shipping[$item]) ) { + $flags[] = $shipping[$item]; } } @@ -307,22 +298,11 @@ public static function formatSort(string $sort) : string strtolower($sort) ); - if ( $sort == 'reviews' ) { - $sort = 'AvgCustomerReviews'; + $values = Provider::SORT; + if ( isset($values[$sort]) ) { + $sort = $values[$sort]; - } 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' ) { + } else { $sort = 'Relevance'; } @@ -377,7 +357,7 @@ public static function formatLanguage($language) : array */ public static function formatCurrency(string $currency) : string { - return $currency; + return self::stripSpace($currency); } /** @@ -389,7 +369,9 @@ public static function formatCurrency(string $currency) : string */ public static function formatMarketplace(string $marketplace) : string { - return $marketplace; + return self::stripSpace( + strtolower($marketplace) + ); } /** @@ -482,6 +464,19 @@ public static function formatOrderBy($order) : array return $order; } + /** + * Parse keyword. + * + * @access public + * @param string $keyword + * @param int $limit + * @return string + */ + public static function parseKeyword(string $keyword, int $limit = 30) : string + { + return substr($keyword, 0, $limit); + } + /** * Decode data. * @@ -742,10 +737,12 @@ private static function parseItem(array $data) : array */ private static function parseSearch(array $data) : array { - if ( isset($data['SearchResult']['SearchRefinements']) ) { - $data = $data['SearchResult']['SearchRefinements']; - $data = $data['SearchIndex']['Bins'] ?? []; - return ['category' => $data]; + if ( Builder::$isCategory ) { + if ( isset($data['SearchResult']['SearchRefinements']) ) { + $data = $data['SearchResult']['SearchRefinements']; + $data = $data['SearchIndex']['Bins'] ?? []; + return ['category' => $data]; + } } return $data['SearchResult']['Items'] ?? []; } diff --git a/src/includes/Parser.php b/src/includes/Parser.php index c961717..e33a80b 100644 --- a/src/includes/Parser.php +++ b/src/includes/Parser.php @@ -65,6 +65,7 @@ private static function resourcesToArray(array $resources) : array foreach ($resource->items as $item) { $wrapper[] = "{$parent}.{$item}"; } + } elseif ( $resource->items === false ) { $wrapper[] = $parent; } diff --git a/src/includes/Provider.php b/src/includes/Provider.php index 217dbf6..f8b4056 100644 --- a/src/includes/Provider.php +++ b/src/includes/Provider.php @@ -23,11 +23,27 @@ final class Provider /** * @access public * @var string HOST, Dynamic API host - * @var array CONDITIONS, API product conditions + * @var array CONDITION, API product condition filter + * @var array SORT, API product sort filter + * @var array SHIPPING, API product delivery filter */ public const HOST = 'https://www.amazon.{locale}'; - public const CONDITIONS = ['Any', 'New', 'Used', 'Refurbished', 'Collectible']; - + public const CONDITION = ['Any', 'New', 'Used', 'Refurbished', 'Collectible']; + public const SORT = [ + 'featured' => 'Featured', + 'newest' => 'NewestArrivals', + 'relevance' => 'Relevance', + 'reviews' => 'AvgCustomerReviews', + 'highest' => 'Price:HighToLow', + 'lowest' => 'Price:LowToHigh' + ]; + public const SHIPPING = [ + 'global' => 'AmazonGlobal', + 'free' => 'FreeShipping', + 'fulfilled' => 'FulfilledByAmazon', + 'prime' => 'AmazonPrime' + ]; + /** * Get request regions. * @@ -242,7 +258,7 @@ public static function generateHeader(string $locale = 'com') : string 'Connection' => 'close' ]; - return implode("\r\n", array_map(function($key, $value) { + return implode("\n", array_map(function($key, $value) { return "{$key}: {$value}"; }, array_keys($header), $header)); } diff --git a/src/lib/Request.php b/src/lib/Request.php index c98bfc3..fadf672 100644 --- a/src/lib/Request.php +++ b/src/lib/Request.php @@ -69,9 +69,9 @@ public function setPayload(OperationInterface $operation) { // Setup params $this->operation = Parser::getName($operation); + $this->payload = Parser::convert($operation); $this->path = $this->path . strtolower($this->operation); $this->target = "{$this->target}.{$this->operation}"; - $this->payload = Parser::convert($operation); $host = self::HOST . ".{$this->locale}"; // Setup headers @@ -83,7 +83,7 @@ public function setPayload(OperationInterface $operation) $header = ''; foreach ( $headers as $key => $value ) { - $header .= "{$key}:{$value}\r\n"; + $header .= "{$key}:{$value}\n"; } $this->endpoint = "https://{$host}{$this->path}"; From dc298d7169148dc84111f770bb4400bef74b1ee2 Mon Sep 17 00:00:00 2001 From: Jakiboy Date: Sat, 30 Mar 2024 15:39:48 +0000 Subject: [PATCH 03/12] Dev --- .github/workflows/phpmd.yml | 57 ---- CODE-OF-CONDUCT.md | 4 +- LICENSE | 2 +- README.md | 9 +- SECURITY.md | 2 +- amazon.svg | 19 -- banner.png | Bin 0 -> 33446 bytes composer.json | 10 +- docs/builder.md | 567 ------------------------------------ docs/provider.md | 68 ----- docs/ressources.md | 102 ------- docs/tlds.md | 27 -- src/includes/Builder.php | 16 +- 13 files changed, 29 insertions(+), 854 deletions(-) delete mode 100644 .github/workflows/phpmd.yml delete mode 100644 amazon.svg create mode 100644 banner.png delete mode 100644 docs/builder.md delete mode 100644 docs/provider.md delete mode 100644 docs/ressources.md delete mode 100644 docs/tlds.md diff --git a/.github/workflows/phpmd.yml b/.github/workflows/phpmd.yml deleted file mode 100644 index 3f9000e..0000000 --- a/.github/workflows/phpmd.yml +++ /dev/null @@ -1,57 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# PHPMD is a spin-off project of PHP Depend and -# aims to be a PHP equivalent of the well known Java tool PMD. -# What PHPMD does is: It takes a given PHP source code base -# and look for several potential problems within that source. -# These problems can be things like: -# Possible bugs -# Suboptimal code -# Overcomplicated expressions -# Unused parameters, methods, properties -# More details at https://phpmd.org/ - -name: PHPMD - -on: - push: - branches: [ "master" ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ "master" ] - schedule: - - cron: '26 11 * * 3' - -permissions: - contents: read - -jobs: - PHPMD: - name: Run PHPMD scanning - runs-on: ubuntu-latest - permissions: - contents: read # for checkout to fetch code - security-events: write # for github/codeql-action/upload-sarif to upload SARIF results - actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Setup PHP - uses: shivammathur/setup-php@aa1fe473f9c687b6fb896056d771232c0bc41161 - with: - coverage: none - tools: phpmd - - - name: Run PHPMD - run: phpmd . sarif codesize --reportfile phpmd-results.sarif - continue-on-error: true - - - name: Upload analysis results to GitHub - uses: github/codeql-action/upload-sarif@v2 - with: - sarif_file: phpmd-results.sarif - wait-for-processing: true diff --git a/CODE-OF-CONDUCT.md b/CODE-OF-CONDUCT.md index de4da8c..ab8f329 100644 --- a/CODE-OF-CONDUCT.md +++ b/CODE-OF-CONDUCT.md @@ -1,6 +1,6 @@ # Contributor Code of Conduct -Our [values](https://apaapi.org/) guide us in our day-to-day interactions and decision-making. Our open source projects are no exception. Trust, respect, collaboration and transparency are core values we believe should live and breathe within our projects. Our community welcomes participants from around the world with different experiences, unique perspectives, and great ideas to share. +Our [values](/) guide us in our day-to-day interactions and decision-making. Our open source projects are no exception. Trust, respect, collaboration and transparency are core values we believe should live and breathe within our projects. Our community welcomes participants from around the world with different experiences, unique perspectives, and great ideas to share. ## Our Pledge @@ -40,7 +40,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting us anonymously through [this form](https://apaapi.org/contact/). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting us anonymously through [this form](/contact/). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. diff --git a/LICENSE b/LICENSE index ecb9a4f..3b2925b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Jihad Sinnaour +Copyright (c) 2024 Jihad Sinnaour Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 6258f46..9370393 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # APAAPI -Amazon Product Advertising API PHP +Amazon Product Advertising API PHP Amazon Product Advertising API V5.0 (**Without Amazon SDK**). This repository contains a lightweight PHP (155 KB) wrapper library, -Easily access the [Amazon Product Advertising API V5.0](https://webservices.amazon.com/paapi5/documentation/index.html) from your PHP app. +Easily access the [Amazon Product Advertising API V5.0](https://webservices.amazon.com/paapi5/documentation/index.html) from your app. -- Become an Amazon Affiliate With PHP -- @@ -44,7 +44,8 @@ This version includes: * **Keyword Converter** (ASIN, ISBN, EAN, Node, Root). * **Caching System** (Basic built-in cache to reduce API calls). -[Full Changelog](#). +[Full Changelog](#). +[Previous Version](/Jakiboy/apaapi/tree/1.1.7). ## ⚡ Getting Started: @@ -53,7 +54,7 @@ This version includes: * "\_KEY\_" : From your Amazon Associates (*your locale*), [More](https://affiliate-program.amazon.com/help/node/topic/GTPNVFFUV2GQ8AZV). * "\_SECRET\_" : From your Amazon Associates (*your locale*), [More](https://affiliate-program.amazon.com/help/node/topic/GTPNVFFUV2GQ8AZV). * "\_TAG\_" : From your Amazon Associates (*your locale*), [More](https://webservices.amazon.com/paapi5/documentation/troubleshooting/sign-up-as-an-associate.html). -* "\_LOCALE\_" : **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). +* "\_LOCALE\_" : **TLD** of the target marketplace to which you are sending requests (*com/fr/co.jp*), [Get TLD](https://webservices.amazon.com/paapi5/documentation/common-request-parameters.html#host-and-region). * "\_KEYWORDS\_" : What you are looking for (*Products*), [More](https://webservices.amazon.com/paapi5/documentation/search-items.html). * "\_ASIN\_" : Accepts (ISBN), Amazon Standard Identification Number (*your locale*), [More](https://webservices.amazon.com/paapi5/documentation/get-items.html#ItemLookup-rp). * "\_NODE\_" : Browse Node ID (*your locale*), [More](https://webservices.amazon.com/paapi5/documentation/use-cases/organization-of-items-on-amazon/browse-nodes/browse-node-properties.html#browse-node-ids). diff --git a/SECURITY.md b/SECURITY.md index 88b5b12..0b4cea7 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,7 +4,7 @@ | Version | Supported | | ------------- | ---------- | -| **>= 5.6.30** | **Yes** | +| **>= 7.4 ** | **Yes** | ## Reporting a Vulnerability diff --git a/amazon.svg b/amazon.svg deleted file mode 100644 index 6529fc2..0000000 --- a/amazon.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - diff --git a/banner.png b/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..e74fd2c7ed8a993afc935dc4b542b8d2731cece3 GIT binary patch literal 33446 zcmaHSWn7e9*Y5yBcL)aEjg&Ct(4EpDUD6B<(nyyQf^-Rz(kV!T(jnb2BPrc*F7Eqz z-g7>@zvCBS*n6$D*IxNwd!p1;<#4f{V}U>*Tm^Y)O%MnL0RkbZVxR$^Jn<0_1O9vF zE~DrE+S%IO%goIRBw^`nZbhx&Xl7%jX=P^V<2q<10_^g$)6#R-Q&ASOaCYQ0dtAfm z?dSsR27yGxy>#URn@Za zwYPv+(u#{wi+BqG1RSm0&8WQ{9h_i7-lDYs&=mrHe*Bn=minJ1?)IXz{|cq2qE0R4 z>}EyH&-seOf}4+rS^&by{puxzhmW0_=OwoQ*GmB|ZeEU;+(Iv33GqUx|MQO);O1s& zEu<+e`yXDwZ=$re?(Qx^TwGpWUYuUMoX&1GT-*={gzF^_7Y`2yuz~~ToK+=DY;OL^F@;`)~VE<7RpfWCRGZ!vy&X-({ zj*sX1XEn@S)9U|<@xQKyY5BNVacNq?oITtufcvnf`|oBzcmIDzj|%~5gw)*ZfJ-rR zkao84aI|uASCAH^1^&WmX=f?)N|vARr8K_;51#}-H@A$8AcPkpAt58fCm|~!!7s@7 zUmE}0UT%KrSNsC9Jiu!1$Gwo3FC}DUWFJYr;uGZN{_kD|Cz!jLlZDlP`Pu<||Faj^ z_5ax`B;{sh=I-pK)JV4I(xxb9&hL0!%ADZ*?C%7 z%DOo_Qvah~A-n&Ly&$(aH;?&C4oeFwYYuKpzLy+kFJJO;KzOYzcz6XYAp+*swEx*} z`TwZxU-JY$0gmAEUrB@n%mplYEd>QREch&6aadXLnRA#~Ld-a<&CIQ>Ukbjo5;PZ} z(7YG#aLqS?X%X@w=3;oj* znVEaF6=6scJ)B$y3a0<`GB?u4!(-U>tSVsOxsLMg`q#39>r$%xnxPhZJv!R_kQ&QG z`siSCX;V}Ak2as08xK3w@X6kPhM&WV_JwJ-w*CWiA`KV~5wP3WnBp;zS{sT=?c33e81kFXstrUa@;Mlib&pJ9rK-17?7ZT9M zVXJ5NIiW|-9$5!vtY_jy!K5>$`d``KCd(M(C z^cs2C+Fm#m5ldbTqYbhajPKb)<9t08eqRu7PStyt?rx%HF;l^prekppejQ7zCmoLrQ!e zpGo(#?3$dfrhZcNow6_(3}ixU~N+GFs0h`&QlDjIvO%A8}2vw|@-syCEbHMW^;niXUbNfxtqb z4~clLTUePj4ZM-rj{<40?m{u6$y%-)1+QbA>skUhG&5HS!}S2ovby2( z9p<^G-8D)NL|aP>nu$Wa+TQhz5EK+zNHNhL#gVeEeX+H*Wu)lHk&QxtY?#CbvG>Dp z%o9?zb`)RCNo+TM{vo#cXTm#U=}{yCIUOBR1_p+_M=g)jad0>qw>=0ZVO(q5`ccP1 zF?*U{xCq(%J*9t^jE);){kQ|$#uMKR7YXDNALi~xi+tx^CrW^v^JGSYN9G+3V^sap z-s|c{rAa1#0>swVKsYE!-Ww(MAB9Lu3PA{i14QO2_3#Xnk_LWK2xir{`{qcVdqSY+ zo?pw$rBBI0pO^VF<P*eKKI~Xk+S}CZ9{^8;^j1&c8d)5}<$t zp^`##5$7lqoo6G2f3&F5%lC2i_CK6r51$aa$0JZf1;3*N8UKa2PXRD!mYQ21R_9ydW!QSk>$77u`B`a0gf{zK=98Jo1p1` z{13|h=g|j1PjFmig)HKYNb+=*G}?#1jGyCkaOT)@It!c4NxmgS0Z| zS!HU1mt3GASanQMKJqT^KYr@L!knxH!>-X^cs9GhkYU$GA=*C&1O4R$2m$iu3~5RW zE(OKTc%qHq_^jfRCxigUKkkH&(EuI?2rEsZhEBNV{yzk)+8mu932gc?T`2>W2!u=* zJ&|W!ERhrry3_^rSwFTDq-$fn_V+XEvqsyILG%V8PuIyc-2ahg8Z-GXPL!uQg?`0; zF;7UlakhS6>$ztn2(%eifM9M#dklD;B&vNO+HxGG|IiBtj^|A*ce2eF3P|P_ip%#8 zCHBn3`X!3X*IV_R-OHRB;u3(4shN!2aR24>_f-mmEsB7t!NTNbyZGbD&DwnK$;V-! zDUbsBZEn7M?kvDe%>Lk1oeElY@4MFu=cd01^O)MI|eiy*?)i zPkZ6Voa8;VJTeZm3`K*kSz#;EAp>b5*nXVNRKOD(pfW5LanG0lh2Q7{?5~Sb*uA zdp7_5;=Qv=3oP;LZ2gDK0j-{DA^I3zC4qkFj|u-3ApG*%-_=JkIKvC^xqaykN0#z`T8F(P-)Y?d9;N^ zOvA+^CzMCkL(p1fg3S0(mpJZ9~mvFBrf$wjH`%QFo ztg+61{Zc-@GTlT4Ayeo{S+Vpa*~7*MI8ynlLaWan)gGy?3 z>%W!=V&`lYcUb{xsQzqG?d|Q=a5QyWASETuK+akbt>XpBg;uZ;Y+x86xo$o;vueA) z=i@)5IQNXQCX^lx&MYh{nr0iZ$MPUOotvBMmtG9MfRY%;3RYbmMn^|0e~Ujy1ElFe zksCI1^ulK~SQrlY=VUNs3g!upcVe$|`=#_s;G3JPZRPzkOp)>4J&-UVkO%wtIGFlv zQ{nTsp)H%kYOjB%;-E)}d^-1Z#&B;CwfIqqS6W({8tG5EbGz6*<2BWPjz2lH7%PR;F z)8Yxl!>-cbzk~Ha$V*Bt4Ss|pv;iH0toV6&k0d+_`4~&7Nx$SXvvTJ1+s{mHa~KZq zra3Oy+8j8|*9#|{A1uk;;BU$)8}ty9e;{{^n;_4sRdcqnL>ApvAD53B3tU~XKg2T9>t0{Fd+POm@yh;AKz*~7H zJcJyI+Mb%!d%S`lQtw}fqTxeN$9 zxEBn`$T2uEFeX+?7ikJfiV~=Sfx#j+Jy26i(0d~_UPn;`FCx{lD(2y8f^TWAz3;(bL+Zl5L z?v7PYui!aqX(j=Xxaa&s=e-=)1^M(v#8rG$r88dZ|E8H*LYFq( z3>ewSXRo9c^Q7I;!+s39VgwbjQ6`PfUsxV|Bkt{tnp-{Dt|*$?#-8XJNeC=^)8SGG zX;mIey1)ix6jimmB*^Hbk}-Pfo_;V7O$x`hY1qC;nL=AX)bmzO{^q=)kA&fl;eOG5 zHWFHLWk_{<|0|m};Z5cx>pOnG%ChfOVtSVailtJuvSa3;kO&%58! zr$!Xsyh)?jKkG^dX#0kUv^tYe8`>&)&`rHCF6=~jA4AlV3#5L?yiSS&9D?L~7@F`W z-Nq#M?DMPE>(=9|)&=K|tiHGxavTPAF58SiEmQuwu+;b~m!fMp0urXxCG|T``FCV- z8Ro*m+%{hP6tnKuC0dx!W5B1?VhAHBE!Y0^0KZE>95H3*?F)PQ?zF-zcZxqd<%{1R zB}}z=*!u+v0>w{-L2Vq~n%9{wAalhxg$9IA82?8f;ON?GZZ<|2M*OqqBa+73M@Z0I zsoYRs65CRm_pD=hgrAPKlv^%>BkXp$7DSiuG9&+t2p<-rQ~Cd$eZHF?)NV}6zgZ>} zc(83YrWbZ>PQqM^p-HRxJ{q+1LqV&#Jngq@HyW{X!(WI8hDUA)RL)h-1>BCjE`lsx zWvpZq-;&6AvwxON2DcU%_wYUHB`cMRz&qV3tBPLHu<;Y_X%@L$Gzlb4QuSEvZD5Vc zBZ$++x7=WwRM>paog^@Tg@yG?g-1k}`c4yD1%#&}r5&}_X{Pv7(e9Qmu$VX^Q#Izh z{(H1iB&L{W!k+9|?h^`iZd;1M!pMTHn88j|ZEJ=3ag$}$2yGTx()e{i9ab}voUnIc z;X?4IKTc2qYja{%@?n$|CO5zD>Ye)XpzeELq+lECtJmtuflDQpdC-3GhI`IG&BcmlK|xo#e{Sx$Yio+2sN5h^S8MDY zzL+9BGqAL#{#TaydQa3;M_{1AcmL+_GP>D?tqkr(ju9W4XorQJT!Yg#A#xeGKPA+u z&Hqh$mh>CJ17gyhOfN74A<|l!8@@1^! z{*23BjNpabhvBCO4UHRn%?4cxDYbB?w!qcnqP4u^pf@1`^jO%~!&sCAJ3zP>6roM& zoOk+r^-G`6binTPWd006OzN`W*f{=CO)UKq{Rfbc@?Gkoa;HWdg1(i$;0#oud<0b1 zyLO_CHU*B1;Q=H>a`Nz@DEFzWcV|?u5ff~cQo|N8=`YQYMUMyQjsih|Wn#K}EdaHF zuG(S?66XeLkn&$y1~*r7!X1B!5)=$~Q+)OSWW7YOlSVSzz4nc9D)7-FY;>G=RD^g% z9H{>|Z-KTO;v#iQj%Qos& z>8>Ih$&<=e0wsjH5cM)nLB5ecRO%oY}9$c&|dNE2O? zW1d9)TR9j3NkkE_7K;EvYMO+5*F2@)XIHydoa1PZsZ4^FXOIA1lUT;T0>R z>jGMK{C?1~tNiT^S9POQ?m}IsBoLi0+Zt95BcSL62-5DEa{|`up2nJ2^SF`}(A>~V z%w(VWS8Zo$CW!o>m6GBviB~7^$(|_U-SquOy`;VbCvA_EBBw7*cc}pNAGn3RItHU_ zLMSggCnuo*nfmA((=r9Gg;{9Ds}$3p)e*$q6y+tkd0#RC@8Lm+<|NFN>r3s=*T>wF z3Cba};tWWApozXcAeLQ&g+)FuMUork<>fWLS`zfdWQxeNw6y%>gTIIcUp3W;awWJ! z*He4UTf+X4xqcz)+NX=%w?}z^i7U~^0_Ut0(!tRyQ4Ckw-zVqq%GW9lkN2~&J(aR^ z-AIbw>73{M{tl8Kk1Gh6V0=-{)MV<~91K3m_VG(Dm3&XRey#0DCWKJuE6~TG8ZPi( zaH#u8M5BHbYC7A4fLoAbEXm;Lt8`+_WY$&ca+;a|m9JxLB3Xp3O@*JF+OF_UQ81ei zzu&|gf1>qkHM(4~8{P;o!H zTaz#Buh=Kf_K!b4Kaes>Ow0+mj`|L`X~6^CW1Lq1xG2C*>sv_zSmO59yxlw^fdFxg z<=%J>iANyAO@s0B@OH=p6X{2KwMccRMK74`e^-3D4^@*9Py7^&f<6&~ij7zCJVg45 zj4g?sH9D1sVmup}tR6|Jl#Whn8LE&R4@7}15xH z64ofw;b3MGj^N>$816MP)8O9P+Rw`p_MFH#dtrN$YLXnF^N~rd@@pKPU#lhZ(z{6% zp2o89h0$!@5^;_r0z?hiRpk5|2<$eh;qPkKaC+)4Jtd06e&AGC6DfajUU_>(W+G7w z|D_9;;X_;FRed30A)Am-*sWHaz0@iv`jU+F$wXK0%2))IO2LKLt#tEAtO()O{z#D) zF0yexo}I`iv`WKFpn{gnWkZ(Zs;#Xxeh`JceJ{qswD_<{A_IrP2Riad{eOknmr9kC zDbSL7VYoN`m^lw>9MuiDxO*#fH3ds<`mMuZIM(#g99r3StNC`Z$+at|UqO!X`?Zkh z-LDf8^#P%)qlm+aW}EEy7rLag%h`LCwP}KWm8AaXX=Gn2vn%7cTS_o|1T2fpy5Cn` zakBqPdWr{>baZ2`XiF-8(@#L8xm@kH8OSlCIM)j%Il260Y2yz!cO0paCx1iM^U&|1 zF9O~u6pTV%r`w#5%TaCIrC@fnh7s~Kvd!Gx2K$h9EXuw8xtfw@A~`C4L(gwZy)1cqTvjznMd__~ z4WB8|keY9{!hK`GVvV}5e(rg(H$?}uK5rtkdHpw$V;muC1-j(l+)<1eK+SlIs7#}q z_kS8hFr82c-PNB!@XhicM$umvJ%Kx@WQhC;{1f+^ah`T0UD&BLO3dFY`m~`01^sxq z#vA%Re#a|C(1t)%7yXTkuS8~TJH@)un?<+tOyYQn%vP5{XOY*-s~dDYPvwk+$nr6b zKxtpF@*U84w@jRsd`wvSLQXnN0u*&ke8Ts|{Z5+;5bcfkGfeGi&@0jRP=QeRLLm8K!xevo#>+zIoI6ZtQ4lB$ge0YrGC9lx<{IJxZWY8n^TnK8 z)!K|D{y?EaAEs*P9VLCf8{O0yVh#TBO5R6Ci$5$-Jj^iBJ#bW?lcw~`40)8@7 zLa>Ufml+Z63m}uBK(^{HWe=~vBbc=DXW5c|xcWjhhWReR>-Nn-$3-BbF!-+MT>y_$ zrMI)}OVXkYzhYGB(djOwaoDMnk87wPd;k)bA@Fa`q8SS=a5k4oX{Y$)53z4>zc}p} z6;bMl}LA@R&K#YuH04X%0g)$!wCDLkatbG|;53~)E6hIAt^w{!= z*ANJhiomuxl6SJ3e$7<~7;{aFp8c8rSa~_zt0~sKzD~p{9w`%E%NybcCd!AuP0GmF zi7OQ*>AHepxaR?YP0)^HmQo#!J~ddVIuvBM;M&0a>tf}BdfGgb!;RR)Uv;SAx_ef* z=v{oP7d($w7>LQn*3RhB?9%>XqZsa|N`ny+Dz6Q4VeK#n<&ccOebi|Sq3S?hqg{*L z7kX2@!){2Ui}qK1Ss*n-z?sv^kYc5k>}+RTWyo)#(OF!m_Vwiwv8W9;H)-k4>JwMW zgU`@6TBLCHwrV7B{*xV-V?gmrE0wu4V`eCogBN9i$=h#$$n+<>)j6%DowLofF8M-R zGuq#@_(>o)Ni^Z zDGn$x+zGhIA-|L2xQS?@7>i$2-esNcPq%)_B)!l|uyjpzW&}nFf@Aw4$<0~&{R749 zfq|P!{DR0>uBb%e6i?Zwz9nG<&eu&zY#`yAxhMI#Svc?HZm=IW~ z1Uo)6;w9*}%;%YkKX+bV1S#VnC$X=OUIv-h1_}2-u&MPTr-OSTfB|Nhjlp?;7JQwl zsy;%Yo&t=R7?GlFH4Sag^ctC~a~O9JI^0VF#wNmcrhz*vhhPzm+*$TjJU?Ml{GC-m zm2v{EVtN5uG1;(=u21Nh1UnAF_%!cD{e7!iN z6m;n%Ic{z}u}z>^^n!1VS1z^M=~f<<9o;M%x7yvrXKCr_xleU$$lt~NzKg%VMTAb3 zXwdNqsVnE6kmTNPVqt%iG#;@Shv_A5MZjHCc5+UN@pVx_oeHzhddL7>w=|BiMTS)O z&Y{NGe{b>HHMp?A{8Q(AC^qjH>GQrfFe75;61lNLc{&H#J1@HTtC08p9wa~WxIpvV z(-Z4Q3X&zs&YtApz(5dS#a`Ux{1NrWs-gu~C3cg=+o(aum(x`eagW5=>Hp2KY$6@thiD@I*biSP84vrUB z{zIoMoAaQr7-#G2=EgVvuPBRM!NI#BXYrotnBGbr_9k7AheUg~!Rq zdO;bQMuC*9si8smc+{QRmhs@wr$I;{&)EC73k#4BVPK=Tm^7K9=sZ8}G{X1x7t>4~ z(OE~Q33dn&Z;6?M>nT8{-9uBl9xOQd3cyOPEKxaaBz|?P$;e$*saa4XhR64@oLzK< z^};{Rede$G=V(DexR2P*`40_6?U5@+EFipLy3eh-;`K8GeKvI+q0NGaJwkRjceW%L z?h2UCx_F{g;@*ocKnEB4J2RL8iY; z5)hDT{3%iN3EW_@oKxhi8>09lklJ7#w3B3qnE@mCK*UIuBeItf1VR@5O7r?RO$bSd zOrowsOq*+%?H8dt&2v}08P8P(eaq9k4OTGT|~>>Uz+ttMF~8t|g9luvF_&@DON=j3k$^9m9rQr|BNT z(`dLzf!r%eVN_~2C;cQCanIvS&QKKdf;T26yur#bJ?hG#=vR6dM3LZrU(0Meb0+Y?{gZzq)Tg48C1zE!er-BN@z`VH64No5)bmz0uY;a6xjlr%# z2*_%bxU*Q1e<0bF;z`&&Ib{(t_wBp=Cb7*IN2GR;C63mJW*B_J3@Jy6)z8$aui9)oGZim9_UET>A5=SaCcb3*MsJViaA~jn8XU|sbZ$@KY(db{ z_P@t7_;>%n`R)JQPz3;x=Wo9mR)~FHKcD&R zAcDM02bw+P69) zFO#Q#COLMwEc)Y%p0Mf2-(>&2Am_79yo$$p@#>eqb9tCRx%X@N9`eanFK6GgX9Kti3T>IuVEAmC72CVk9lsNU8h1#AUUM|E{ClK5!!uTUXSvfw zA^+abK48!7pEBmvtIfBu6S%cX204d=iMt~@3nC2O%;0a+rOc0 z@2E+5=$dt~PxaWa{BQOfCmINy_1qo~Cch*w7wh z?9&TF9deqvHlNyRTvH}vOxEsx#Wu}+Vr1iJN?k1bR`imWXsIbHmHFW@-}8Kc?i^?` zo`#Bp(OfihPh6^4e0G^ltcsv3^cT2j9#|<(OdPewT~zgf4*^BlxXz5qSwhoos>1tx zE%(>E;h1M80ar3R4VP!XO*kHY>5XKc)XuhgRZTheG`O>Yg>rm!?NE~8A=P?^;|1tT zF5S4+TB75l;A2r6E|g%W-L4H&p!{o!AA&Lf;PM2?1`N)b+pCmDSHP8s-H|clnOt@J z32K8iL#$6dru}|&Wc0H@LS@SiYs*pJ3l0w5e$0I=dNq<3$Awo);t8Vy{chPFyE7FN z7ldUC@-j1(#!C1ie}jCKH<$emi+~F^OMJvD(7&DXNtL4}1<$HDtb7Z#4mdN}$WSpE z$#jf71wBWVFaI?~f{4sE*L(H!E_*bJORqX8+y7W$XDlz2A&1TH z)F|vScAB+J@u?Sje@l?c<6w&?#ioqg zPW;uHg3wxz*;M@nv<0?z$M>#JITA*FC0|wCo`s4<)UDr!VA1D#P$6~b4E;)@IJv>Qu}h|vXN9I-BU`Tif>5B5KuX683Slh zS5A@SX3EN4n~41os1GJ$I>~sI5b_s>Lp#35-F7_0#^<^&%@?*#&)~DLi5tx6Uu1@p z4a_rB{N3Nb{B+ct#^>FW&bPh3GND@w&({dbr+XLu;gBjHK9jFa{*v=*flsT66rT+_ zbNUlqSd_J??$%z-lNXq60|_>HRg&%*H{AzItw-k4t3X#@+OVjkBZncM%mIEuK!P8yYqJD_dFWJ$~`b0RaK(~t( zJc51eoew8Z(XqPK*wPo`PY}7k+|bg}3hPtHsgeUq5t%NV5i>{Kx72}mIS<8^?^N>> zidE%8fng%L#Hmp-)TC-;m{!9mi`}4Zpv~Osccrm4$s1InxYK@O(Z+T7JSHu|qx-Y| z>tbaIsYe_f<@Ldj>@TyRfHf=K^k;J#&d-{#8XD~1&h@BY7@2x8FPEr_Gj~Exuf6{i zr7;s&pc1Ai!Wd}9Aik7`v?k%t5v)#(g1vH7@4`Ga?XoBMMwuc*p=79XBh3ep#B2bN zdR=wTM2AG~@&}{J`ZRMi2pMTfA`e68wVR&( zI!P(PAf#XoNJTF7xiL0UG)V|Zfntc@dy1fwz)51wO4YxIR*{8SUb*q^m(#y8srfb_i~#KsLrmDXMNzQZ>_{G4}qGa=MKEdi-hU zRza)pM{=G<>^(f0Xa?gl9DaZH(STbEG#Up4{gWC$4#$LGr_Me z5T68`DW6NY>1Ec+1Ni1lp03v1Lh#hO|J0|Swgq#kqBrQG2Klmu<(zkGsE92B2U{Tp$?6?A&Q+4D=F7sPXxZlvq0_)W?amYvG6g z@F7Abh;3~8`TUhX_2V(5qe9FZK{0=9SJ|guymH7Ddv`r` zb)glD*H0@(9G6|j3cy05zK?+!1szXXYJgeZ%sB$u=xHq?|2WkDp!uw%@Ml$vgq`aM ziq0tIQi*-q$W##weP^+h*kf-tc)r0&X85x}QTea(PvWp2`3wXIr@pEgPQIKQu=&xG zQl>HBy0cY2znW$n(k9hc%tLXJ zDYTI6Tc_I2uf_kw39RVY5LVc^&oPjVQ8X?qOQCg#psqiGi3bfB{M7jWq9@yrk4MMXu8+NPBx)DGkgKY2dF2q3$`OZG|S4zSB~>z zO|Bayt{Z**xB7N=rE>>mIu-d`9k(BbhI$x{mH`lt5sWF}CWzr~zsd_ol743y&fD;C zp7A-1E3({lHR{fGLMiYI&>ye=KpMq#w{&VORl;EZwr%f46J9e<7)5~C4*DJ?uNgd& zUr<@H6@-Lh`|A^Top~>o?`kl5*!jiKP|MG@8dQU1!|;Lyc09ud2L)lTgF5ZpGN6a+ zv1H2e=Mg>XA-2{OFH3?*JphG`8Zg!VdUBZQ+V||Vb>(hkag*@Q_)@g)>nISn&0uU9 z)H;&dGoI8OUh(ZYZeIJT*OYT3_ou`O6Kz#Cz*{$LIron7=BcMM`s$H-B_S9aD&KM~ z?OFP01&WX~Jmp+@o$d|n7WfU6xMOM;oLW_y5H5LDeW7Nb-~JL z6F`5rQJH=aSxKcePZ8a*B>tG+beKcNZ5 z3hxs7`R$akA8sIbqgjm~)wyr>I-Bi6?l|<6Avg6{V~!L z%T<-LmZ23EmNZ2=wQf+B9)43h%s$;0fZ0JG0=!K-Ymq6&VZxYE!lBtq`|2G^v>dOM z?_<^1p6iOBihT(HdIsV16PZ*!?3j^i^~UFvY?mT|D&F1QLlfrt><9kmLUeT9x9ue= zII2X5R642r(TRw}#b?MsC^U{C$4bt7M!!9(CIX;GREg*JvN$g!VI3K&KrHC(MxN<* z!coC}47IORkBi<{V@A8aL!FqXQaDbV)c*DB^Fye8woIeb68X(sJ}rV>Y;tmPc%_l_ z(`H?UJEl>ebkTq*V{0}x3P9VFPz0U={f#lQ{oX&HFpM0H2c;b zhsC;d06%Z3yYN~M3gFu>@M7krBgV&${81&#!rJha*4K&3U{J?(l&7L1{cL{GWpIyv zJ3@<`UdX%i$!OQ$u)YQJnBi;Z&PKFt(2nd&n;gTZMR1~}kj zz+{2j#Sc8Y0Nk+NGX#(uM|P+J3jgq0EBZLI^sS!!k$cFKjeEW(ffT&HXp@bSN)ewe zr+5wqd&YfGSqJ-Cw{~~nTh|c_;F3AH_tuu^b1iZ14Xjcw2Mzop*&r(86sw5Ojv;OI z#B@*cLEEGEw?uC8cj8$#1D-vcf_0LM{F~7CskwoPlR|1`QC|^P>tq zJk;A)9KujhQ323QemH}_R>b$sI}|9Ge>Cf)i7`J;IshG14m<^G8s2NXwSpZu=oO=VRruU>K9JW+B@G6IcO-2|Do10C|a(RcQHSqz)ZVzpg#QBt z`4!8{X0JRCV}XM7&S!Pz5_6;cBl7_cU}LhU5>>Oc8LU{?2}E#$jWY{f+|%xup+frC zLDAI(wc0uTBxPh5`6D^Jp3dfxx11n7qjS zkMRoIo0V>HM+$5&HA7m9TVgDknU-? zm@aQK$?>@rJi91^=}x+C)F_(TphP0J{mr5uuV{r>140}G{z;lMTGs^dMf5_-_lN#Y zs@a!lPEf38i?*=`bZ4gko4#`UqF&du$48|I|1M?#