├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── composer.json ├── phpcs.xml.dist ├── phpunit.xml.dist ├── src ├── Exceptions │ ├── PrestashopWebServiceException.php │ └── PrestashopWebServiceRequestException.php ├── PrestaShopWebserviceException.php ├── PrestashopWebService.php ├── PrestashopWebServiceFacade.php ├── PrestashopWebServiceLibrary.php ├── PrestashopWebServiceProvider.php └── config.php └── tests ├── PrestashopWebServiceTest.php ├── TestCase.php ├── config └── prestashop-webservice.php └── requests └── category-schema.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | .idea/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.0 5 | - 7.1 6 | - 7.2 7 | sudo: false 8 | 9 | before_script: 10 | - travis_retry composer self-update 11 | - travis_retry composer install --no-interaction 12 | 13 | script: vendor/bin/phpunit 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | Copyright (c) 2016 Protech Studio di Laera Vito 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Laravel Prestashop Web Service 2 | ======== 3 | 4 | Laravel 5 wrapper for Prestashop Web Service Library 5 | 6 | Installation 7 | ------------ 8 | 9 | Require this package with composer using the following command: 10 | 11 | ```shell 12 | composer require protechstudio/laravel-prestashop-webservice 13 | ``` 14 | 15 | After updating composer, add the service provider to the `providers` array in `config/app.php` 16 | 17 | ```php 18 | Protechstudio\PrestashopWebService\PrestashopWebServiceProvider::class, 19 | ``` 20 | 21 | You may also add the Facade in the `aliases` array in `config/app.php` 22 | 23 | ```php 24 | 'Prestashop' => Protechstudio\PrestashopWebService\PrestashopWebServiceFacade::class, 25 | ``` 26 | 27 | Finally publish the configuration file using the artisan command 28 | 29 | ```shell 30 | php artisan vendor:publish --provider="Protechstudio\PrestashopWebService\PrestashopWebServiceProvider" 31 | ``` 32 | 33 | Configuration 34 | ------------- 35 | 36 | Open the published configuration file at `config/prestashop-webservice.php`: 37 | 38 | ```php 39 | return [ 40 | 'url' => 'http://domain.com', 41 | 'token' => '', 42 | 'debug' => env('APP_DEBUG', false) 43 | ]; 44 | ``` 45 | 46 | Then populate the `url` field with the **root url** of the targeted Prestashop installation and `token` field with the API token obtained from Prestashop control panel in Web Service section. If `debug` is `true` Prestashop will return debug information when responding to API requests. 47 | 48 | Usage 49 | ----- 50 | 51 | You may use the Prestashop Web Service wrapper in two ways: 52 | ### Using the dependency or method injection 53 | 54 | ```php 55 | ... 56 | use Protechstudio\PrestashopWebService\PrestashopWebService; 57 | 58 | class FooController extends Controller 59 | { 60 | private $prestashop; 61 | 62 | public function __construct(PrestashopWebService $prestashop) 63 | { 64 | $this->prestashop = $prestashop; 65 | } 66 | 67 | public function bar() 68 | { 69 | $opt['resource'] = 'customers'; 70 | $xml=$this->prestashop->get($opt); 71 | } 72 | } 73 | ``` 74 | ### Using the Facade 75 | 76 | ```php 77 | ... 78 | use Prestashop; 79 | 80 | ... 81 | 82 | public function bar() 83 | { 84 | $opt['resource'] = 'customers'; 85 | $xml=Prestashop::get($opt); 86 | } 87 | ``` 88 | 89 | Prestashop Underlying library usage 90 | --------------------------- 91 | 92 | You may find complete documentation and tutorials regarding Prestashop Web Service Library in the [Prestashop Documentation](http://doc.prestashop.com/display/PS16/Using+the+PrestaShop+Web+Service). 93 | 94 | Helper methods 95 | -------------- 96 | 97 | I've added some helper methods to reduce development time: 98 | 99 | ### Retrieving resource schema and filling data for posting 100 | 101 | You may call `getSchema()` method to retrieve the requested resource schema. You may then fill the schema with an associative array of data with `fillSchema()` method. 102 | 103 | ```php 104 | 105 | $xmlSchema=Prestashop::getSchema('categories'); //returns a SimpleXMLElement instance with the desired schema 106 | 107 | $data=[ 108 | 'name'=>'Clothes', 109 | 'link_rewrite'=>'clothes', 110 | 'active'=>true 111 | ]; 112 | 113 | $postXml=Prestashop::fillSchema($xmlSchema,$data); 114 | 115 | //The xml is now ready for being sent back to the web service to create a new category 116 | 117 | $response=Prestashop::add(['resource'=>'categories','postXml'=>$postXml->asXml()]); 118 | 119 | ``` 120 | 121 | #### Preserving not filled nodes from removal 122 | 123 | The default behaviour for the `fillSchema` method is to remove the nodes that are not filled. If you want to preserve those nodes (typical update situation) put the third parameter as `false` 124 | 125 | ```php 126 | $putXml=Prestashop::fillSchema($xmlSchema,$data,false); 127 | ``` 128 | 129 | #### Removing specific nodes 130 | 131 | When preserving unfilled nodes from removal you may specify some nodes to be removed as the fourth argument (this may be useful when updating a resource with some readonly nodes that would trigger error 400): 132 | 133 | ```php 134 | $putXml=Prestashop::fillSchema($xmlSchema,$data,false,['manufacturer_name','quantity']); 135 | //manufacturer_name and quantity only will be removed from the XML 136 | ``` 137 | 138 | 139 | #### Handling language values 140 | 141 | If the node has a language child you may use a simple string for the value if your shop has only one language installed. 142 | 143 | ```php 144 | /* 145 | xml node with one language child example 146 | ... 147 | 148 | 149 | 150 | ... 151 | */ 152 | $data= ['name'=>'Clothes']; 153 | ``` 154 | 155 | If your shops has more than one language installed you may pass the node value as an array where the key is the language ID. 156 | 157 | ```php 158 | /* 159 | xml node with n language children example 160 | ... 161 | 162 | 163 | 164 | 165 | ... 166 | */ 167 | $data= [ 168 | 'name'=>[ 169 | 1 => 'Clothes', 170 | 2 => 'Abbigliamento' 171 | ] 172 | ]; 173 | ``` 174 | _Please note that if you don't provide an array of values keyed by the language ID all language values will have the same value._ 175 | 176 | #### Handling associations with several siblings 177 | 178 | Provided you got a node with several associations like category association for products or similar as from this extract of product schema: 179 | 180 | ```xml 181 | ... 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | ... 195 | ``` 196 | You can prepare the array data map for the `fillSchema` method in this way: 197 | 198 | ```php 199 | $data => [ 200 | ... 201 | 'associations' => [ 202 | 'categories' =>[ 203 | [ 'category' => ['id' => 4] ], 204 | [ 'category' => ['id' => 5] ], 205 | [ 'category' => ['id' => 11] ], 206 | ], 207 | 'product_features' => [ 208 | [ 209 | 'product_feature' => [ 210 | 'id' => 5, 211 | 'id_feature_value' => 94 212 | ] 213 | ], 214 | [ 215 | 'product_feature' => [ 216 | 'id' => 1, 217 | 'id_feature_value' => 2 218 | ] 219 | ] 220 | ] 221 | ] 222 | ] 223 | ``` 224 | The result will be this as expected: 225 | 226 | ```xml 227 | ... 228 | 229 | 230 | 231 | 4 232 | 233 | 234 | 5 235 | 236 | 237 | 11 238 | 239 | 240 | 241 | 242 | 5 243 | 94 244 | 245 | 246 | 1 247 | 2 248 | 249 | 250 | ... 251 | ``` 252 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "protechstudio/laravel-prestashop-webservice", 3 | "description": "Laravel 5 wrapper for Prestashop Web Service Library", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Vito Laera", 8 | "email": "vito.laera@protechstudio.it" 9 | } 10 | ], 11 | "autoload": { 12 | "psr-4": { 13 | "Protechstudio\\PrestashopWebService\\": "src/" 14 | } 15 | }, 16 | "autoload-dev": { 17 | "psr-4": { 18 | "Protechstudio\\PrestashopWebService\\Tests\\": "tests/" 19 | } 20 | }, 21 | "extra": { 22 | "laravel": { 23 | "providers": [ 24 | "Protechstudio\\PrestashopWebService\\PrestashopWebServiceProvider" 25 | ], 26 | "aliases": { 27 | "Prestashop": "Protechstudio\\PrestashopWebService\\PrestashopWebServiceFacade" 28 | } 29 | } 30 | }, 31 | "require-dev": { 32 | "squizlabs/php_codesniffer": "^3.2", 33 | "Orchestra/Testbench": "^3.5" 34 | }, 35 | "scripts": { 36 | "test": "vendor/bin/phpunit", 37 | "cs": "vendor/bin/phpcs src/*" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | A basic coding standard. 5 | 6 | vendor/* 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | app/ 20 | 21 | 22 | ./vendor 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Exceptions/PrestashopWebServiceException.php: -------------------------------------------------------------------------------- 1 | response = $response; 16 | } 17 | 18 | public function hasResponse() 19 | { 20 | return isset($this->response) && !empty($this->response); 21 | } 22 | 23 | public function getResponse() 24 | { 25 | return $this->response; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/PrestaShopWebserviceException.php: -------------------------------------------------------------------------------- 1 | get(['resource' => $resource . "?schema=$schema"]); 21 | } 22 | 23 | /** 24 | * Fill the provided schema with an associative array data, also remove the useless XML nodes if 25 | * the corresponding flag is true 26 | * 27 | * @param SimpleXMLElement $xmlSchema 28 | * @param array $data 29 | * @param bool $removeUselessNodes set true if you want to remove nodes that are not present in the data array 30 | * @param array $removeSpecificNodes If $removeUselessNodes is false you may add here the first level nodes that 31 | * you want to remove 32 | * @return SimpleXMLElement 33 | */ 34 | public function fillSchema( 35 | SimpleXMLElement $xmlSchema, 36 | $data, 37 | $removeUselessNodes = true, 38 | $removeSpecificNodes = array() 39 | ) { 40 | $resource = $xmlSchema->children()->children(); 41 | foreach ($data as $key => $value) { 42 | $this->processNode($resource, $key, $value); 43 | } 44 | if ($removeUselessNodes) { 45 | $this->checkForUselessNodes($resource, $data); 46 | } else { 47 | $this->removeSpecificNodes($resource, $removeSpecificNodes); 48 | } 49 | return $xmlSchema; 50 | } 51 | 52 | /** 53 | * @param string|array $data 54 | * @param $languageId 55 | * @return string 56 | */ 57 | private function getLanguageValue($data, $languageId) 58 | { 59 | if (is_string($data)) { 60 | return $data; 61 | } 62 | 63 | if (array_key_exists($languageId, $data)) { 64 | return $data[$languageId]; 65 | } else { 66 | return $data[1]; 67 | } 68 | } 69 | 70 | /** 71 | * @param $node 72 | * @param $data 73 | */ 74 | private function fillLanguageNode($node, $data) 75 | { 76 | for ($i = 0; $i < count($node->language); $i++) { 77 | $node->language[$i] = $this->getLanguageValue($data, (int)$node->language[$i]['id']->__toString()); 78 | } 79 | } 80 | 81 | /** 82 | * @param SimpleXMLElement $node 83 | * @param $dataKey 84 | * @param $dataValue 85 | */ 86 | private function processNode(SimpleXMLElement $node, $dataKey, $dataValue) 87 | { 88 | if (is_int($dataKey)) { 89 | if ($dataKey===0) { 90 | $this->emptyNode($node); 91 | } 92 | $this->createNode($node, $dataValue); 93 | } elseif (property_exists($node->$dataKey, 'language')) { 94 | $this->fillLanguageNode($node->$dataKey, $dataValue); 95 | } elseif (is_array($dataValue)) { 96 | foreach ($dataValue as $key => $value) { 97 | $this->processNode($node->$dataKey, $key, $value); 98 | } 99 | } else { 100 | $node->$dataKey = $dataValue; 101 | } 102 | } 103 | 104 | /** 105 | * Remove XML first level nodes that are not present int the data array 106 | * @param SimpleXMLElement $resource 107 | * @param $data 108 | */ 109 | private function checkForUselessNodes(SimpleXMLElement $resource, $data) 110 | { 111 | $uselessNodes = []; 112 | foreach ($resource as $key => $value) { 113 | if (!array_key_exists($key, $data)) { 114 | $uselessNodes[] = $key; 115 | } 116 | } 117 | foreach ($uselessNodes as $key) { 118 | unset($resource->$key); 119 | } 120 | } 121 | 122 | /** 123 | * Remove the given nodes from the resource 124 | * @param $resource 125 | * @param $removeSpecificNodes 126 | */ 127 | private function removeSpecificNodes($resource, $removeSpecificNodes) 128 | { 129 | foreach ($removeSpecificNodes as $node) { 130 | unset($resource->$node); 131 | } 132 | } 133 | 134 | /** 135 | * @param SimpleXMLElement $node 136 | * @param array $dataValue 137 | */ 138 | private function createNode(SimpleXMLElement $node, $dataValue) 139 | { 140 | foreach ($dataValue as $key => $value) { 141 | if (is_array($value)) { 142 | if (is_int($key)) { 143 | $this->createNode($node, $value); 144 | } else { 145 | $childNode=$node->addChild($key); 146 | $this->createNode($childNode, $value); 147 | } 148 | } else { 149 | $node->addChild($key, $value); 150 | } 151 | } 152 | } 153 | 154 | /** 155 | * @param SimpleXMLElement $node 156 | */ 157 | private function emptyNode(SimpleXMLElement $node) 158 | { 159 | $nodeNames = array(); 160 | foreach ($node->children() as $key => $value) { 161 | $nodeNames[]=$key; 162 | } 163 | foreach ($nodeNames as $nodeName) { 164 | unset($node->$nodeName); 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/PrestashopWebServiceFacade.php: -------------------------------------------------------------------------------- 1 | 37 | * getMessage(); 47 | * } 48 | * ?> 49 | * 50 | * @param string $url Root URL for the shop 51 | * @param string $key Authentification key 52 | * @param mixed $debug Debug mode Activated (true) or deactivated (false) 53 | * @throws PrestashopWebServiceException 54 | */ 55 | public function __construct($url, $key, $debug = true) 56 | { 57 | if (!extension_loaded('curl')) { 58 | $exception = 'Please activate the PHP extension \'curl\' to allow use of PrestaShop WebService library'; 59 | throw new PrestashopWebServiceException($exception); 60 | } 61 | 62 | $this->url = $url; 63 | $this->key = $key; 64 | $this->debug = $debug; 65 | $this->version = 'unknown'; 66 | 67 | $this->runningInConsole = app()->runningInConsole(); 68 | } 69 | 70 | /** 71 | * Take the status code and throw an exception if the server didn't return 200 or 201 code 72 | * @param int $status_code Status code of an HTTP return 73 | * @return boolean 74 | * @throws PrestashopWebServiceException 75 | */ 76 | protected function checkRequest($request) 77 | { 78 | if ($request['status_code'] === 200 || $request['status_code'] === 201) { 79 | return true; 80 | } 81 | 82 | $messages = array( 83 | 204 => 'No content', 84 | 400 => 'Bad Request', 85 | 401 => 'Unauthorized', 86 | 404 => 'Not Found', 87 | 405 => 'Method Not Allowed', 88 | 500 => 'Internal Server Error', 89 | ); 90 | 91 | if (isset($messages[$request['status_code']])) { 92 | $xml = null; 93 | if ($request['response']) { 94 | $xml = $this->parseXML($request['response'], true); 95 | } 96 | 97 | throw new PrestashopWebServiceRequestException($messages[$request['status_code']], $request['status_code'], $xml); 98 | } else { 99 | $exception = 'This call to PrestaShop Web Services returned an unexpected HTTP status of: '; 100 | $exception.= $request['status_code']; 101 | throw new PrestashopWebServiceException($exception); 102 | } 103 | } 104 | 105 | /** 106 | * Throws exception if prestashop version is not supported 107 | * @param int $version The prestashop version 108 | * @throws PrestashopWebServiceException 109 | */ 110 | public function isPrestashopVersionSupported($version) 111 | { 112 | if (version_compare($version, self::PS_COMPATIBLE_VERSION_MIN, '>=') === false || 113 | version_compare($version, self::PS_COMPATIBLE_VERSION_MAX, '<=') === false 114 | ) { 115 | $exception = 'This library is not compatible with this version of PrestaShop. '; 116 | $exception.= 'Please upgrade/downgrade this library'; 117 | throw new PrestashopWebServiceException($exception); 118 | } 119 | } 120 | 121 | /** 122 | * Prepares and validate a CURL request to PrestaShop WebService. Can throw exception. 123 | * @param string $url Resource name 124 | * @param mixed $curl_params CURL parameters (sent to curl_set_opt) 125 | * @return array status_code, response 126 | * @throws PrestashopWebServiceException 127 | */ 128 | protected function executeRequest($url, $curl_params = array()) 129 | { 130 | $defaultParams = array( 131 | CURLOPT_HEADER => true, 132 | CURLOPT_RETURNTRANSFER => true, 133 | CURLINFO_HEADER_OUT => true, 134 | CURLOPT_HTTPAUTH => CURLAUTH_BASIC, 135 | CURLOPT_USERPWD => $this->key.':', 136 | CURLOPT_HTTPHEADER => array( 'Expect:' ), 137 | CURLOPT_SSL_VERIFYPEER => config('app.env') === 'local' ? 0 : 1, 138 | CURLOPT_SSL_VERIFYHOST => config('app.env') === 'local' ? 0 : 2 // value 1 is not accepted https://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYHOST.html 139 | ); 140 | 141 | $curl_options = array(); 142 | foreach ($defaultParams as $defkey => $defval) { 143 | if (isset($curl_params[$defkey])) { 144 | $curl_options[$defkey] = $curl_params[$defkey]; 145 | } else { 146 | $curl_options[$defkey] = $defaultParams[$defkey]; 147 | } 148 | } 149 | foreach ($curl_params as $defkey => $defval) { 150 | if (!isset($curl_options[$defkey])) { 151 | $curl_options[$defkey] = $curl_params[$defkey]; 152 | } 153 | } 154 | 155 | list($response, $info, $error) = $this->executeCurl($url, $curl_options); 156 | 157 | $status_code = $info['http_code']; 158 | if ($status_code === 0 || $error) { 159 | throw new PrestashopWebServiceException('CURL Error: '.$error); 160 | } 161 | 162 | $index = $info['header_size']; 163 | if ($index === false && $curl_params[CURLOPT_CUSTOMREQUEST] !== 'HEAD') { 164 | throw new PrestashopWebServiceException('Bad HTTP response'); 165 | } 166 | 167 | $header = substr($response, 0, $index); 168 | $body = substr($response, $index); 169 | 170 | $headerArray = array(); 171 | foreach (explode("\n", $header) as $headerItem) { 172 | $tmp = explode(':', $headerItem, 2); 173 | if (count($tmp) === 2) { 174 | $tmp = array_map('trim', $tmp); 175 | $headerArray[$tmp[0]] = $tmp[1]; 176 | } 177 | } 178 | 179 | if (array_key_exists('PSWS-Version', $headerArray)) { 180 | $this->isPrestashopVersionSupported($headerArray['PSWS-Version']); 181 | $this->version = $headerArray['PSWS-Version']; 182 | } 183 | 184 | $this->printDebug('HTTP REQUEST HEADER', $info['request_header']); 185 | $this->printDebug('HTTP RESPONSE HEADER', $header); 186 | 187 | if ($curl_params[CURLOPT_CUSTOMREQUEST] == 'PUT' || $curl_params[CURLOPT_CUSTOMREQUEST] == 'POST') { 188 | $this->printDebug('XML SENT', urldecode($curl_params[CURLOPT_POSTFIELDS])); 189 | } 190 | if ($curl_params[CURLOPT_CUSTOMREQUEST] != 'DELETE' && $curl_params[CURLOPT_CUSTOMREQUEST] != 'HEAD') { 191 | $this->printDebug('RETURN HTTP BODY', $body); 192 | } 193 | 194 | return array( 195 | 'status_code' => $status_code, 196 | 'response' => $body, 197 | 'header' => $header, 198 | 'headers' => $headerArray 199 | ); 200 | } 201 | 202 | /** 203 | * Executes the CURL request to PrestaShop WebService. 204 | * @param string $url Resource name 205 | * @param mixed $options CURL parameters (sent to curl_setopt_array) 206 | * @return array response, info 207 | */ 208 | protected function executeCurl($url, array $options = array()) 209 | { 210 | $session = curl_init($url); 211 | 212 | if (count($options)) { 213 | curl_setopt_array($session, $options); 214 | } 215 | 216 | $response = curl_exec($session); 217 | 218 | $error = false; 219 | $info = curl_getinfo($session); 220 | if ($response === false) { 221 | $error = curl_error($session); 222 | } 223 | 224 | curl_close($session); 225 | 226 | return array($response, $info, $error); 227 | } 228 | 229 | public function printDebug($title, $content) 230 | { 231 | if ($this->debug) { 232 | if ($this->runningInConsole) { 233 | echo 'START '.$title."\n"; 234 | echo $content . "\n"; 235 | echo 'END '.$title."\n"; 236 | echo "\n"; 237 | } else { 238 | echo '
'; 239 | echo '
'.$title.'
'; 240 | echo '
'.htmlentities($content).'
'; 241 | echo '
'; 242 | } 243 | } 244 | } 245 | 246 | public function getVersion() 247 | { 248 | return $this->version; 249 | } 250 | 251 | /** 252 | * Load XML from string. Can throw exception 253 | * @param string $response String from a CURL response 254 | * @param boolean $suppressExceptions Whether to throw exceptions on errors 255 | * @return SimpleXMLElement status_code, response 256 | * @throws PrestashopWebServiceException 257 | */ 258 | protected function parseXML($response, $suppressExceptions = false) 259 | { 260 | if ($response != '') { 261 | libxml_clear_errors(); 262 | libxml_use_internal_errors(true); 263 | $xml = simplexml_load_string($response, 'SimpleXMLElement', LIBXML_NOCDATA); 264 | if (libxml_get_errors()) { 265 | $msg = var_export(libxml_get_errors(), true); 266 | libxml_clear_errors(); 267 | 268 | if (!$suppressExceptions) { 269 | throw new PrestashopWebServiceException('HTTP XML response is not parsable: '.$msg); 270 | } 271 | } 272 | 273 | return $xml; 274 | } elseif (!$suppressExceptions) { 275 | throw new PrestashopWebServiceException('HTTP response is empty'); 276 | } 277 | 278 | return null; 279 | } 280 | 281 | /** 282 | * Add (POST) a resource 283 | *

