From 4f1b38c96aa2d414ab174c7f233bfe1b211e04c5 Mon Sep 17 00:00:00 2001 From: repat Date: Thu, 21 Feb 2019 15:36:22 +0100 Subject: [PATCH 1/4] 0.1.4 rate limiting + readme update --- README.md | 15 +++++++++------ composer.json | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 8c4cdb6..2af58ee 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,19 @@ # plentymarkets-rest-client +[![Latest Version on Packagist](https://img.shields.io/packagist/v/repat/plentymarkets-rest-client.svg?style=flat-square)](https://packagist.org/packages/repat/plentymarkets-rest-client) +[![Total Downloads](https://img.shields.io/packagist/dt/repat/plentymarkets-rest-client.svg?style=flat-square)](https://packagist.org/packages/repat/plentymarkets-rest-client) This is a PHP package for Plentymarkets new REST API. The API is relatively new (March 2017), so not everything might work correctly and this package might also be out of date at some point. -I'm not in anyway affiliated with Plentymarkets, nor do I get paid for this by anybody. As it says in the license, this software is 'as-is'. If you want/need more features, open a GitHub ticket or write a pull request. I'll do my best :) +I'm not in anyway affiliated with Plentymarkets, nor do I get paid for this by anybody. As it says in the license, this software is 'as-is'. If you want/need more features, open a GitHub ticket or write a pull request. I'll do my best :) That said, I don't work for the company I developed this for anymore, so if you have any interest in becoming a contributor on this repo, let me know. You can find the Plentymarkets documentation [here](https://developers.plentymarkets.com/): ### Overview * Functions for the 4 HTTP verbs: GET, POST, PUT, DELETE * Automatic login and refresh if login is not valid anymore -* Simple one-time configuration with PHP array (will be saved serialzed in a file) +* Simple one-time configuration with PHP array (will be saved serialized in a file) * Functions return an associative array +* Handle rate limiting (thanks [hepisec](http://github.com/hepisec)) ## Installation Available via composer on [Packagist](https://packagist.org/packages/repat/plentymarkets-rest-client): @@ -66,7 +69,7 @@ $client->singleCall("GET", $guzzleParameterArray); * If there was an error with the call (=> guzzle throws an exception) all functions will return false * If the specified config file doesn't exist or doesn't include username/password/url, an exception will be thrown -## TODO +## TODO * Refresh without new login but refresh-token ## Dependencies @@ -74,10 +77,11 @@ $client->singleCall("GET", $guzzleParameterArray); * [https://packagist.org/packages/guzzlehttp/guzzle](guzzlehttp/guzzle) for HTTP calls. * [https://packagist.org/packages/danielstjules/stringy](danielstjules/stringy) for string comparisons -## License +## License * see [LICENSE](https://github.com/repat/plentymarkets-rest-client/blob/master/LICENSE) file ## Changelog +* 0.1.4 Automatic rate limiting (thx [hepisec](https://github.com/repat/plentymarkets-rest-client/pull/8)) * 0.1.3 Fix PHP 7.2 dependency * 0.1.2 Fix Carbon dependency * 0.1.1 Update Guzzle for PHP 7.2 @@ -88,5 +92,4 @@ $client->singleCall("GET", $guzzleParameterArray); * e-mail: repat@repat.de * Twitter: [@repat123](https://twitter.com/repat123 "repat123 on twitter") -[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=repat&url=https://github.com/repat/plentymarkets-rest-client&title=plentymarkets-rest-client&language=&tags=github&category=software) - +[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=repat&url=https://github.com/repat/plentymarkets-rest-client&title=plentymarkets-rest-client&language=&tags=github&category=software) diff --git a/composer.json b/composer.json index 1634fbc..e1224cc 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "keywords": ["plentymarkets", "pm", "rest", "api", "client", "sdk"], "homepage": "https://github.com/repat/plentymarkets-rest-client", "license": "MIT", - "version" : "0.1.3", + "version" : "0.1.4", "authors": [ { "name": "repat", From cdcd90c1fbeec017ee357ed7337d90f58c95e50b Mon Sep 17 00:00:00 2001 From: repat Date: Thu, 21 Feb 2019 15:44:02 +0100 Subject: [PATCH 2/4] formatting --- .../PlentymarketsRestClient.php | 367 +++++++++--------- 1 file changed, 188 insertions(+), 179 deletions(-) diff --git a/src/PlentymarketsRestClient/PlentymarketsRestClient.php b/src/PlentymarketsRestClient/PlentymarketsRestClient.php index 1f69145..8e4dacb 100644 --- a/src/PlentymarketsRestClient/PlentymarketsRestClient.php +++ b/src/PlentymarketsRestClient/PlentymarketsRestClient.php @@ -7,191 +7,200 @@ use Psr\Http\Message\ResponseInterface; use Stringy\Stringy as s; -class PlentymarketsRestClient { - - const PATH_LOGIN = "rest/login"; - const METHOD_GET = "GET"; - const METHOD_POST = "POST"; - const METHOD_PUT = "PUT"; - const METHOD_DELETE = "DELETE"; - const THROTTLING_PREFIX_LONG_PERIOD = "X-Plenty-Global-Long-Period"; - const THROTTLING_PREFIX_SHORT_PERIOD = "X-Plenty-Global-Short-Period"; - const THROTTLING_PREFIX_ROUTE = "X-Plenty-Route"; - - private $client; - private $config; - private $configFile; - private $rateLimitingEnabled = true; - private $throttledOnLastRequest = false; - - public function __construct($configFile, $config) { - $this->client = new Client(); - $this->config = $config; - - if (!file_exists($configFile)) { - $this->configFile = $configFile; - $this->saveConfigFile(); - } - - $this->setConfigFile($configFile); - - if (!$this->isAccessTokenValid()) { - $this->login(); - } - } - - public function getRateLimitingEnabled() { - return $this->rateLimitingEnabled; +class PlentymarketsRestClient +{ + const PATH_LOGIN = 'rest/login'; + const METHOD_GET = 'GET'; + const METHOD_POST = 'POST'; + const METHOD_PUT = 'PUT'; + const METHOD_DELETE = 'DELETE'; + const THROTTLING_PREFIX_LONG_PERIOD = 'X-Plenty-Global-Long-Period'; + const THROTTLING_PREFIX_SHORT_PERIOD = 'X-Plenty-Global-Short-Period'; + const THROTTLING_PREFIX_ROUTE = 'X-Plenty-Route'; + + private $client; + private $config; + private $configFile; + private $rateLimitingEnabled = true; + private $throttledOnLastRequest = false; + + public function __construct($configFile, $config) + { + $this->client = new Client(); + $this->config = $config; + + if (!file_exists($configFile)) { + $this->configFile = $configFile; + $this->saveConfigFile(); } - public function setRateLimitingEnabled($rateLimitingEnabled) { - $this->rateLimitingEnabled = $rateLimitingEnabled; - return $this; - } - - public function getThrottledOnLastRequest() { - return $this->throttledOnLastRequest; + $this->setConfigFile($configFile); + + if (!$this->isAccessTokenValid()) { + $this->login(); + } + } + + public function getRateLimitingEnabled() + { + return $this->rateLimitingEnabled; + } + + public function setRateLimitingEnabled($rateLimitingEnabled) + { + $this->rateLimitingEnabled = $rateLimitingEnabled; + return $this; + } + + public function getThrottledOnLastRequest() + { + return $this->throttledOnLastRequest; + } + + public function singleCall($method, $path, $params = []) + { + $path = ltrim($path, '/'); + + if (!($path == self::PATH_LOGIN)) { + $params = array_merge($params, [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $this->config['access_token'], + ], + ]); + } + + try { + /* @var $response ResponseInterface */ + $response = $this->client->request($method, $this->config['url'] . $path, $params); + } catch (\Exception $e) { + return false; + } + + $this->throttledOnLastRequest = false; + + if ($this->rateLimitingEnabled) { + $this->handleRateLimiting($response); } - - public function singleCall($method, $path, $params = []) { - $path = ltrim($path, "/"); - - if (!($path == self::PATH_LOGIN)) { - $params = array_merge($params, [ - "headers" => [ - "Authorization" => "Bearer " . $this->config["access_token"], - ], - ]); - } - - try { - /* @var $response ResponseInterface */ - $response = $this->client->request($method, $this->config["url"] . $path, $params); - } catch (\Exception $e) { - return false; - } - - $this->throttledOnLastRequest = false; - - if ($this->rateLimitingEnabled) { - $this->handleRateLimiting($response); - } - - return json_decode($response->getBody(), true); - } - - public function get($path, $array = []) { - return $this->singleCall(self::METHOD_GET, $path, ["query" => $array]); - } - - public function post($path, $array = []) { - return $this->singleCall(self::METHOD_POST, $path, ["json" => $array]); - } - - public function put($path, $array = []) { - return $this->singleCall(self::METHOD_PUT, $path, ["json" => $array]); - } - - public function delete($path, $array = []) { - return $this->singleCall(self::METHOD_DELETE, $path, ["json" => $array]); - } - - private function isAccessTokenValid() { - - if (!in_array("valid_until", $this->config)) { - return false; - } - if (Carbon::parse($this->config["valid_until"])->gt(Carbon::now())) { - return true; - } - return false; - } - - private function login() { - - $response = $this->singleCall(self::METHOD_POST, self::PATH_LOGIN, [ - "form_params" => [ - "username" => $this->config["username"], - "password" => $this->config["password"], - ], - ]); - - $this->config["access_token"] = $response["accessToken"]; - $this->config["valid_until"] = Carbon::now()->addSeconds($response["expiresIn"])->toDateTimeString(); - - $this->saveConfigFile(); - } - - private function saveConfigFile() { - file_put_contents($this->configFile, serialize($this->config)); - } - - private function correctURL($url) { - $sUrl = new s($url); - - if (!($sUrl->contains("https"))) { - $url = str_replace("http://", "https://.", $url); - } - - if (!($sUrl->contains("www."))) { - $url = str_replace("https://", "https://www.", $url); - } - - $url = rtrim($url, "/") . "/"; - - return $url; - } - - private function setConfigFile($configFile) { - - $this->configFile = $configFile; - - if (!file_exists($configFile)) { - throw new \Exception("config file does not exists."); - } - - $this->config = unserialize(file_get_contents($this->configFile)); - - if (!array_key_exists("username", $this->config) - || !array_key_exists("password", $this->config) - || !array_key_exists("url", $this->config)) { - throw new \Exception("username and/or password and/or url not in config(file)"); - } - - $this->config["url"] = $this->correctURL($this->config["url"]); - - $this->saveConfigFile(); - } - - private function handleRateLimiting(ResponseInterface $response) { - $prefixes = array( - self::THROTTLING_PREFIX_LONG_PERIOD, - self::THROTTLING_PREFIX_SHORT_PERIOD, + return json_decode($response->getBody(), true); + } + + public function get($path, $array = []) + { + return $this->singleCall(self::METHOD_GET, $path, ['query' => $array]); + } + + public function post($path, $array = []) + { + return $this->singleCall(self::METHOD_POST, $path, ['json' => $array]); + } + + public function put($path, $array = []) + { + return $this->singleCall(self::METHOD_PUT, $path, ['json' => $array]); + } + + public function delete($path, $array = []) + { + return $this->singleCall(self::METHOD_DELETE, $path, ['json' => $array]); + } + + private function isAccessTokenValid() + { + if (!in_array('valid_until', $this->config)) { + return false; + } + return Carbon::parse($this->config['valid_until'])->gt(Carbon::now()); + } + + private function login() + { + $response = $this->singleCall(self::METHOD_POST, self::PATH_LOGIN, [ + 'form_params' => [ + 'username' => $this->config['username'], + 'password' => $this->config['password'], + ], + ]); + + $this->config['access_token'] = $response['accessToken']; + $this->config['valid_until'] = Carbon::now()->addSeconds($response['expiresIn'])->toDateTimeString(); + + $this->saveConfigFile(); + } + + private function saveConfigFile() + { + file_put_contents($this->configFile, serialize($this->config)); + } + + private function correctURL($url) + { + $sUrl = new s($url); + + if (!($sUrl->contains('https'))) { + $url = str_replace('http://', 'https://.', $url); + } + + if (!($sUrl->contains('www.'))) { + $url = str_replace('https://', 'https://www.', $url); + } + + $url = rtrim($url, '/') . '/'; + + return $url; + } + + private function setConfigFile($configFile) + { + $this->configFile = $configFile; + + if (!file_exists($configFile)) { + throw new \Exception('config file does not exists.'); + } + + $this->config = unserialize(file_get_contents($this->configFile)); + + if (!array_key_exists('username', $this->config) + || !array_key_exists('password', $this->config) + || !array_key_exists('url', $this->config)) { + throw new \Exception('username and/or password and/or url not in config(file)'); + } + + $this->config['url'] = $this->correctURL($this->config['url']); + + $this->saveConfigFile(); + } + + private function handleRateLimiting(ResponseInterface $response) + { + $prefixes = [ + self::THROTTLING_PREFIX_LONG_PERIOD, + self::THROTTLING_PREFIX_SHORT_PERIOD, self::THROTTLING_PREFIX_ROUTE - ); + ]; + + $throttled = 0; + + foreach ($prefixes as $prefix) { + $throttled += $this->handleThrottling($response, $prefix, $throttled); + } + } - $throttled = 0; + private function handleThrottling(ResponseInterface $response, $prefix, $throttled = 0) + { + $callsLeft = $response->getHeader($prefix . '-Calls-Left'); + $decay = $response->getHeader($prefix . '-Decay'); - foreach ($prefixes as $prefix) { - $throttled += $this->handleThrottling($response, $prefix, $throttled); - } + if (count($callsLeft) < 1 || count($decay) < 1) { + return 0; } - - private function handleThrottling(ResponseInterface $response, $prefix, $throttled = 0) { - $callsLeft = $response->getHeader($prefix . '-Calls-Left'); - $decay = $response->getHeader($prefix . '-Decay'); - - if (count($callsLeft) < 1 || count($decay) < 1) { - return 0; - } - - if ($callsLeft[0] < 1 && $decay[0] > $throttled) { - sleep($decay[0] - $throttled); - $this->throttledOnLastRequest = true; - return $decay[0]; - } - - return 0; + + if ($callsLeft[0] < 1 && $decay[0] > $throttled) { + sleep($decay[0] - $throttled); + $this->throttledOnLastRequest = true; + return $decay[0]; } -} \ No newline at end of file + + return 0; + } +} From c101c05c8c93d1e7cf489ddd8f7207c9c3d75bf9 Mon Sep 17 00:00:00 2001 From: daniel-mannheimer Date: Thu, 29 Aug 2019 18:35:56 +0200 Subject: [PATCH 3/4] remove www. check breaks usage of subdomains like https://testdomain.plentymarkets.com --- src/PlentymarketsRestClient/PlentymarketsRestClient.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/PlentymarketsRestClient/PlentymarketsRestClient.php b/src/PlentymarketsRestClient/PlentymarketsRestClient.php index 8e4dacb..addceb9 100644 --- a/src/PlentymarketsRestClient/PlentymarketsRestClient.php +++ b/src/PlentymarketsRestClient/PlentymarketsRestClient.php @@ -141,10 +141,6 @@ private function correctURL($url) $url = str_replace('http://', 'https://.', $url); } - if (!($sUrl->contains('www.'))) { - $url = str_replace('https://', 'https://www.', $url); - } - $url = rtrim($url, '/') . '/'; return $url; From 5fcd3e32aba27cfaa8d5467a8c1e3f36e265cc85 Mon Sep 17 00:00:00 2001 From: repat Date: Thu, 29 Aug 2019 10:57:45 -0700 Subject: [PATCH 4/4] Update version --- README.md | 1 + composer.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2af58ee..bb3f724 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ $client->singleCall("GET", $guzzleParameterArray); * see [LICENSE](https://github.com/repat/plentymarkets-rest-client/blob/master/LICENSE) file ## Changelog +* 0.1.5 Remove check for `www.` as it breaks subdomains (thx [daniel-mannheimer](https://github.com/repat/plentymarkets-rest-client/pull/9)) * 0.1.4 Automatic rate limiting (thx [hepisec](https://github.com/repat/plentymarkets-rest-client/pull/8)) * 0.1.3 Fix PHP 7.2 dependency * 0.1.2 Fix Carbon dependency diff --git a/composer.json b/composer.json index e1224cc..0c60f82 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "keywords": ["plentymarkets", "pm", "rest", "api", "client", "sdk"], "homepage": "https://github.com/repat/plentymarkets-rest-client", "license": "MIT", - "version" : "0.1.4", + "version" : "0.1.5", "authors": [ { "name": "repat",