diff --git a/.gitignore b/.gitignore index 0e4193cf..928a5ca5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ test.php log.txt /test-cases/log.txt composer.lock +/.idea/ \ No newline at end of file diff --git a/INSTALL.md b/INSTALL.md index 668b6e68..4b1f6afe 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -2,7 +2,9 @@ To install, simply add the library to your project. Composer is the default installation tool for this library. If you do not use Composer for your project, you can still auto-load classes by including the file **includes/classes.php** in the page or function. -Before you use any commands, you need to create an **amazon-config.php** file with your account credentials. Start by copying the template provided (*amazon-config.default.php*) and renaming the file. +Before you use any commands, you need to either create an **amazon-config.php** file or define an array with your account credentials. +For the file approach start by copying the template provided (*amazon-config.default.php*) and renaming the file, for the array based approach +see the supplied example (code_config_examples.php) If you are operating outside of the United States, be sure to change the Amazon Service URL to the one matching your region. diff --git a/amazon-config.default.php b/amazon-config.default.php index 08f93dab..892b33c5 100644 --- a/amazon-config.default.php +++ b/amazon-config.default.php @@ -36,5 +36,3 @@ //Turn off normal logging $muteLog = false; - -?> diff --git a/examples/code_config_examples.php b/examples/code_config_examples.php new file mode 100644 index 00000000..100c0189 --- /dev/null +++ b/examples/code_config_examples.php @@ -0,0 +1,68 @@ + + array('myStoreName' => + array( + 'merchantId' => 'AAAAAAAAAAAA', + 'marketplaceId' => 'AAAAAAAAAAAAAA', + 'keyId' => 'AAAAAAAAAAAAAAAAAAAA', + 'secretKey' => 'BABABABABABABABABABABABABABABABABABABABA', + 'serviceUrl' => '', + 'MWSAuthToken' => '', + ) + ), + 'AMAZON_SERVICE_URL' => 'https://mws-eu.amazonservices.com', // eu store + 'logpath' => __DIR__ . './logs/amazon_mws.log', + 'logfunction' => '', + 'muteLog' => false +); + +/** + * This function will retrieve a list of all items with quantity that was adjusted within the past 24 hours. + * The entire list of items is returned, with each item contained in an array. + * Note that this does not relay whether or not the feed had any errors. + * To get this information, the feed's results must be retrieved. + */ +function getAmazonFeedStatusA(){ + global $amazonConfig; // only for example purposes, please don't use globals! + + + try { + $amz=new AmazonFeedList($amazonConfig); + $amz->setStore('myStoreName'); // Not strictly needed as there is only 1 store in the array and its automatically activated + $amz->setTimeLimits('- 24 hours'); //limit time frame for feeds to any updated since the given time + $amz->setFeedStatuses(array("_SUBMITTED_", "_IN_PROGRESS_", "_DONE_")); //exclude cancelled feeds + $amz->fetchFeedSubmissions(); //this is what actually sends the request + return $amz->getFeedList(); + } catch (Exception $ex) { + echo 'There was a problem with the Amazon library. Error: '.$ex->getMessage(); + } +} + +/** + * As above but with an alternative method of creating the config object. + */ +function getAmazonFeedStatusB(){ + global $amazonConfig; // only for example purposes, please don't use globals! + + $configObject = new \AmazonMWSConfig($amazonConfig); + + try { + // using the getConfigFor method creates another instance of AmazonMWSConfig containing just that store's data + // If the method in getAmazonFeedStatusA() has more than 1 store setup in the array, they all are available to + // the Amazon MWS library and you can switch between them using setStore(). However, should you want to + // have clear seperation between the stores forwhatever reason, you can use getConfigFor to ensure that only + // one store is available to the library. They're all still available in the configObject for later use, + // calling getConfigFor does not affect the store list within the $configObject + + $amz=new AmazonFeedList($configObject->getConfigFor('myStoreName')); + $amz->setTimeLimits('- 24 hours'); //limit time frame for feeds to any updated since the given time + $amz->setFeedStatuses(array("_SUBMITTED_", "_IN_PROGRESS_", "_DONE_")); //exclude cancelled feeds + $amz->fetchFeedSubmissions(); //this is what actually sends the request + return $amz->getFeedList(); + } catch (Exception $ex) { + echo 'There was a problem with the Amazon library. Error: '.$ex->getMessage(); + } +}?> diff --git a/includes/classes/AmazonCore.php b/includes/classes/AmazonCore.php index 3cc301b3..705d1035 100644 --- a/includes/classes/AmazonCore.php +++ b/includes/classes/AmazonCore.php @@ -99,11 +99,11 @@ abstract class AmazonCore{ protected $throttleStop = false; protected $storeName; protected $options; + /** @var AmazonMWSConfig */ protected $config; protected $mockMode = false; protected $mockFiles; protected $mockIndex = 0; - protected $logpath; protected $env; protected $rawResponses = array(); @@ -126,11 +126,20 @@ abstract class AmazonCore{ * @param string $config [optional]

An alternate config file to set. Used for testing.