Unique parameter must take :

284 | * 'resource' => Resource name
285 | * 'postXml' => Full XML string to add resource

286 | * Examples are given in the tutorial

287 | * @param array $options 288 | * @return SimpleXMLElement status_code, response 289 | * @throws PrestashopWebServiceException 290 | */ 291 | public function add($options) 292 | { 293 | $xml = ''; 294 | 295 | if (isset($options['resource'], $options['postXml']) || isset($options['url'], $options['postXml'])) { 296 | $url = (isset($options['resource']) ? $this->url.'/api/'.$options['resource'] : $options['url']); 297 | $xml = $options['postXml']; 298 | if (isset($options['id_shop'])) { 299 | $url .= '&id_shop='.$options['id_shop']; 300 | } 301 | if (isset($options['id_group_shop'])) { 302 | $url .= '&id_group_shop='.$options['id_group_shop']; 303 | } 304 | } else { 305 | throw new PrestashopWebServiceException('Bad parameters given'); 306 | } 307 | $request = $this->executeRequest($url, array(CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_POSTFIELDS => $xml)); 308 | 309 | $this->checkRequest($request); 310 | return $this->parseXML($request['response']); 311 | } 312 | 313 | /** 314 | * Retrieve (GET) a resource 315 | *

Unique parameter must take :

316 | * 'url' => Full URL for a GET request of WebService (ex: http://mystore.com/api/customers/1/)
317 | * OR
318 | * 'resource' => Resource name,
319 | * 'id' => ID of a resource you want to get

320 | *

321 | * 322 | * get(array('resource' => 'orders', 'id' => 1)); 328 | * // Here in $xml, a SimpleXMLElement object you can parse 329 | * foreach ($xml->children()->children() as $attName => $attValue) 330 | * echo $attName.' = '.$attValue.'
'; 331 | * } 332 | * catch (PrestashopWebServiceException $ex) 333 | * { 334 | * echo 'Error : '.$ex->getMessage(); 335 | * } 336 | * ?> 337 | *
338 | * @param array $options Array representing resource to get. 339 | * @return SimpleXMLElement status_code, response 340 | * @throws PrestashopWebServiceException 341 | */ 342 | public function get($options) 343 | { 344 | if (isset($options['url'])) { 345 | $url = $options['url']; 346 | } elseif (isset($options['resource'])) { 347 | $url = $this->url.'/api/'.$options['resource']; 348 | $url_params = array(); 349 | if (isset($options['id'])) { 350 | $url .= '/'.$options['id']; 351 | } 352 | 353 | $params = array('filter', 'display', 'sort', 'limit', 'id_shop', 'id_group_shop','date', 'price'); 354 | foreach ($params as $p) { 355 | foreach ($options as $k => $o) { 356 | if (strpos($k, $p) !== false) { 357 | $url_params[$k] = $options[$k]; 358 | } 359 | } 360 | } 361 | if (count($url_params) > 0) { 362 | $url .= '?'.http_build_query($url_params); 363 | } 364 | } else { 365 | throw new PrestashopWebServiceException('Bad parameters given'); 366 | } 367 | 368 | $request = $this->executeRequest($url, array(CURLOPT_CUSTOMREQUEST => 'GET')); 369 | 370 | $this->checkRequest($request);// check the response validity 371 | return $this->parseXML($request['response']); 372 | } 373 | 374 | /** 375 | * Head method (HEAD) a resource 376 | * 377 | * @param array $options Array representing resource for head request. 378 | * @return SimpleXMLElement status_code, response 379 | * @throws PrestashopWebServiceException 380 | */ 381 | public function head($options) 382 | { 383 | if (isset($options['url'])) { 384 | $url = $options['url']; 385 | } elseif (isset($options['resource'])) { 386 | $url = $this->url.'/api/'.$options['resource']; 387 | $url_params = array(); 388 | if (isset($options['id'])) { 389 | $url .= '/'.$options['id']; 390 | } 391 | 392 | $params = array('filter', 'display', 'sort', 'limit'); 393 | foreach ($params as $p) { 394 | foreach ($options as $k => $o) { 395 | if (strpos($k, $p) !== false) { 396 | $url_params[$k] = $options[$k]; 397 | } 398 | } 399 | } 400 | if (count($url_params) > 0) { 401 | $url .= '?'.http_build_query($url_params); 402 | } 403 | } else { 404 | throw new PrestashopWebServiceException('Bad parameters given'); 405 | } 406 | $request = $this->executeRequest($url, array(CURLOPT_CUSTOMREQUEST => 'HEAD', CURLOPT_NOBODY => true)); 407 | $this->checkRequest($request);// check the response validity 408 | return $request['header']; 409 | } 410 | 411 | /** 412 | * Edit (PUT) a resource 413 | *

Unique parameter must take :

414 | * 'resource' => Resource name ,
415 | * 'id' => ID of a resource you want to edit,
416 | * 'putXml' => Modified XML string of a resource

417 | * Examples are given in the tutorial

418 | * @param array $options Array representing resource to edit. 419 | * @return SimpleXMLElement 420 | * @throws PrestashopWebServiceException 421 | */ 422 | public function edit($options) 423 | { 424 | $xml = ''; 425 | if (isset($options['url'])) { 426 | $url = $options['url']; 427 | } elseif ((isset($options['resource'], $options['id']) || isset($options['url'])) && $options['putXml']) { 428 | if (isset($options['url'])) { 429 | $url = $options['url']; 430 | } else { 431 | $url = $this->url.'/api/'.$options['resource'].'/'.$options['id']; 432 | } 433 | $xml = $options['putXml']; 434 | if (isset($options['id_shop'])) { 435 | $url .= '&id_shop='.$options['id_shop']; 436 | } 437 | if (isset($options['id_group_shop'])) { 438 | $url .= '&id_group_shop='.$options['id_group_shop']; 439 | } 440 | } else { 441 | throw new PrestashopWebServiceException('Bad parameters given'); 442 | } 443 | 444 | $request = $this->executeRequest($url, array(CURLOPT_CUSTOMREQUEST => 'PUT', CURLOPT_POSTFIELDS => $xml)); 445 | $this->checkRequest($request);// check the response validity 446 | return $this->parseXML($request['response']); 447 | } 448 | 449 | /** 450 | * Delete (DELETE) a resource. 451 | * Unique parameter must take :

452 | * 'resource' => Resource name
453 | * 'id' => ID or array which contains IDs of a resource(s) you want to delete

454 | * 455 | * delete(array('resource' => 'orders', 'id' => 1)); 461 | * // Following code will not be executed if an exception is thrown. 462 | * echo 'Successfully deleted.'; 463 | * } 464 | * catch (PrestashopWebServiceException $ex) 465 | * { 466 | * echo 'Error : '.$ex->getMessage(); 467 | * } 468 | * ?> 469 | * 470 | * @param array $options Array representing resource to delete. 471 | * @return bool 472 | */ 473 | public function delete($options) 474 | { 475 | if (isset($options['url'])) { 476 | $url = $options['url']; 477 | } elseif (isset($options['resource']) && isset($options['id'])) { 478 | if (is_array($options['id'])) { 479 | $url = $this->url.'/api/'.$options['resource'].'/?id=['.implode(',', $options['id']).']'; 480 | } else { 481 | $url = $this->url.'/api/'.$options['resource'].'/'.$options['id']; 482 | } 483 | } 484 | if (isset($options['id_shop'])) { 485 | $url .= '&id_shop='.$options['id_shop']; 486 | } 487 | if (isset($options['id_group_shop'])) { 488 | $url .= '&id_group_shop='.$options['id_group_shop']; 489 | } 490 | $request = $this->executeRequest($url, array(CURLOPT_CUSTOMREQUEST => 'DELETE')); 491 | $this->checkRequest($request);// check the response validity 492 | return true; 493 | } 494 | } 495 | -------------------------------------------------------------------------------- /src/PrestashopWebServiceProvider.php: -------------------------------------------------------------------------------- 1 | publish(); 20 | } 21 | 22 | /** 23 | * Register the application services. 24 | * 25 | * @return void 26 | */ 27 | public function register() 28 | { 29 | $this->registerConfig(); 30 | 31 | $this->app->singleton(PrestashopWebService::class, function () { 32 | return new PrestashopWebService( 33 | config('prestashop-webservice.url'), 34 | config('prestashop-webservice.token'), 35 | config('prestashop-webservice.debug') 36 | ); 37 | }); 38 | } 39 | 40 | public function provides() 41 | { 42 | return ['Protechstudio\PrestashopWebService\PrestashopWebService']; 43 | } 44 | 45 | private function publish() 46 | { 47 | $this->publishes([ 48 | __DIR__ . '/config.php' => config_path('prestashop-webservice.php'), 49 | ], 'config'); 50 | } 51 | 52 | private function registerConfig() 53 | { 54 | $this->mergeConfigFrom(__DIR__ . '/config.php', 'prestashop-webservice'); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/config.php: -------------------------------------------------------------------------------- 1 | env('PRESTASHOP_URL', 'http://domain.com'), 5 | 'token' => env('PRESTASHOP_TOKEN', ''), 6 | 'debug' => env('PRESTASHOP_DEBUG', env('APP_DEBUG', false)) 7 | ]; 8 | -------------------------------------------------------------------------------- /tests/PrestashopWebServiceTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(PrestashopWebService::class, Prestashop::getFacadeRoot()); 17 | } 18 | 19 | /** @test */ 20 | public function test_request_is_correct() 21 | { 22 | $requestResponseStub = require(__DIR__.'/requests/category-schema.php'); 23 | 24 | list($header, $body) = explode("\n\n", $requestResponseStub[0], 2); 25 | $header_size = strlen($header) + 2; 26 | 27 | $this->assertEquals($header_size, $requestResponseStub[1]['header_size']); 28 | } 29 | 30 | /** @test */ 31 | public function it_can_perform_a_get_request() 32 | { 33 | $requestResponseStub = require(__DIR__.'/requests/category-schema.php'); 34 | $ps = $this->getMockedLibrary('executeCurl', $requestResponseStub); 35 | 36 | $xml = $ps->get(['resource' => 'categories']); 37 | 38 | $this->assertEquals('prestashop', $xml->getName()); 39 | $this->assertEquals('category', $xml->children()[0]->getName()); 40 | } 41 | 42 | /** @test */ 43 | public function it_throws_exception_on_404() 44 | { 45 | $ps = $this->getMockedLibrary('executeRequest', [ 46 | 'status_code' => 404, 47 | 'response' => ' 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | ', 56 | 'header' => '' 57 | ]); 58 | 59 | try { 60 | $xml = $ps->get(['resource' => 'categories']); 61 | } catch (PrestashopWebServiceRequestException $exception) { 62 | $this->assertEquals(404, $exception->getCode()); 63 | $this->assertTrue($exception->hasResponse()); 64 | $this->assertEquals('Invalid ID', (string)$exception->getResponse()->errors->error->message); 65 | } 66 | } 67 | 68 | /** @test */ 69 | public function it_throws_exception_on_unknown_http_status() 70 | { 71 | $ps = $this->getMockedLibrary('executeRequest', [ 72 | 'status_code' => 999, 73 | 'response' => ' 74 | 75 | ', 76 | 'header' => '' 77 | ]); 78 | 79 | $this->expectExceptionMessage('unexpected HTTP status of: 999', PrestashopWebServiceException::class); 80 | $xml = $ps->get(['resource' => 'categories']); 81 | } 82 | 83 | /** @test */ 84 | public function it_throws_exception_on_empty_response() 85 | { 86 | $ps = $this->getMockedLibrary('executeRequest', [ 87 | 'status_code' => 200, 88 | 'response' => '', 89 | 'header' => '' 90 | ]); 91 | 92 | $this->expectExceptionMessage('HTTP response is empty', PrestashopWebServiceException::class); 93 | $xml = $ps->get(['resource' => 'categories']); 94 | } 95 | 96 | /** @test */ 97 | public function it_throws_exception_on_malformed_xml() 98 | { 99 | $ps = $this->getMockedLibrary('executeRequest', [ 100 | 'status_code' => 200, 101 | 'response' => '', 102 | 'header' => '' 103 | ]); 104 | 105 | $this->expectExceptionMessage('HTTP XML response is not parsable', PrestashopWebServiceException::class); 106 | $xml = $ps->get(['resource' => 'categories']); 107 | } 108 | 109 | /** @test */ 110 | public function it_throws_exception_on_unsupported_version() 111 | { 112 | $this->expectExceptionMessage('This library is not compatible with this version of PrestaShop', PrestashopWebServiceException::class); 113 | Prestashop::isPrestashopVersionSupported('0.0.0.0'); 114 | Prestashop::isPrestashopVersionSupported('99.99.99.9999'); 115 | } 116 | 117 | /** @test */ 118 | public function it_throws_exception_on_unsupported_version_from_request() 119 | { 120 | $requestResponseStub = require(__DIR__.'/requests/category-schema.php'); 121 | $requestResponseStub[0] = preg_replace('/^PSWS-Version:(.+?)$/im', 'PSWS-Version: 99.99.99.9999', $requestResponseStub[0]); 122 | $ps = $this->getMockedLibrary('executeCurl', $requestResponseStub); 123 | 124 | $this->expectExceptionMessage('This library is not compatible with this version of PrestaShop', PrestashopWebServiceException::class); 125 | $xml = $ps->get(['resource' => 'categories']); 126 | } 127 | 128 | protected function getMockedLibrary($method = null, $returns = null) 129 | { 130 | $ps = $this->getMockBuilder(PrestashopWebServiceLibrary::class) 131 | ->setConstructorArgs([ 132 | env('prestashop-webservice.url'), 133 | env('prestashop-webservice.key'), 134 | env('prestashop-webservice.debug'), 135 | ]); 136 | 137 | if (!$method) { 138 | return $ps->getMock(); 139 | } else { 140 | $ps = $ps->setMethods([$method])->getMock(); 141 | 142 | $ps->expects($this->once()) 143 | ->method($method) 144 | ->willReturn($returns); 145 | return $ps; 146 | } 147 | } 148 | } 149 | 150 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | set('app.debug', true); 28 | $app['config']->set('prestashop-webservice', require('config/prestashop-webservice.php')); 29 | } 30 | 31 | protected function getPackageAliases($app) 32 | { 33 | return [ 34 | 'Prestashop' => \Protechstudio\PrestashopWebService\PrestashopWebServiceFacade::class, 35 | ]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/config/prestashop-webservice.php: -------------------------------------------------------------------------------- 1 | 'http://example.com', 5 | 'token' => '1234', 6 | 'debug' => true 7 | ]; 8 | -------------------------------------------------------------------------------- /tests/requests/category-schema.php: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ', 50 | 51 | json_decode('{"url":"http:\/\/example.com\/api\/categories?schema=blank","content_type":"text\/xml;charset=utf-8","http_code":200,"header_size":436,"request_size":155,"filetime":-1,"ssl_verify_result":0,"redirect_count":0,"total_time":0.149629,"namelookup_time":0.004162,"connect_time":0.025428,"pretransfer_time":0.025453,"size_upload":0,"size_download":825,"speed_download":5513,"speed_upload":0,"download_content_length":825,"upload_content_length":-1,"starttransfer_time":0.149337,"redirect_time":0,"redirect_url":"","primary_ip":"1.2.3.4","certinfo":[],"primary_port":80,"local_ip":"192.168.1.1","local_port":56568,"request_header":"GET \/api\/categories?schema=blank HTTP\/1.1\r\nHost: example.com\r\nAuthorization: Basic XXX\r\nAccept: *\/*\r\n\r\n"}', true), 52 | 53 | false 54 | ]; 55 | --------------------------------------------------------------------------------