*/ protected function __construct($s = null, $mock = false, $m = null, $config = null){ - if (is_null($config)){ - $config = __DIR__.'/../../amazon-config.php'; + /** If the store argument is an array, then assume its the entire config being passed in */ + if ($s instanceof \AmazonMWSConfig) { + // New config object passed in, use it. + $this->setConfig($s); + } + else { + if (is_null($config)){ + $config = __DIR__.'/../../amazon-config.php'; + } + + $config = new \AmazonMWSConfig($config); + $this->setConfig($config->getConfigFor($s)); + $this->setStore($s); } - $this->setConfig($config); - $this->setStore($s); $this->setMock($mock,$m); $this->env=__DIR__.'/../../environment.php'; @@ -353,19 +362,26 @@ protected function checkResponse($r){ * This method can be used to change the config file after the object has * been initiated. The file will not be set if it cannot be found or read. * This is useful for testing, in cases where you want to use a different file. - * @param string $path

The path to the config file.

+ * @param string|\AmazonMWSConfig $config

The path to the config file.

* @throws Exception If the file cannot be found or read. */ - public function setConfig($path){ - if (file_exists($path) && is_readable($path)){ - include($path); - $this->config = $path; - $this->setLogPath($logpath); - if (isset($AMAZON_SERVICE_URL)) { - $this->urlbase = rtrim($AMAZON_SERVICE_URL, '/') . '/'; + public function setConfig($config){ + + if ($config instanceof \AmazonMWSConfig) { + $this->config = $config; + } + else { + if (file_exists($config) && is_readable($config)){ + $this->config = new \AmazonMWSConfig($config); + } else { + throw new Exception("Config file does not exist or cannot be read! ($config)"); } - } else { - throw new Exception("Config file does not exist or cannot be read! ($path)"); + } + $this->urlbase = $config->getEndPoint(); + + // If the current storeName doesn't exist in the stores array any more, reset it to the first in the list of stores + if (empty($this->storeName) OR ($this->config->storeExists($this->storeName) === false)) { + $this->setStore(); // Passing no argument defaults it to the first store. } } @@ -375,15 +391,11 @@ public function setConfig($path){ * Use this method to change the log file used. This method is called * each time the config file is changed. * @param string $path

The path to the log file.

- * @throws Exception If the file cannot be found or read. */ public function setLogPath($path){ - if (file_exists($path) && is_readable($path)){ - $this->logpath = $path; - } else { - throw new Exception("Log file does not exist or cannot be read! ($path)"); - } - + // We move the validation from here into the Config object to save on duplication. + // Its also automatically enables logging when set. + $this->config->setLogFile($path); } /** @@ -394,49 +406,45 @@ public function setLogPath($path){ * for making requests with Amazon. If the store cannot be found in the * config file, or if any of the key values are missing, * the incident will be logged. - * @param string $s [optional]

The store name to look for. + * @param string $storeName [optional]

The store name to look for. * This parameter is not required if there is only one store defined in the config file.

* @throws Exception If the file can't be found. */ - public function setStore($s=null){ - if (file_exists($this->config)){ - include($this->config); - } else { - throw new Exception("Config file does not exist!"); - } - - if (empty($store) || !is_array($store)) { + public function setStore($storeName = null){ + if ($this->config->getStoreCount() === 0) { throw new Exception("No stores defined!"); } - - if (!isset($s) && count($store)===1) { - $s=key($store); + + // If no store name is passed, default it to the first store. + if (empty($storeName)) { + $storeName = key($this->config->getStores()); } - if(array_key_exists($s, $store)){ - $this->storeName = $s; - if(array_key_exists('merchantId', $store[$s])){ - $this->options['SellerId'] = $store[$s]['merchantId']; + if(array_key_exists($storeName, $this->config->getStores())){ + $this->storeName = $storeName; + $store = $this->config->getStore($storeName); + if(array_key_exists('merchantId', $store)){ + $this->options['SellerId'] = $store['merchantId']; } else { $this->log("Merchant ID is missing!",'Warning'); } - if(array_key_exists('keyId', $store[$s])){ - $this->options['AWSAccessKeyId'] = $store[$s]['keyId']; + if(array_key_exists('keyId', $store)){ + $this->options['AWSAccessKeyId'] = $store['keyId']; } else { $this->log("Access Key ID is missing!",'Warning'); } - if(!array_key_exists('secretKey', $store[$s])){ + if(!array_key_exists('secretKey', $store)){ $this->log("Secret Key is missing!",'Warning'); } - if (!empty($store[$s]['serviceUrl'])) { - $this->urlbase = $store[$s]['serviceUrl']; + if (!empty($store['serviceUrl'])) { + $this->urlbase = $store['serviceUrl']; } - if (!empty($store[$s]['MWSAuthToken'])) { - $this->options['MWSAuthToken'] = $store[$s]['MWSAuthToken']; + if (!empty($store['MWSAuthToken'])) { + $this->options['MWSAuthToken'] = $store['MWSAuthToken']; } } else { - $this->log("Store $s does not exist!",'Warning'); + $this->log("Store $storeName does not exist!",'Warning'); } } @@ -467,13 +475,8 @@ public function setThrottleStop($b=true) { protected function log($msg, $level = 'Info'){ if ($msg != false) { $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); - - if (file_exists($this->config)){ - include($this->config); - } else { - throw new Exception("Config file does not exist!"); - } - if (isset($logfunction) && $logfunction != '' && function_exists($logfunction)){ + $logCallback = $this->config->getLogCallback(); + if (empty($logCallback) === false){ switch ($level){ case('Info'): $loglevel = LOG_INFO; break; case('Throttle'): $loglevel = LOG_INFO; break; @@ -481,11 +484,12 @@ protected function log($msg, $level = 'Info'){ case('Urgent'): $loglevel = LOG_ERR; break; default: $loglevel = LOG_INFO; } - call_user_func($logfunction,$msg,$loglevel); + call_user_func($logCallback,$msg,$loglevel); } - - if (isset($muteLog) && $muteLog == true){ - return; + + $fnPath = $this->config->getLogFile(); + if (($this->config->isLoggingDisabled()) OR (empty($fnPath))){ + return null; } if(isset($userName) && $userName != ''){ @@ -511,17 +515,13 @@ protected function log($msg, $level = 'Info'){ }else{ $ip = 'cli'; } - if (!file_exists($this->logpath)) { - //attempt to create the file if it does not exist - file_put_contents($this->logpath, "This is the Amazon log, for Amazon classes to use.\n"); - } - if (file_exists($this->logpath) && is_writable($this->logpath)){ - $str = "[$level][" . date("Y/m/d H:i:s") . " $name@$ip $fileName:$line $function] " . $msg; - $fd = fopen($this->logpath, "a+"); + $str = "[$level][" . date("Y/m/d H:i:s") . " $name@$ip $fileName:$line $function] " . $msg; + $fd = fopen($this->config->getLogFile(), "a+"); + if ($fd !== false) { fwrite($fd,$str . "\r\n"); fclose($fd); } else { - throw new Exception('Error! Cannot write to log! ('.$this->logpath.')'); + throw new Exception('Error! Cannot write to log! ('.$this->config->getLogFile().')'); } } else { return false; @@ -577,14 +577,9 @@ protected function genTime($time=false){ * @throws Exception if config file or secret key is missing */ protected function genQuery(){ - if (file_exists($this->config)){ - include($this->config); - } else { - throw new Exception("Config file does not exist!"); - } - - if (array_key_exists($this->storeName, $store) && array_key_exists('secretKey', $store[$this->storeName])){ - $secretKey = $store[$this->storeName]['secretKey']; + $store = $this->config->getStore($this->storeName); + if (array_key_exists('secretKey', $store)){ + $secretKey = $store['secretKey']; } else { throw new Exception("Secret Key is missing!"); } @@ -629,7 +624,7 @@ protected function sendRequest($url,$param){ * * @param int $i [optional]

If set, retrieves the specific response instead of the last one. * If the index for the response is not used, FALSE will be returned.

- * @return array associative array of HTTP response or FALSE if not set yet + * @return array|bool associative array of HTTP response or FALSE if not set yet */ public function getLastResponse($i=NULL) { if (!isset($i)) { @@ -644,7 +639,7 @@ public function getLastResponse($i=NULL) { /** * Gives all response code received from Amazon. - * @return array list of associative arrays of HTTP response or FALSE if not set yet + * @return array|bool list of associative arrays of HTTP response or FALSE if not set yet * @see getLastResponse */ public function getRawResponses() { @@ -666,6 +661,7 @@ public function getLastResponseCode() { if (!empty($last['code'])) { return $last['code']; } + return null; } /** @@ -682,6 +678,7 @@ public function getLastErrorResponse() { } } } + return null; } /** @@ -699,6 +696,7 @@ public function getLastErrorCode() { return $xml->Error->Code; } } + return null; } /** @@ -716,6 +714,7 @@ public function getLastErrorMessage() { return $xml->Error->Message; } } + return null; } /** @@ -737,28 +736,35 @@ protected function checkToken($xml){ if ($xml && $xml->NextToken && (string)$xml->HasNext != 'false' && (string)$xml->MoreResultsAvailable != 'false'){ $this->tokenFlag = true; $this->options['NextToken'] = (string)$xml->NextToken; + return true; } else { unset($this->options['NextToken']); $this->tokenFlag = false; + return false; } } - - //Functions from Athena: - /** - * Get url or send POST data - * @param string $url - * @param array $param['Header'] - * $param['Post'] - * @return array $return['ok'] 1 - success, (0,-1) - fail - * $return['body'] - response - * $return['error'] - error, if "ok" is not 1 - * $return['head'] - http header - */ - function fetchURL ($url, $param) { + + public function getConfig() { + return $this->config; + } + + /** Functions from Athena: */ + + /** + * Get url or send POST data + * @param string $url + * @param array $param['Header'] + * $param['Post'] + * @return array $return['ok'] 1 - success, (0,-1) - fail + * $return['body'] - response + * $return['error'] - error, if "ok" is not 1 + * $return['head'] - http header + */ + function fetchURL ($url, $param) { $return = array(); - + $ch = curl_init(); - + curl_setopt($ch,CURLOPT_RETURNTRANSFER, true); curl_setopt($ch,CURLOPT_TIMEOUT, 0); curl_setopt($ch,CURLOPT_FORBID_REUSE, 1); @@ -773,60 +779,60 @@ function fetchURL ($url, $param) { curl_setopt($ch,CURLOPT_POSTFIELDS, $param['Post']); } } - + $data = curl_exec($ch); if ( curl_errno($ch) ) { - $return['ok'] = -1; - $return['error'] = curl_error($ch); - return $return; + $return['ok'] = -1; + $return['error'] = curl_error($ch); + return $return; } - + if (is_numeric(strpos($data, 'HTTP/1.1 100 Continue'))) { $data=str_replace('HTTP/1.1 100 Continue', '', $data); } $data = preg_split("/\r\n\r\n/",$data, 2, PREG_SPLIT_NO_EMPTY); if (!empty($data)) { - $return['head'] = ( isset($data[0]) ? $data[0] : null ); - $return['body'] = ( isset($data[1]) ? $data[1] : null ); + $return['head'] = ( isset($data[0]) ? $data[0] : null ); + $return['body'] = ( isset($data[1]) ? $data[1] : null ); } else { - $return['head'] = null; - $return['body'] = null; + $return['head'] = null; + $return['body'] = null; } - + $matches = array(); $data = preg_match("/HTTP\/[0-9.]+ ([0-9]+) (.+)\r\n/",$return['head'], $matches); if (!empty($matches)) { - $return['code'] = $matches[1]; - $return['answer'] = $matches[2]; + $return['code'] = $matches[1]; + $return['answer'] = $matches[2]; } - + $data = preg_match("/meta http-equiv=.refresh. +content=.[0-9]*;url=([^'\"]*)/i",$return['body'], $matches); if (!empty($matches)) { - $return['location'] = $matches[1]; - $return['code'] = '301'; + $return['location'] = $matches[1]; + $return['code'] = '301'; } - + if ( $return['code'] == '200' || $return['code'] == '302' ) { - $return['ok'] = 1; + $return['ok'] = 1; } else { - $return['error'] = (($return['answer'] and $return['answer'] != 'OK') ? $return['answer'] : 'Something wrong!'); - $return['ok'] = 0; + $return['error'] = (($return['answer'] and $return['answer'] != 'OK') ? $return['answer'] : 'Something wrong!'); + $return['ok'] = 0; } - + foreach (preg_split('/\n/', $return['head'], -1, PREG_SPLIT_NO_EMPTY) as $value) { - $data = preg_split('/:/', $value, 2, PREG_SPLIT_NO_EMPTY); - if (is_array($data) and isset($data['1'])) { - $return['headarray'][$data['0']] = trim($data['1']); - } + $data = preg_split('/:/', $value, 2, PREG_SPLIT_NO_EMPTY); + if (is_array($data) and isset($data['1'])) { + $return['headarray'][$data['0']] = trim($data['1']); + } } - + curl_close($ch); - + return $return; - } + } // End Functions from Athena - // Functions from Amazon: + /** Functions from Amazon: **/ /** * Reformats the provided string using rawurlencode while also replacing ~, copied from Amazon * @@ -846,11 +852,14 @@ protected function _urlencode($value) { * @return string */ protected function _getParametersAsString(array $parameters) { - $queryParameters = array(); - foreach ($parameters as $key => $value) { - $queryParameters[] = $key . '=' . $this->_urlencode($value); - } - return implode('&', $queryParameters); +// $queryParameters = array(); +// foreach ($parameters as $key => $value) { +// $queryParameters[] = $key . '=' . $this->_urlencode($value); +// } +// return implode('&', $queryParameters); + + // We may as well use the input method! + return http_build_query($parameters); } /** @@ -865,7 +874,6 @@ protected function _signParameters(array $parameters, $key) { $stringToSign = null; if (2 === $this->options['SignatureVersion']) { $stringToSign = $this->_calculateStringToSignV2($parameters); -// var_dump($stringToSign); } else { throw new Exception("Invalid Signature Version specified"); } @@ -875,7 +883,7 @@ protected function _signParameters(array $parameters, $key) { /** * generates the string to sign, copied from Amazon * @param array $parameters - * @return type + * @return string */ protected function _calculateStringToSignV2(array $parameters) { $data = 'POST'; @@ -919,6 +927,4 @@ protected function _sign($data, $key, $algorithm) // -- End Functions from Amazon -- -} - -?> +} \ No newline at end of file diff --git a/includes/classes/AmazonMWSConfig.php b/includes/classes/AmazonMWSConfig.php new file mode 100644 index 00000000..37599929 --- /dev/null +++ b/includes/classes/AmazonMWSConfig.php @@ -0,0 +1,353 @@ +logFile = ''; + + // Yes I know this is a change from the previous release which defaulted to always logging, + // but to better handle log files not existing, this is required as validation is performed when logging + // is enabled + $this->loggingDisabled = true; + + $this->logCallback = null; + $this->stores = array(); + $this->endpoint = self::DEFAULT_ENDPOINT; + + if ($configFN instanceof self) { + // Config can also be another instance of this class! If so, simply grab the config from it. + $this->_getConfigFrom($configFN,true); + + } elseif (is_array($configFN)) { + // Get the config out of the array instead. + if (array_key_exists('stores',$configFN) === false OR empty($configFN['stores'])) { + throw new Exception("Config array does not contain any store data!"); + } + + if ((array_key_exists('AMAZON_SERVICE_URL',$configFN)) AND (empty($configFN['AMAZON_SERVICE_URL']) === false)) { + $this->setEndPoint($configFN['AMAZON_SERVICE_URL']); + } + + // Add the stores to the internal array. + foreach ($configFN['stores'] as $storeName => $rec) { + $this->addStore($storeName,$rec); + } + + if ((array_key_exists('logpath',$configFN)) AND (empty($configFN['logpath']) === false)) { + $this->setLogFile($configFN['logpath']); + } + + if ((array_key_exists('logfunction',$configFN)) AND (empty($configFN['logfunction']) === false)) { + $this->setLogCallback($configFN['logfunction']); + } + + if (array_key_exists('muteLog',$configFN)) { + $this->setLoggingDisabled($configFN['muteLog']); + } + } else { + // Presume its a string (filename) + if (file_exists($configFN) AND is_readable($configFN)){ + include $configFN; + } else { + throw new Exception("Config file does not exist or cannot be read! ($configFN)"); + } + + if (empty($store)) { + throw new Exception("Config file does not contain any store data! ($configFN)"); + } + + if (empty($AMAZON_SERVICE_URL) === false) { + $this->setEndPoint($AMAZON_SERVICE_URL); + } + + // Add the stores to the internal array. + foreach ($store as $storeName => $rec) { + $this->addStore($storeName,$rec); + } + + if (isset($logpath)) { + $this->setLogFile($logpath); + } + + if (isset($logfunction)) { + $this->setLogCallback($logfunction); + } + + if (isset($muteLog)) { + $this->setLoggingDisabled($muteLog); + } + } + } else { + // No Config is also valid if you want to set it up manually. + } + } + + /** + * Copies the config out of the passed source object, optionally copying the store data over. + * + * @param $source + * @param bool $copyStoreData + */ + protected function _getConfigFrom($source,$copyStoreData = false) { + // Copy the config over and yes I purposefully didn't call the setters, all the property values should + // be valid as they're validated when set. + $this->endpoint = $source->endpoint; + $this->logCallback = $source->logCallback; + $this->logFile = $source->logFile; + $this->loggingDisabled = $source->loggingDisabled; + + if ($copyStoreData) { + $this->stores = array_merge($source->stores,array()); // Ensure we get a duplicate of the array, not a reference. + } + } + + /// Getter / Setters + + /** + * @return mixed + */ + public function getStores() + { + return $this->stores; + } + + /** + * Does this store exists within the configuration? + * + * @param $storeName + * @return bool + */ + public function storeExists($storeName) { + return array_key_exists($storeName,$this->stores); + } + + /** + * Adds a new store to the Array + * + * @param $storename + * @param array $storeData + */ + public function addStore($storename,array $storeData) + { + $this->stores[$storename] = $storeData; + } + + /** + * Returns a config class to pass into the constructor of a library call. This enables you to have a master configuration + * object and then only pass the config for a particular store into the Amazon calls. + * + * @param $aStoreName + * @return AmazonMWSConfig + * @throws Exception + */ + public function getConfigFor($aStoreName) { + /** @var AmazonMWSConfig $result */ + + if ($this->storeExists($aStoreName) === false) { + throw new Exception("The requested store does not exist!"); + } + + $result = new self; + $result->_getConfigFrom($this); + $result->addStore($aStoreName,$this->stores[$aStoreName]); + + return $result; + } + + /** + * @return string + */ + public function getLogFile() + { + return $this->logFile; + } + + /** + * @param string $logFile + */ + public function setLogFile($logFile) + { + $this->logFile = $logFile; + // If we set a log file, assume we want to enable logging! + $this->setLoggingDisabled(false); + } + + /** + * @return boolean + */ + public function isLoggingDisabled() + { + return $this->loggingDisabled; + } + + /** + * Ok, the point at which the logging is enabled, we do some checks. A Blank filename is permitted if a callback + * is configured, but must be configured prior to logging being enabled. + * + * @param boolean $loggingDisabled + */ + public function setLoggingDisabled($loggingDisabled) + { + // Cast it to a bool + $bValue = (bool) $loggingDisabled; + + if ($bValue === false) { + // Ok, we don't want to disable logging, inwhich case we need to ensure we can write to the file. + if ((empty($this->logFile)) AND (empty($this->logCallback))) { + $this->loggingDisabled = true; + return; + } + + if (empty($this->logCallback) === false) { + // The callback is validated upon setting, so if its not empty, it must be valid. + $this->loggingDisabled = false; + return; + } + + if (is_dir($this->logFile)) { + // doh! + $this->loggingDisabled = true; + return; + } + + /** If we get here, then we have configured a log file... **/ + if ((empty($this->logFile) === false) AND (file_exists($this->logFile) === false)) { + // Ok, file doesn't exist, try and create it. + $hFile = @fopen($this->logFile,'a+b'); + if ($hFile !== false) { + // File created ok, close it. + fclose($hFile); + } + } + + if ((file_exists($this->logFile) === false) OR (is_writable($this->logFile) === false)){ + // Ok, either we have no logfile or we do, but it can't be written to. + // We won't die, but we'll simply disable logging. + $this->loggingDisabled = true; + } else { + $this->loggingDisabled = false; + } + } + else { + // We don't care if logging is disabled, no checks to do! + $this->loggingDisabled = true; + } + } + + /** + * Sets the endpoint and ensures we have a trailing path separator. + * @param string $endPoint + */ + public function setEndPoint($endPoint) { + $this->endpoint = rtrim($endPoint, '/') . '/'; + } + + public function getEndPoint() { + return $this->endpoint; + } + + /** + * Returns the requested store configuration, or a blank array if the key is invalid. + * + * @param $aStoreName + * @return array|mixed + */ + public function getStore($aStoreName) { + if (array_key_exists($aStoreName,$this->stores)) { + return $this->stores[$aStoreName]; + } + + // Invalid key, simply return an empty array + return []; + } + + /** + * Returns the length of the stores array + * + * @return int + */ + public function getStoreCount() { + return count($this->stores); + } + + /** + * @return callable + */ + public function getLogCallback() + { + return $this->logCallback; + } + + /** + * @param callable $logCallback + */ + public function setLogCallback($logCallback) + { + $this->logCallback = null; + if (empty($logCallback) === false AND is_callable($logCallback)) { + $this->logCallback = $logCallback; + } + } + + /** + * Gets the marketplace for the store from the configuration data + * + * @param $storeName + * @return mixed|string + */ + public function getStoreMarketPlace($storeName) { + $store = $this->getStore($storeName); + if(array_key_exists('marketplaceId', $store)){ + return $store['marketplaceId']; + } + return ''; + } + +} \ No newline at end of file diff --git a/includes/classes/AmazonMerchantShipmentCreator.php b/includes/classes/AmazonMerchantShipmentCreator.php index 8a1f7f51..f27ee95f 100644 --- a/includes/classes/AmazonMerchantShipmentCreator.php +++ b/includes/classes/AmazonMerchantShipmentCreator.php @@ -462,7 +462,7 @@ public function setServiceOffer($id) { public function fetchServices() { $services = new AmazonMerchantServiceList($this->storeName, $this->mockMode, $this->mockFiles, $this->config); $services->mockIndex = $this->mockIndex; - $services->setLogPath($this->logpath); + $services->setLogPath($this->config->getLogFile()); $services->setDetailsByCreator($this); $services->fetchServices(); return $services; @@ -547,7 +547,7 @@ protected function parseXML($xml){ } $this->shipment = new AmazonMerchantShipment($this->storeName, NULL, $xml, $this->mockMode, $this->mockFiles, $this->config); - $this->shipment->setLogPath($this->logpath); + $this->shipment->setLogPath($this->config->getLogFile()); $this->shipment->mockIndex = $this->mockIndex; } diff --git a/includes/classes/AmazonOrder.php b/includes/classes/AmazonOrder.php index 266c841c..a84a41e9 100644 --- a/includes/classes/AmazonOrder.php +++ b/includes/classes/AmazonOrder.php @@ -132,7 +132,7 @@ public function fetchItems($token = false){ $token = false; } $items = new AmazonOrderItemList($this->storeName,$this->data['AmazonOrderId'],$this->mockMode,$this->mockFiles,$this->config); - $items->setLogPath($this->logpath); + $items->setLogPath($this->config->getLogFile()); $items->mockIndex = $this->mockIndex; $items->setUseToken($token); $items->fetchItems(); diff --git a/includes/classes/AmazonOrderList.php b/includes/classes/AmazonOrderList.php index 20e32185..7f22c61f 100644 --- a/includes/classes/AmazonOrderList.php +++ b/includes/classes/AmazonOrderList.php @@ -219,15 +219,11 @@ public function resetMarketplaceFilter(){ } //reset to store's default marketplace - if (file_exists($this->config)){ - include($this->config); - } else { - throw new Exception('Config file does not exist!'); - } - if(isset($store[$this->storeName]) && array_key_exists('marketplaceId', $store[$this->storeName])){ - $this->options['MarketplaceId.Id.1'] = $store[$this->storeName]['marketplaceId']; - } else { + $storeMP = $this->config->getStoreMarketPlace($this->storeName); + if(empty($storeMP)){ $this->log("Marketplace ID is missing",'Urgent'); + } else { + $this->options['MarketplaceId.Id.1'] = $storeMP; } } @@ -495,7 +491,7 @@ protected function parseXML($xml){ break; } $this->orderList[$this->index] = new AmazonOrder($this->storeName,null,$data,$this->mockMode,$this->mockFiles,$this->config); - $this->orderList[$this->index]->setLogPath($this->logpath); + $this->orderList[$this->index]->setLogPath($this->config->getLogFile()); $this->orderList[$this->index]->mockIndex = $this->mockIndex; $this->index++; } diff --git a/includes/classes/AmazonProductsCore.php b/includes/classes/AmazonProductsCore.php index 08ad18dc..1cd97077 100644 --- a/includes/classes/AmazonProductsCore.php +++ b/includes/classes/AmazonProductsCore.php @@ -43,24 +43,21 @@ abstract class AmazonProductsCore extends AmazonCore{ public function __construct($s = null, $mock = false, $m = null, $config = null){ parent::__construct($s, $mock, $m, $config); include($this->env); - if (file_exists($this->config)){ - include($this->config); - } else { - throw new Exception('Config file does not exist!'); - } if(isset($AMAZON_VERSION_PRODUCTS)){ $this->urlbranch = 'Products/'.$AMAZON_VERSION_PRODUCTS; $this->options['Version'] = $AMAZON_VERSION_PRODUCTS; } - - //set the store's marketplace as the default - if(isset($store[$this->storeName]) && array_key_exists('marketplaceId', $store[$this->storeName])){ - $this->setMarketplace($store[$this->storeName]['marketplaceId']); - } else { + + + //reset to store's default marketplace + $storeMP = $this->config->getStoreMarketPlace($this->storeName); + if(empty($storeMP)){ $this->log("Marketplace ID is missing",'Urgent'); + } else { + $this->setMarketplace($storeMP); } - + if(isset($THROTTLE_LIMIT_PRODUCT)) { $this->throttleLimit = $THROTTLE_LIMIT_PRODUCT; } diff --git a/includes/classes/AmazonRecommendationCore.php b/includes/classes/AmazonRecommendationCore.php index 7d8786e4..c225a6dd 100644 --- a/includes/classes/AmazonRecommendationCore.php +++ b/includes/classes/AmazonRecommendationCore.php @@ -38,15 +38,11 @@ abstract class AmazonRecommendationCore extends AmazonCore{ * This defaults to FALSE.

* @param array|string $m [optional]

The files (or file) to use in Mock Mode.

* @param string $config [optional]

An alternate config file to set. Used for testing.

+ * @throws Exception */ public function __construct($s = null, $mock = false, $m = null, $config = null){ parent::__construct($s, $mock, $m, $config); include($this->env); - if (file_exists($this->config)){ - include($this->config); - } else { - throw new Exception('Config file does not exist!'); - } if (isset($AMAZON_VERSION_RECOMMEND)){ $this->urlbranch = 'Recommendations/' . $AMAZON_VERSION_RECOMMEND; @@ -59,12 +55,14 @@ public function __construct($s = null, $mock = false, $m = null, $config = null) if(isset($THROTTLE_TIME_RECOMMEND)) { $this->throttleTime = $THROTTLE_TIME_RECOMMEND; } - - if (isset($store[$this->storeName]['marketplaceId'])){ - $this->setMarketplace($store[$this->storeName]['marketplaceId']); + //reset to store's default marketplace + $storeMP = $this->config->getStoreMarketPlace($this->storeName); + if(empty($storeMP)){ + $this->log("Marketplace ID is missing",'Urgent'); } else { - $this->log("Marketplace ID is missing", 'Urgent'); + $this->setMarketplace($storeMP); } + } /** diff --git a/includes/classes/AmazonSubscriptionCore.php b/includes/classes/AmazonSubscriptionCore.php index f555b874..7e0004d5 100644 --- a/includes/classes/AmazonSubscriptionCore.php +++ b/includes/classes/AmazonSubscriptionCore.php @@ -42,11 +42,6 @@ abstract class AmazonSubscriptionCore extends AmazonCore{ public function __construct($s = null, $mock = false, $m = null, $config = null){ parent::__construct($s, $mock, $m, $config); include($this->env); - if (file_exists($this->config)){ - include($this->config); - } else { - throw new Exception('Config file does not exist!'); - } if (isset($AMAZON_VERSION_SUBSCRIBE)){ $this->urlbranch = 'Subscriptions/' . $AMAZON_VERSION_SUBSCRIBE; @@ -60,10 +55,12 @@ public function __construct($s = null, $mock = false, $m = null, $config = null) $this->throttleTime = $THROTTLE_TIME_SUBSCRIBE; } - if (isset($store[$this->storeName]['marketplaceId'])){ - $this->setMarketplace($store[$this->storeName]['marketplaceId']); + //reset to store's default marketplace + $storeMP = $this->config->getStoreMarketPlace($this->storeName); + if(empty($storeMP)){ + $this->log("Marketplace ID is missing",'Urgent'); } else { - $this->log("Marketplace ID is missing", 'Urgent'); + $this->setMarketplace($storeMP); } } diff --git a/test-cases/helperFunctions.php b/test-cases/helperFunctions.php index e21a6a36..682f8553 100644 --- a/test-cases/helperFunctions.php +++ b/test-cases/helperFunctions.php @@ -38,5 +38,4 @@ function parseLog($s = null){ } } return $return; -} -?> +} \ No newline at end of file diff --git a/test-cases/includes/classes/AmazonConfigTest.php b/test-cases/includes/classes/AmazonConfigTest.php new file mode 100644 index 00000000..ce98e673 --- /dev/null +++ b/test-cases/includes/classes/AmazonConfigTest.php @@ -0,0 +1,70 @@ +object = new AmazonMWSConfig( __DIR__.'/../../test-config.php'); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown() { + + } + + /** + * @covers AmazonMWSConfig::__construct() + */ + public function testParsing() { + $this->assertEquals($this->object->getStoreCount(),2); + $store = $this->object->getStoreCount('testStore'); + $this->assertTrue(count($store) != 0); // Check we have some records. + + $this->assertEquals($this->object->getEndPoint(),'https://mws.amazonservices.com/'); + $this->assertEquals($this->object->getLogCallback(),null); + $this->assertEquals($this->object->isLoggingDisabled(),false); + + $store = $this->object->getStoreCount('bad'); + $this->assertTrue(count($store) == 1); // Check we have some 1 record + } + + public function testCreateCopy() { + $store = $this->object->getConfigFor('testStore'); + + + $this->assertEquals($this->object->getLogFile(),$store->getLogFile()); + $this->assertEquals($this->object->getLogCallback(),$store->getLogCallback()); + $this->assertEquals($this->object->getEndPoint(),$store->getEndPoint()); + $this->assertEquals($this->object->getStoreCount(),2); + $this->assertEquals($this->object->getStore('testStore'),$store->getStore('testStore')); + + } + + public function testCreateCopyAll() { + // Passing the already created object in should result in the config being loaded from it. + $newConfig = new AmazonMWSConfig($this->object); + + $this->assertEquals($this->object->getStoreCount(),$newConfig->getStoreCount()); + $this->assertEquals($this->object->getLogFile(),$newConfig->getLogFile()); + $this->assertEquals($this->object->getLogCallback(),$newConfig->getLogCallback()); + $stores = array_keys($this->object->getStores()); + foreach($stores as $storeName) { + $this->assertEquals($this->object->getStore($storeName),$newConfig->getStore($storeName)); + } + } + +} + +require_once('helperFunctions.php'); diff --git a/test-cases/includes/classes/AmazonCoreTest.php b/test-cases/includes/classes/AmazonCoreTest.php index 56c46052..b1094e39 100644 --- a/test-cases/includes/classes/AmazonCoreTest.php +++ b/test-cases/includes/classes/AmazonCoreTest.php @@ -9,6 +9,8 @@ class AmazonCoreTest extends PHPUnit_Framework_TestCase { * @var AmazonServiceStatus */ protected $object; + /** @var AmazonMWSConfig */ + protected $config; /** * Sets up the fixture, for example, opens a network connection. @@ -16,6 +18,7 @@ class AmazonCoreTest extends PHPUnit_Framework_TestCase { */ protected function setUp() { resetLog(); + $this->config = new AmazonMWSConfig( __DIR__.'/../../test-config.php'); $this->object = new AmazonServiceStatus('testStore', 'Inbound', true, null, __DIR__.'/../../test-config.php'); } @@ -69,16 +72,32 @@ public function testSetConfig() { /** * @covers AmazonCore::setLogPath - * @expectedException Exception - * @expectedExceptionMessage Log file does not exist or cannot be read! (no) */ public function testSetLogPath() { - $this->object->setLogPath('no'); + if (PHP_OS == 'Windows') { + $this->object->setLogPath('C:\\BadFolder\\ReallyBadFolder\\BadLogFile.txt'); + } else { + $this->object->setLogPath('/dev/no/file/exists'); + } + $this->assertTrue($this->object->getConfig()->isLoggingDisabled()); + } + + /** + * @covers AmazonCore::setLogPath + */ + public function testSetLogPathToFolder() { + if (PHP_OS == 'Windows') { + $this->object->setLogPath('C:\\Windows\\'); // !! + } else { + $this->object->setLogPath('/var/log/'); + } + $this->assertTrue($this->object->getConfig()->isLoggingDisabled()); } /** + * Updated to use the new config class instead. + * * @covers AmazonCore::setStore - * @todo Implement testSetStore(). */ public function testSetStore() { $this->object->setStore('no'); @@ -88,6 +107,11 @@ public function testSetStore() { resetLog(); $this->object->setStore('bad'); $bad = parseLog(); + $this->assertEquals('Store bad does not exist!',$bad[0]); + resetLog(); + // Ok, now load the 'bad' store from the config. + $this->object->setConfig($this->config->getConfigFor('bad')); + $bad = parseLog(); $this->assertEquals('Merchant ID is missing!',$bad[0]); $this->assertEquals('Access Key ID is missing!',$bad[1]); $this->assertEquals('Secret Key is missing!',$bad[2]);