PageRenderTime 106ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/classes/webservice/WebserviceRequest.php

https://github.com/netplayer/PrestaShop
PHP | 1826 lines | 1354 code | 128 blank | 344 comment | 278 complexity | 710522100b24bd45e1e61016cfd7a39b MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, LGPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /*
  3. * 2007-2014 PrestaShop
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@prestashop.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
  18. * versions in the future. If you wish to customize PrestaShop for your
  19. * needs please refer to http://www.prestashop.com for more information.
  20. *
  21. * @author Prestashop SA <contact@prestashop.com>
  22. * @copyright 2007-2010 Prestashop SA
  23. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  24. * International Registered Trademark & Property of PrestaShop SA
  25. */
  26. class WebserviceRequestCore
  27. {
  28. const HTTP_GET = 1;
  29. const HTTP_POST = 2;
  30. const HTTP_PUT = 4;
  31. protected $_available_languages = null;
  32. /**
  33. * Errors triggered at execution
  34. * @var array
  35. */
  36. public $errors = array();
  37. /**
  38. * Set if return should display content or not
  39. * @var boolean
  40. */
  41. protected $_outputEnabled = true;
  42. /**
  43. * Set if the management is specific or if it is classic (entity management)
  44. * @var boolean
  45. */
  46. protected $objectSpecificManagement = false;
  47. /**
  48. * Base PrestaShop webservice URL
  49. * @var string
  50. */
  51. public $wsUrl;
  52. /**
  53. * PrestaShop Webservice Documentation URL
  54. * @var string
  55. */
  56. protected $_docUrl = 'http://doc.prestashop.com/display/PS16/Using+the+PrestaShop+Web+Service';
  57. /**
  58. * Set if the authentication key was checked
  59. * @var boolean
  60. */
  61. protected $_authenticated = false;
  62. /**
  63. * HTTP Method to support
  64. * @var string
  65. */
  66. public $method;
  67. /**
  68. * The segment of the URL
  69. * @var array
  70. */
  71. public $urlSegment = array();
  72. /**
  73. * The segment list of the URL after the "api" segment
  74. * @var array
  75. */
  76. public $urlFragments = array();
  77. /**
  78. * The time in microseconds of the start of the execution of the web service request
  79. * @var int
  80. */
  81. protected $_startTime = 0;
  82. /**
  83. * The list of each resources manageable via web service
  84. * @var array
  85. */
  86. public $resourceList;
  87. /**
  88. * The configuration parameters of the current resource
  89. * @var array
  90. */
  91. public $resourceConfiguration;
  92. /**
  93. * The permissions for the current key
  94. * @var array
  95. */
  96. public $keyPermissions;
  97. /**
  98. * The XML string to display if web service call succeed
  99. * @var string
  100. */
  101. protected $specificOutput = '';
  102. /**
  103. * The list of objects to display
  104. * @var array
  105. */
  106. public $objects;
  107. /**
  108. * The current object to support, it extends the PrestaShop ObjectModel
  109. * @var ObjectModel
  110. */
  111. protected $_object;
  112. /**
  113. * The schema to display.
  114. * If null, no schema have to be displayed and normal management has to be performed
  115. * @var string
  116. */
  117. public $schemaToDisplay;
  118. /**
  119. * The fields to display. These fields will be displayed when retrieving objects
  120. * @var string
  121. */
  122. public $fieldsToDisplay = 'minimum';
  123. /**
  124. * If we are in PUT or POST case, we use this attribute to store the xml string value during process
  125. * @var string
  126. */
  127. protected $_inputXml;
  128. /**
  129. * Object instance for singleton
  130. * @var WebserviceRequest
  131. */
  132. protected static $_instance;
  133. /**
  134. * Key used for authentication
  135. * @var string
  136. */
  137. protected $_key;
  138. /**
  139. * This is used to have a deeper tree diagram.
  140. * @var int
  141. */
  142. public $depth = 0;
  143. /**
  144. * Name of the output format
  145. * @var string
  146. */
  147. protected $outputFormat = 'xml';
  148. /**
  149. * The object to build the output.
  150. * @var WebserviceOutputBuilder
  151. */
  152. protected $objOutput;
  153. /**
  154. * Save the class name for override used in getInstance()
  155. * @var ws_current_classname
  156. */
  157. public static $ws_current_classname;
  158. public static $shopIDs;
  159. public function getOutputEnabled()
  160. {
  161. return $this->_outputEnabled;
  162. }
  163. public function setOutputEnabled($bool)
  164. {
  165. if (Validate::isBool($bool))
  166. $this->_outputEnabled = $bool;
  167. return $this;
  168. }
  169. /**
  170. * Get WebserviceRequest object instance (Singleton)
  171. *
  172. * @return object WebserviceRequest instance
  173. */
  174. public static function getInstance()
  175. {
  176. if(!isset(self::$_instance))
  177. self::$_instance = new WebserviceRequest::$ws_current_classname();
  178. return self::$_instance;
  179. }
  180. protected function getOutputObject($type)
  181. {
  182. switch ($type)
  183. {
  184. case 'XML' :
  185. default :
  186. $obj_render = new WebserviceOutputXML();
  187. break;
  188. }
  189. return $obj_render;
  190. }
  191. public static function getResources()
  192. {
  193. $resources = array(
  194. 'addresses' => array('description' => 'The Customer, Manufacturer and Customer addresses','class' => 'Address'),
  195. 'carriers' => array('description' => 'The Carriers','class' => 'Carrier'),
  196. 'carts' => array('description' => 'Customer\'s carts', 'class' => 'Cart'),
  197. 'cart_rules' => array('description' => 'Cart rules management', 'class' => 'CartRule'),
  198. 'categories' => array('description' => 'The product categories','class' => 'Category'),
  199. 'combinations' => array('description' => 'The product combinations','class' => 'Combination'),
  200. 'configurations' => array('description' => 'Shop configuration', 'class' => 'Configuration'),
  201. 'contacts' => array('description' => 'Shop contacts','class' => 'Contact'),
  202. 'countries' => array('description' => 'The countries','class' => 'Country'),
  203. 'currencies' => array('description' => 'The currencies', 'class' => 'Currency'),
  204. 'customers' => array('description' => 'The e-shop\'s customers','class' => 'Customer'),
  205. 'customer_threads' => array('description' => 'Customer services threads','class' => 'CustomerThread'),
  206. 'customer_messages' => array('description' => 'Customer services messages','class' => 'CustomerMessage'),
  207. 'deliveries' => array('description' => 'Product delivery', 'class' => 'Delivery'),
  208. 'groups' => array('description' => 'The customer\'s groups','class' => 'Group'),
  209. 'guests' => array('description' => 'The guests', 'class' => 'Guest'),
  210. 'images' => array('description' => 'The images', 'specific_management' => true),
  211. 'image_types' => array('description' => 'The image types', 'class' => 'ImageType'),
  212. 'languages' => array('description' => 'Shop languages', 'class' => 'Language'),
  213. 'manufacturers' => array('description' => 'The product manufacturers','class' => 'Manufacturer'),
  214. 'order_carriers' => array('description' => 'The Order carriers','class' => 'OrderCarrier'),
  215. 'order_details' => array('description' => 'Details of an order', 'class' => 'OrderDetail'),
  216. 'order_discounts' => array('description' => 'Discounts of an order', 'class' => 'OrderDiscount'),
  217. 'order_histories' => array('description' => 'The Order histories','class' => 'OrderHistory'),
  218. 'order_invoices' => array('description' => 'The Order invoices','class' => 'OrderInvoice'),
  219. 'orders' => array('description' => 'The Customers orders','class' => 'Order'),
  220. 'order_payments' => array('description' => 'The Order payments','class' => 'OrderPayment'),
  221. 'order_states' => array('description' => 'The Order statuses','class' => 'OrderState'),
  222. 'order_slip' => array('description' => 'The Order slips', 'class' => 'OrderSlip'),
  223. 'price_ranges' => array('description' => 'Price ranges', 'class' => 'RangePrice'),
  224. 'product_features' => array('description' => 'The product features','class' => 'Feature'),
  225. 'product_feature_values' => array('description' => 'The product feature values','class' => 'FeatureValue'),
  226. 'product_options' => array('description' => 'The product options','class' => 'AttributeGroup'),
  227. 'product_option_values' => array('description' => 'The product options value','class' => 'Attribute'),
  228. 'products' => array('description' => 'The products','class' => 'Product'),
  229. 'states' => array('description' => 'The available states of countries','class' => 'State'),
  230. 'stores' => array('description' => 'The stores', 'class' => 'Store'),
  231. 'suppliers' => array('description' => 'The product suppliers','class' => 'Supplier'),
  232. 'tags' => array('description' => 'The Products tags','class' => 'Tag'),
  233. 'translated_configurations' => array('description' => 'Shop configuration', 'class' => 'TranslatedConfiguration'),
  234. 'weight_ranges' => array('description' => 'Weight ranges', 'class' => 'RangeWeight'),
  235. 'zones' => array('description' => 'The Countries zones','class' => 'Zone'),
  236. 'employees' => array('description' => 'The Employees', 'class' => 'Employee'),
  237. 'search' => array('description' => 'Search', 'specific_management' => true, 'forbidden_method' => array('PUT', 'POST', 'DELETE')),
  238. 'content_management_system' => array('description' => 'Content management system', 'class' => 'CMS'),
  239. 'shops' => array('description' => 'Shops from multi-shop feature', 'class' => 'Shop'),
  240. 'shop_groups' => array('description' => 'Shop groups from multi-shop feature', 'class' => 'ShopGroup'),
  241. 'taxes' => array('description' => 'The tax rate', 'class' => 'Tax'),
  242. 'stock_movements' => array('description' => 'Stock movements', 'class' => 'StockMvtWS', 'forbidden_method' => array('PUT', 'POST', 'DELETE')),
  243. 'stock_movement_reasons' => array('description' => 'Stock movement reason', 'class' => 'StockMvtReason'),
  244. 'warehouses' => array('description' => 'Warehouses', 'class' => 'Warehouse', 'forbidden_method' => array('DELETE')),
  245. 'stocks' => array('description' => 'Stocks', 'class' => 'Stock', 'forbidden_method' => array('PUT', 'POST', 'DELETE')),
  246. 'stock_availables' => array('description' => 'Available quantities', 'class' => 'StockAvailable', 'forbidden_method' => array('POST', 'DELETE')),
  247. 'warehouse_product_locations' => array('description' => 'Location of products in warehouses', 'class' => 'WarehouseProductLocation', 'forbidden_method' => array('PUT', 'POST', 'DELETE')),
  248. 'supply_orders' => array('description' => 'Supply Orders', 'class' => 'SupplyOrder', 'forbidden_method' => array('PUT', 'POST', 'DELETE')),
  249. 'supply_order_details' => array('description' => 'Supply Order Details', 'class' => 'SupplyOrderDetail', 'forbidden_method' => array('PUT', 'POST', 'DELETE')),
  250. 'supply_order_states' => array('description' => 'Supply Order Statuses', 'class' => 'SupplyOrderState', 'forbidden_method' => array('PUT', 'POST', 'DELETE')),
  251. 'supply_order_histories' => array('description' => 'Supply Order Histories', 'class' => 'SupplyOrderHistory', 'forbidden_method' => array('PUT', 'POST', 'DELETE')),
  252. 'supply_order_receipt_histories' => array('description' => 'Supply Order Receipt Histories', 'class' => 'SupplyOrderReceiptHistory', 'forbidden_method' => array('PUT', 'POST', 'DELETE')),
  253. 'product_suppliers' => array('description' => 'Product Suppliers', 'class' => 'ProductSupplier', 'forbidden_method' => array('PUT', 'POST', 'DELETE')),
  254. 'tax_rules' => array('description' => 'Tax rules entity', 'class' => 'TaxRule'),
  255. 'tax_rule_groups' => array('description' => 'Tax rule groups', 'class' => 'TaxRulesGroup'),
  256. 'specific_prices' => array('description' => 'Specific price management', 'class' => 'SpecificPrice'),
  257. 'specific_price_rules' => array('description' => 'Specific price management', 'class' => 'SpecificPriceRule'),
  258. );
  259. ksort($resources);
  260. return $resources;
  261. }
  262. // @todo Check how get parameters
  263. // @todo : set this method out
  264. /**
  265. * This method is used for calculate the price for products on the output details
  266. *
  267. * @param $field
  268. * @param $entity_object
  269. * @param $ws_params
  270. * @return array field parameters.
  271. */
  272. public function getPriceForProduct($field, $entity_object, $ws_params)
  273. {
  274. if (is_int($entity_object->id))
  275. {
  276. $arr_return = $this->specificPriceForProduct($entity_object, array('default_price'=>''));
  277. $field['value'] = $arr_return['default_price']['value'];
  278. }
  279. return $field;
  280. }
  281. // @todo : set this method out
  282. /**
  283. * This method is used for calculate the price for products on a virtual fields
  284. *
  285. * @param $entity_object
  286. * @param array $parameters
  287. * @return array
  288. */
  289. public function specificPriceForProduct($entity_object, $parameters)
  290. {
  291. foreach(array_keys($parameters) as $name)
  292. {
  293. $parameters[$name]['object_id'] = $entity_object->id;
  294. }
  295. $arr_return = $this->specificPriceCalculation($parameters);
  296. return $arr_return;
  297. }
  298. public function specificPriceCalculation($parameters)
  299. {
  300. $arr_return = array();
  301. foreach($parameters as $name => $value)
  302. {
  303. $id_shop = (int)Context::getContext()->shop->id;
  304. $id_country = (int)(isset($value['country']) ? $value['country'] : (Configuration::get('PS_COUNTRY_DEFAULT')));
  305. $id_state = (int)(isset($value['state']) ? $value['state'] : 0);
  306. $id_currency = (int)(isset($value['currency']) ? $value['currency'] : Configuration::get('PS_CURRENCY_DEFAULT'));
  307. $id_group = (int)(isset($value['group']) ? $value['group'] : (int)Configuration::get('PS_CUSTOMER_GROUP'));
  308. $quantity = (int)(isset($value['quantity']) ? $value['quantity'] : 1);
  309. $use_tax = (int)(isset($value['use_tax']) ? $value['use_tax'] : Configuration::get('PS_TAX'));
  310. $decimals = (int)(isset($value['decimals']) ? $value['decimals'] : Configuration::get('PS_PRICE_ROUND_MODE'));
  311. $id_product_attribute = (int)(isset($value['product_attribute']) ? $value['product_attribute'] : null);
  312. $only_reduc = (int)(isset($value['only_reduction']) ? $value['only_reduction'] : false);
  313. $use_reduc = (int)(isset($value['use_reduction']) ? $value['use_reduction'] : true);
  314. $use_ecotax = (int)(isset($value['use_ecotax']) ? $value['use_ecotax'] : Configuration::get('PS_USE_ECOTAX'));
  315. $specific_price_output = null;
  316. $id_county = (int)(isset($value['county']) ? $value['county'] : 0);
  317. $return_value = Product::priceCalculation($id_shop, $value['object_id'], $id_product_attribute, $id_country, $id_state, $id_county, $id_currency, $id_group, $quantity,
  318. $use_tax, $decimals, $only_reduc, $use_reduc, $use_ecotax, $specific_price_output, null);
  319. $arr_return[$name] = array('sqlId'=>strtolower($name), 'value'=>sprintf('%f', $return_value));
  320. }
  321. return $arr_return;
  322. }
  323. // @todo : set this method out
  324. /**
  325. * This method is used for calculate the price for products on a virtual fields
  326. *
  327. * @param $entity_object
  328. * @param array $parameters
  329. * @return array
  330. */
  331. public function specificPriceForCombination($entity_object, $parameters)
  332. {
  333. foreach(array_keys($parameters) as $name)
  334. {
  335. $parameters[$name]['object_id'] = $entity_object->id_product;
  336. $parameters[$name]['product_attribute'] = $entity_object->id;
  337. }
  338. $arr_return = $this->specificPriceCalculation($parameters);
  339. return $arr_return;
  340. }
  341. /**
  342. * Start Webservice request
  343. * Check webservice activation
  344. * Check autentication
  345. * Check resource
  346. * Check HTTP Method
  347. * Execute the action
  348. * Display the result
  349. *
  350. * @param string $key
  351. * @param string $method
  352. * @param string $url
  353. * @param string $params
  354. * @param string $inputXml
  355. *
  356. * @return array Returns an array of results (headers, content, type of resource...)
  357. */
  358. public function fetch($key, $method, $url, $params, $bad_class_name, $inputXml = NULL)
  359. {
  360. // Time logger
  361. $this->_startTime = microtime(true);
  362. $this->objects = array();
  363. // Error handler
  364. set_error_handler(array($this, 'webserviceErrorHandler'));
  365. ini_set('html_errors', 'off');
  366. // Two global vars, for compatibility with the PS core...
  367. global $webservice_call, $display_errors;
  368. $webservice_call = true;
  369. $display_errors = strtolower(ini_get('display_errors')) != 'off';
  370. // __PS_BASE_URI__ is from Shop::$current_base_uri
  371. $this->wsUrl = Tools::getHttpHost(true).__PS_BASE_URI__.'api/';
  372. // set the output object which manage the content and header structure and informations
  373. $this->objOutput = new WebserviceOutputBuilder($this->wsUrl);
  374. $this->_key = trim($key);
  375. $this->outputFormat = isset($params['output_format']) ? $params['output_format'] : $this->outputFormat;
  376. // Set the render object to build the output on the asked format (XML, JSON, CSV, ...)
  377. $this->objOutput->setObjectRender($this->getOutputObject($this->outputFormat));
  378. $this->params = $params;
  379. // Check webservice activation and request authentication
  380. if ($this->webserviceChecks())
  381. {
  382. if ($bad_class_name)
  383. $this->setError(500, 'Bad override class name for this key. Please update class_name field', 126);
  384. // parse request url
  385. $this->method = $method;
  386. $this->urlSegment = explode('/', $url);
  387. $this->urlFragments = $params;
  388. $this->_inputXml = $inputXml;
  389. $this->depth = isset($this->urlFragments['depth']) ? (int)$this->urlFragments['depth'] : $this->depth;
  390. try {
  391. // Method below set a particular fonction to use on the price field for products entity
  392. // @see WebserviceRequest::getPriceForProduct() method
  393. // @see WebserviceOutputBuilder::setSpecificField() method
  394. $this->objOutput->setSpecificField($this, 'getPriceForProduct', 'price', 'products');
  395. if (isset($this->urlFragments['price']))
  396. {
  397. $this->objOutput->setVirtualField($this, 'specificPriceForCombination', 'combinations', $this->urlFragments['price']);
  398. $this->objOutput->setVirtualField($this, 'specificPriceForProduct', 'products', $this->urlFragments['price']);
  399. }
  400. } catch (Exception $e) {
  401. $this->setError(500, $e->getMessage(), $e->getCode());
  402. }
  403. if (isset($this->urlFragments['language']))
  404. $this->_available_languages = $this->filterLanguage();
  405. else
  406. {
  407. foreach (Language::getLanguages() as $key=>$language)
  408. $this->_available_languages[] = $language['id_lang'];
  409. }
  410. if (empty($this->_available_languages))
  411. $this->setError(400, 'language is not available', 81);
  412. // Need to set available languages for the render object.
  413. // Thus we can filter i18n field for the output
  414. // @see WebserviceOutputXML::renderField() method for example
  415. $this->objOutput->objectRender->setLanguages($this->_available_languages);
  416. // check method and resource
  417. if (empty($this->errors) && $this->checkResource() && $this->checkHTTPMethod())
  418. {
  419. // The resource list is necessary for build the output
  420. $this->objOutput->setWsResources($this->resourceList);
  421. // if the resource is a core entity...
  422. if (!isset($this->resourceList[$this->urlSegment[0]]['specific_management']) || !$this->resourceList[$this->urlSegment[0]]['specific_management'])
  423. {
  424. // load resource configuration
  425. if ($this->urlSegment[0] != '')
  426. {
  427. $object = new $this->resourceList[$this->urlSegment[0]]['class']();
  428. if (isset($this->resourceList[$this->urlSegment[0]]['parameters_attribute']))
  429. $this->resourceConfiguration = $object->getWebserviceParameters($this->resourceList[$this->urlSegment[0]]['parameters_attribute']);
  430. else
  431. $this->resourceConfiguration = $object->getWebserviceParameters();
  432. }
  433. $success = false;
  434. // execute the action
  435. switch ($this->method)
  436. {
  437. case 'GET':
  438. case 'HEAD':
  439. if ($this->executeEntityGetAndHead())
  440. $success = true;
  441. break;
  442. case 'POST':
  443. if ($this->executeEntityPost())
  444. $success = true;
  445. break;
  446. case 'PUT':
  447. if ($this->executeEntityPut())
  448. $success = true;
  449. break;
  450. case 'DELETE':
  451. $this->executeEntityDelete();
  452. break;
  453. }
  454. // Need to set an object for the WebserviceOutputBuilder object in any case
  455. // because schema need to get webserviceParameters of this object
  456. if (isset($object))
  457. $this->objects['empty'] = $object;
  458. }
  459. // if the management is specific
  460. else
  461. {
  462. $specificObjectName = 'WebserviceSpecificManagement'.ucfirst(Tools::toCamelCase($this->urlSegment[0]));
  463. if(!class_exists($specificObjectName))
  464. $this->setError(501, sprintf('The specific management class is not implemented for the "%s" entity.', $this->urlSegment[0]), 124);
  465. else
  466. {
  467. $this->objectSpecificManagement = new $specificObjectName();
  468. $this->objectSpecificManagement->setObjectOutput($this->objOutput)
  469. ->setWsObject($this);
  470. try {
  471. $this->objectSpecificManagement->manage();
  472. } catch (WebserviceException $e) {
  473. if ($e->getType() == WebserviceException::DID_YOU_MEAN)
  474. $this->setErrorDidYouMean($e->getStatus(), $e->getMessage(), $e->getWrongValue(), $e->getAvailableValues(), $e->getCode());
  475. elseif ($e->getType() == WebserviceException::SIMPLE)
  476. $this->setError($e->getStatus(), $e->getMessage(), $e->getCode());
  477. }
  478. }
  479. }
  480. }
  481. }
  482. $return = $this->returnOutput();
  483. unset($webservice_call);
  484. unset($display_errors);
  485. return $return;
  486. }
  487. protected function webserviceChecks()
  488. {
  489. return ($this->isActivated() && $this->authenticate() && $this->groupShopExists($this->params) && $this->shopExists($this->params) && $this->shopHasRight($this->_key));
  490. }
  491. /**
  492. * Set a webservice error
  493. *
  494. * @param int $status
  495. * @param string $label
  496. * @param int $code
  497. * @return void
  498. */
  499. public function setError($status, $label, $code)
  500. {
  501. global $display_errors;
  502. if (!isset($display_errors))
  503. $display_errors = strtolower(ini_get('display_errors')) != 'off';
  504. if (isset($this->objOutput))
  505. $this->objOutput->setStatus($status);
  506. $this->errors[] = $display_errors ? array($code, $label) : 'Internal error. To see this error please display the PHP errors.';
  507. }
  508. /**
  509. * Set a webservice error and propose a new value near from the available values
  510. *
  511. * @param int $num
  512. * @param string $label
  513. * @param array $value
  514. * @param array $values
  515. * @param int $code
  516. * @return void
  517. */
  518. public function setErrorDidYouMean($num, $label, $value, $available_values, $code)
  519. {
  520. $this->setError($num, $label.'. Did you mean: "'.$this->getClosest($value, $available_values).'"?'.(count($available_values) > 1 ? ' The full list is: "'.implode('", "', $available_values).'"' : ''), $code);
  521. }
  522. /**
  523. * Return the nearest value picked in the values list
  524. *
  525. * @param string $input
  526. * @param array $words
  527. * @return string
  528. */
  529. protected function getClosest($input, $words)
  530. {
  531. $shortest = -1;
  532. foreach ($words as $word)
  533. {
  534. $lev = levenshtein($input, $word);
  535. if ($lev == 0)
  536. {
  537. $closest = $word;
  538. $shortest = 0;
  539. break;
  540. }
  541. if ($lev <= $shortest || $shortest < 0)
  542. {
  543. $closest = $word;
  544. $shortest = $lev;
  545. }
  546. }
  547. return $closest;
  548. }
  549. /**
  550. * Used to replace the default PHP error handler, in order to display PHP errors in a XML format
  551. *
  552. * @param string $errno contains the level of the error raised, as an integer
  553. * @param array $errstr contains the error message, as a string
  554. * @param array $errfile errfile, which contains the filename that the error was raised in, as a string
  555. * @param array $errline errline, which contains the line number the error was raised at, as an integer
  556. * @return boolean Always return true to avoid the default PHP error handler
  557. */
  558. public function webserviceErrorHandler($errno, $errstr, $errfile, $errline)
  559. {
  560. $display_errors = strtolower(ini_get('display_errors')) != 'off';
  561. if (!(error_reporting() & $errno) || $display_errors)
  562. return;
  563. $errortype = array (
  564. E_ERROR => 'Error',
  565. E_WARNING => 'Warning',
  566. E_PARSE => 'Parse',
  567. E_NOTICE => 'Notice',
  568. E_CORE_ERROR => 'Core Error',
  569. E_CORE_WARNING => 'Core Warning',
  570. E_COMPILE_ERROR => 'Compile Error',
  571. E_COMPILE_WARNING => 'Compile Warning',
  572. E_USER_ERROR => 'Error',
  573. E_USER_WARNING => 'User warning',
  574. E_USER_NOTICE => 'User notice',
  575. E_STRICT => 'Runtime Notice',
  576. E_RECOVERABLE_ERROR => 'Recoverable error'
  577. );
  578. $type = (isset($errortype[$errno]) ? $errortype[$errno] : 'Unknown error');
  579. error_log('[PHP '.$type.' #'.$errno.'] '.$errstr.' ('.$errfile.', line '.$errline.')');
  580. switch($errno)
  581. {
  582. case E_ERROR:
  583. WebserviceRequest::getInstance()->setError(500, '[PHP Error #'.$errno.'] '.$errstr.' ('.$errfile.', line '.$errline.')', 2);
  584. break;
  585. case E_WARNING:
  586. WebserviceRequest::getInstance()->setError(500, '[PHP Warning #'.$errno.'] '.$errstr.' ('.$errfile.', line '.$errline.')', 3);
  587. break;
  588. case E_PARSE:
  589. WebserviceRequest::getInstance()->setError(500, '[PHP Parse #'.$errno.'] '.$errstr.' ('.$errfile.', line '.$errline.')', 4);
  590. break;
  591. case E_NOTICE:
  592. WebserviceRequest::getInstance()->setError(500, '[PHP Notice #'.$errno.'] '.$errstr.' ('.$errfile.', line '.$errline.')', 5);
  593. break;
  594. case E_CORE_ERROR:
  595. WebserviceRequest::getInstance()->setError(500, '[PHP Core #'.$errno.'] '.$errstr.' ('.$errfile.', line '.$errline.')', 6);
  596. break;
  597. case E_CORE_WARNING:
  598. WebserviceRequest::getInstance()->setError(500, '[PHP Core warning #'.$errno.'] '.$errstr.' ('.$errfile.', line '.$errline.')', 7);
  599. break;
  600. case E_COMPILE_ERROR:
  601. WebserviceRequest::getInstance()->setError(500, '[PHP Compile #'.$errno.'] '.$errstr.' ('.$errfile.', line '.$errline.')', 8);
  602. break;
  603. case E_COMPILE_WARNING:
  604. WebserviceRequest::getInstance()->setError(500, '[PHP Compile warning #'.$errno.'] '.$errstr.' ('.$errfile.', line '.$errline.')', 9);
  605. break;
  606. case E_USER_ERROR:
  607. WebserviceRequest::getInstance()->setError(500, '[PHP Error #'.$errno.'] '.$errstr.' ('.$errfile.', line '.$errline.')', 10);
  608. break;
  609. case E_USER_WARNING:
  610. WebserviceRequest::getInstance()->setError(500, '[PHP User warning #'.$errno.'] '.$errstr.' ('.$errfile.', line '.$errline.')', 11);
  611. break;
  612. case E_USER_NOTICE:
  613. WebserviceRequest::getInstance()->setError(500, '[PHP User notice #'.$errno.'] '.$errstr.' ('.$errfile.', line '.$errline.')', 12);
  614. break;
  615. case E_STRICT:
  616. WebserviceRequest::getInstance()->setError(500, '[PHP Strict #'.$errno.'] '.$errstr.' ('.$errfile.', line '.$errline.')', 13);
  617. break;
  618. case E_RECOVERABLE_ERROR:
  619. WebserviceRequest::getInstance()->setError(500, '[PHP Recoverable error #'.$errno.'] '.$errstr.' ('.$errfile.', line '.$errline.')', 14);
  620. break;
  621. default:
  622. WebserviceRequest::getInstance()->setError(500, '[PHP Unknown error #'.$errno.'] '.$errstr.' ('.$errfile.', line '.$errline.')', 15);
  623. }
  624. return true;
  625. }
  626. /**
  627. * Check if there is one or more error
  628. *
  629. * @return boolean
  630. */
  631. protected function hasErrors()
  632. {
  633. return (boolean)$this->errors;
  634. }
  635. /**
  636. * Check request authentication
  637. *
  638. * @return boolean
  639. */
  640. protected function authenticate()
  641. {
  642. if (!$this->hasErrors())
  643. {
  644. if (is_null($this->_key))
  645. {
  646. $this->setError(401, 'Please enter the authentication key as the login. No password required', 16);
  647. }
  648. else
  649. {
  650. if (empty($this->_key))
  651. {
  652. $this->setError(401, 'Authentication key is empty', 17);
  653. }
  654. elseif (strlen($this->_key) != '32')
  655. {
  656. $this->setError(401, 'Invalid authentication key format', 18);
  657. }
  658. else
  659. {
  660. $keyValidation = WebserviceKey::isKeyActive($this->_key);
  661. if (is_null($keyValidation))
  662. {
  663. $this->setError(401, 'Authentification key does not exist', 19);
  664. }
  665. elseif($keyValidation === true)
  666. {
  667. $this->keyPermissions = WebserviceKey::getPermissionForAccount($this->_key);
  668. }
  669. else
  670. {
  671. $this->setError(401, 'Authentification key is not active', 20);
  672. }
  673. if (!$this->keyPermissions)
  674. {
  675. $this->setError(401, 'No permission for this authentication key', 21);
  676. }
  677. }
  678. }
  679. if ($this->hasErrors())
  680. {
  681. header('WWW-Authenticate: Basic realm="Welcome to PrestaShop Webservice, please enter the authentication key as the login. No password required."');
  682. $this->objOutput->setStatus(401);
  683. return false;
  684. }
  685. else
  686. {
  687. // only now we can say the access is authenticated
  688. $this->_authenticated = true;
  689. return true;
  690. }
  691. }
  692. }
  693. /**
  694. * Check webservice activation
  695. *
  696. * @return boolean
  697. */
  698. protected function isActivated()
  699. {
  700. if (!Configuration::get('PS_WEBSERVICE'))
  701. {
  702. $this->setError(503, 'The PrestaShop webservice is disabled. Please activate it in the PrestaShop Back Office', 22);
  703. return false;
  704. }
  705. return true;
  706. }
  707. protected function shopHasRight($key)
  708. {
  709. $sql = 'SELECT 1
  710. FROM '._DB_PREFIX_.'webservice_account wsa LEFT JOIN '._DB_PREFIX_.'webservice_account_shop wsas ON (wsa.id_webservice_account = wsas.id_webservice_account)
  711. WHERE wsa.key = \''.pSQL($key).'\'';
  712. foreach (self::$shopIDs as $id_shop)
  713. {
  714. $OR[] = ' wsas.id_shop = '.(int)$id_shop.' ';
  715. }
  716. $sql .= ' AND ('.implode('OR', $OR).') ';
  717. if (!Db::getInstance()->getValue($sql))
  718. {
  719. $this->setError(403, 'No permission for this key on this shop', 132);
  720. return false;
  721. }
  722. return true;
  723. }
  724. protected function shopExists($params)
  725. {
  726. if (count(self::$shopIDs))
  727. return true;
  728. if (isset($params['id_shop']))
  729. {
  730. if ($params['id_shop'] != 'all' && is_numeric($params['id_shop']))
  731. {
  732. Shop::setContext(Shop::CONTEXT_SHOP, (int)$params['id_shop']);
  733. self::$shopIDs[] = (int)$params['id_shop'];
  734. return true;
  735. }
  736. else if ($params['id_shop'] == 'all')
  737. {
  738. Shop::setContext(Shop::CONTEXT_ALL);
  739. self::$shopIDs = Shop::getShops(true, null, true);
  740. return true;
  741. }
  742. }
  743. else
  744. {
  745. self::$shopIDs[] = Context::getContext()->shop->id;
  746. return true;
  747. }
  748. $this->setError(404, 'This shop id does not exist', 999);
  749. return false;
  750. }
  751. protected function groupShopExists($params)
  752. {
  753. if (isset($params['id_group_shop']) && is_numeric($params['id_group_shop']))
  754. {
  755. Shop::setContext(Shop::CONTEXT_GROUP, (int)$params['id_group_shop']);
  756. self::$shopIDs = Shop::getShops(true, (int)$params['id_group_shop'], true);
  757. if (count(self::$shopIDs) == 0)
  758. {
  759. // @FIXME Set ErrorCode !
  760. $this->setError(500, 'This group shop doesn\'t have shops', 999);
  761. return false;
  762. }
  763. }
  764. // id_group_shop isn't mandatory
  765. return true;
  766. }
  767. /**
  768. * Check HTTP method
  769. *
  770. * @return boolean
  771. */
  772. protected function checkHTTPMethod()
  773. {
  774. if (!in_array($this->method, array('GET', 'POST', 'PUT', 'DELETE', 'HEAD')))
  775. $this->setError(405, 'Method '.$this->method.' is not valid', 23);
  776. elseif (isset($this->urlSegment[0]) && isset($this->resourceList[$this->urlSegment[0]]['forbidden_method']) && in_array($this->method, $this->resourceList[$this->urlSegment[0]]['forbidden_method']))
  777. $this->setError(405, 'Method '.$this->method.' is not allowed for the resource '.$this->urlSegment[0], 101);
  778. elseif ($this->urlSegment[0] && !in_array($this->method, $this->keyPermissions[$this->urlSegment[0]]))
  779. $this->setError(405, 'Method '.$this->method.' is not allowed for the resource '.$this->urlSegment[0].' with this authentication key', 25);
  780. else
  781. return true;
  782. return false;
  783. }
  784. /**
  785. * Check resource validity
  786. *
  787. * @return boolean
  788. */
  789. protected function checkResource()
  790. {
  791. $this->resourceList = $this->getResources();
  792. $resourceNames = array_keys($this->resourceList);
  793. if ($this->urlSegment[0] == '')
  794. $this->resourceConfiguration['objectsNodeName'] = 'resources';
  795. elseif (in_array($this->urlSegment[0], $resourceNames))
  796. {
  797. if (!in_array($this->urlSegment[0], array_keys($this->keyPermissions)))
  798. {
  799. $this->setError(401, 'Resource of type "'.$this->urlSegment[0].'" is not allowed with this authentication key', 26);
  800. return false;
  801. }
  802. }
  803. else
  804. {
  805. $this->setErrorDidYouMean(400, 'Resource of type "'.$this->urlSegment[0].'" does not exists', $this->urlSegment[0], $resourceNames, 27);
  806. return false;
  807. }
  808. return true;
  809. }
  810. protected function setObjects()
  811. {
  812. $objects = array();
  813. $arr_avoid_id = array();
  814. $ids = array();
  815. if (isset($this->urlFragments['id']))
  816. {
  817. preg_match('#^\[(.*)\]$#Ui', $this->urlFragments['id'], $matches);
  818. if (count($matches) > 1)
  819. $ids = explode(',', $matches[1]);
  820. }
  821. else
  822. {
  823. $ids[] = (int)$this->urlSegment[1];
  824. }
  825. if (!empty($ids))
  826. {
  827. foreach ($ids as $id)
  828. {
  829. $object = new $this->resourceConfiguration['retrieveData']['className']((int)$id);
  830. if (!$object->id)
  831. $arr_avoid_id[] = $id;
  832. else
  833. $objects[] = $object;
  834. }
  835. }
  836. if (!empty($arr_avoid_id) || empty($ids))
  837. {
  838. $this->setError(404, 'Id(s) not exists: '.implode(', ', $arr_avoid_id), 87);
  839. $this->_outputEnabled = true;
  840. }
  841. else
  842. {
  843. }
  844. }
  845. protected function parseDisplayFields($str)
  846. {
  847. $bracket_level = 0;
  848. $part = array();
  849. $tmp = '';
  850. for ($i = 0; $i < strlen($str); $i++)
  851. {
  852. if ($str[$i] == ',' && $bracket_level == 0)
  853. {
  854. $part[] = $tmp;
  855. $tmp = '';
  856. }
  857. else
  858. $tmp .= $str[$i];
  859. if ($str[$i] == '[')
  860. $bracket_level++;
  861. if ($str[$i] == ']')
  862. $bracket_level--;
  863. }
  864. if ($tmp != '')
  865. $part[] = $tmp;
  866. $fields = array();
  867. foreach ($part as $str)
  868. {
  869. $field_name = trim(substr($str, 0, (strpos($str, '[') === false ? strlen($str) : strpos($str, '['))));
  870. if (!isset($fields[$field_name])) $fields[$field_name] = null;
  871. if (strpos($str, '[') !== false)
  872. {
  873. $sub_fields = substr($str, strpos($str, '[') + 1, strlen($str) - strpos($str, '[') - 2);
  874. $tmp_array = array();
  875. if (strpos($sub_fields, ',') !== false)
  876. $tmp_array = explode(',', $sub_fields);
  877. else
  878. $tmp_array = array($sub_fields);
  879. $fields[$field_name] = (is_array($fields[$field_name])) ? array_merge($fields[$field_name], $tmp_array) : $tmp_array;
  880. }
  881. }
  882. return $fields;
  883. }
  884. public function setFieldsToDisplay()
  885. {
  886. // set the fields to display in the list : "full", "minimum", "field_1", "field_1,field_2,field_3"
  887. if (isset($this->urlFragments['display']))
  888. {
  889. $this->fieldsToDisplay = $this->urlFragments['display'];
  890. if ($this->fieldsToDisplay != 'full' && $this->fieldsToDisplay != 'minimum')
  891. {
  892. preg_match('#^\[(.*)\]$#Ui', $this->fieldsToDisplay, $matches);
  893. if (count($matches))
  894. {
  895. $error = false;
  896. $fieldsToTest = $this->parseDisplayFields($matches[1]);
  897. foreach ($fieldsToTest as $field_name => $part)
  898. {
  899. // in case it is not an association
  900. if (!is_array($part))
  901. {
  902. // We have to allow new specific field for price calculation too
  903. $error = (!isset($this->resourceConfiguration['fields'][$field_name]) && !isset($this->urlFragments['price'][$field_name]));
  904. }
  905. else
  906. {
  907. // if this association does not exists
  908. if (!array_key_exists($field_name, $this->resourceConfiguration['associations'])) {
  909. $error = true;
  910. }
  911. foreach ($part as $field)
  912. if ($field != 'id' && !array_key_exists($field, $this->resourceConfiguration['associations'][$field_name]['fields']))
  913. {
  914. $error = true;
  915. break;
  916. }
  917. }
  918. if ($error)
  919. {
  920. $this->setError(400,'Unable to display this field "'.$field_name.(is_array($part) ? ' (details : '.var_export($part, true).')' : '').'". However, these are available: '.implode(', ', array_keys($this->resourceConfiguration['fields'])), 35);
  921. return false;
  922. }
  923. }
  924. $this->fieldsToDisplay = $fieldsToTest;
  925. }
  926. else
  927. {
  928. $this->setError(400, 'The \'display\' syntax is wrong. You can set \'full\' or \'[field_1,field_2,field_3,...]\'. These are available: '.implode(', ', array_keys($this->resourceConfiguration['fields'])), 36);
  929. return false;
  930. }
  931. }
  932. }
  933. return true;
  934. }
  935. protected function manageFilters()
  936. {
  937. // filtered fields which can not use filters : hidden_fields
  938. $available_filters = array();
  939. // filtered i18n fields which can use filters
  940. $i18n_available_filters = array();
  941. foreach ($this->resourceConfiguration['fields'] as $fieldName => $field)
  942. if ((!isset($this->resourceConfiguration['hidden_fields']) ||
  943. (isset($this->resourceConfiguration['hidden_fields']) && !in_array($fieldName, $this->resourceConfiguration['hidden_fields']))))
  944. if ((!isset($field['i18n']) ||
  945. (isset($field['i18n']) && !$field['i18n'])))
  946. $available_filters[] = $fieldName;
  947. else
  948. $i18n_available_filters[] = $fieldName;
  949. // Date feature : date=1
  950. if (!empty($this->urlFragments['date']) && $this->urlFragments['date'])
  951. {
  952. if (!in_array('date_add', $available_filters))
  953. $available_filters[] = 'date_add';
  954. if (!in_array('date_upd', $available_filters))
  955. $available_filters[] = 'date_upd';
  956. if (!array_key_exists('date_add', $this->resourceConfiguration['fields']))
  957. $this->resourceConfiguration['fields']['date_add'] = array('sqlId' => 'date_add');
  958. if (!array_key_exists('date_upd', $this->resourceConfiguration['fields']))
  959. $this->resourceConfiguration['fields']['date_upd'] = array('sqlId' => 'date_upd');
  960. }
  961. else
  962. foreach ($available_filters as $key => $value)
  963. if ($value == 'date_add' || $value == 'date_upd')
  964. unset($available_filters[$key]);
  965. //construct SQL filter
  966. $sql_filter = '';
  967. $sql_join = '';
  968. if ($this->urlFragments)
  969. {
  970. $schema = 'schema';
  971. // if we have to display the schema
  972. if (isset($this->urlFragments[$schema]))
  973. {
  974. if ($this->urlFragments[$schema] == 'blank' || $this->urlFragments[$schema] == 'synopsis')
  975. {
  976. $this->schemaToDisplay = $this->urlFragments[$schema];
  977. return true;
  978. }
  979. else
  980. {
  981. $this->setError(400, 'Please select a schema of type \'synopsis\' to get the whole schema informations (which fields are required, which kind of content...) or \'blank\' to get an empty schema to fill before using POST request', 28);
  982. return false;
  983. }
  984. }
  985. else
  986. {
  987. // if there are filters
  988. if (isset($this->urlFragments['filter']))
  989. foreach ($this->urlFragments['filter'] as $field => $url_param)
  990. {
  991. if ($field != 'sort' && $field != 'limit')
  992. {
  993. if (!in_array($field, $available_filters))
  994. {
  995. // if there are linked tables
  996. if (isset($this->resourceConfiguration['linked_tables']) && isset($this->resourceConfiguration['linked_tables'][$field]))
  997. {
  998. // contruct SQL join for linked tables
  999. $sql_join .= 'LEFT JOIN `'.bqSQL(_DB_PREFIX_.$this->resourceConfiguration['linked_tables'][$field]['table']).'` '.bqSQL($field).' ON (main.`'.bqSQL($this->resourceConfiguration['fields']['id']['sqlId']).'` = '.bqSQL($field).'.`'.bqSQL($this->resourceConfiguration['fields']['id']['sqlId']).'`)'."\n";
  1000. // construct SQL filter for linked tables
  1001. foreach ($url_param as $field2 => $value)
  1002. {
  1003. if (isset($this->resourceConfiguration['linked_tables'][$field]['fields'][$field2]))
  1004. {
  1005. $linked_field = $this->resourceConfiguration['linked_tables'][$field]['fields'][$field2];
  1006. $sql_filter .= $this->getSQLRetrieveFilter($linked_field['sqlId'], $value, $field.'.');
  1007. }
  1008. else
  1009. {
  1010. $list = array_keys($this->resourceConfiguration['linked_tables'][$field]['fields']);
  1011. $this->setErrorDidYouMean(400, 'This filter does not exist for this linked table', $field2, $list, 29);
  1012. return false;
  1013. }
  1014. }
  1015. }
  1016. elseif ($url_param != '' && in_array($field, $i18n_available_filters))
  1017. {
  1018. if (!is_array($url_param))
  1019. $url_param = array($url_param);
  1020. $sql_join .= 'LEFT JOIN `'.bqSQL(_DB_PREFIX_.$this->resourceConfiguration['retrieveData']['table']).'_lang` AS main_i18n ON (main.`'.pSQL($this->resourceConfiguration['fields']['id']['sqlId']).'` = main_i18n.`'.bqSQL($this->resourceConfiguration['fields']['id']['sqlId']).'`)'."\n";
  1021. foreach ($url_param as $field2 => $value)
  1022. {
  1023. $linked_field = $this->resourceConfiguration['fields'][$field];
  1024. $sql_filter .= $this->getSQLRetrieveFilter($linked_field['sqlId'], $value, 'main_i18n.');
  1025. $language_filter = '['.implode('|',$this->_available_languages).']';
  1026. $sql_filter .= $this->getSQLRetrieveFilter('id_lang', $language_filter, 'main_i18n.');
  1027. }
  1028. }
  1029. // if there are filters on linked tables but there are no linked table
  1030. elseif (is_array($url_param))
  1031. {
  1032. if (isset($this->resourceConfiguration['linked_tables']))
  1033. $this->setErrorDidYouMean(400, 'This linked table does not exist', $field, array_keys($this->resourceConfiguration['linked_tables']), 30);
  1034. else
  1035. $this->setError(400, 'There is no existing linked table for this resource', 31);
  1036. return false;
  1037. }
  1038. else
  1039. {
  1040. $this->setErrorDidYouMean(400, 'This filter does not exist', $field, $available_filters, 32);
  1041. return false;
  1042. }
  1043. }
  1044. elseif ($url_param == '')
  1045. {
  1046. $this->setError(400, 'The filter "'.$field.'" is malformed.', 33);
  1047. return false;
  1048. }
  1049. else
  1050. {
  1051. if (isset($this->resourceConfiguration['fields'][$field]['getter']))
  1052. {
  1053. $this->setError(400, 'The field "'.$field.'" is dynamic. It is not possible to filter GET query with this field.', 34);
  1054. return false;
  1055. }
  1056. else
  1057. {
  1058. if (isset($this->resourceConfiguration['retrieveData']['tableAlias'])) {
  1059. $sql_filter .= $this->getSQLRetrieveFilter($this->resourceConfiguration['fields'][$field]['sqlId'], $url_param, $this->resourceConfiguration['retrieveData']['tableAlias'].'.');
  1060. } else {
  1061. $sql_filter .= $this->getSQLRetrieveFilter($this->resourceConfiguration['fields'][$field]['sqlId'], $url_param);
  1062. }
  1063. }
  1064. }
  1065. }
  1066. }
  1067. }
  1068. }
  1069. if (!$this->setFieldsToDisplay())
  1070. return false;
  1071. // construct SQL Sort
  1072. $sql_sort = '';
  1073. if (isset($this->urlFragments['sort']))
  1074. {
  1075. preg_match('#^\[(.*)\]$#Ui', $this->urlFragments['sort'], $matches);
  1076. if (count($matches) > 1)
  1077. $sorts = explode(',', $matches[1]);
  1078. else
  1079. $sorts = array($this->urlFragments['sort']);
  1080. $sql_sort .= ' ORDER BY ';
  1081. foreach ($sorts as $sort)
  1082. {
  1083. $delimiterPosition = strrpos($sort, '_');
  1084. if ($delimiterPosition !== false)
  1085. {
  1086. $fieldName = substr($sort, 0, $delimiterPosition);
  1087. $direction = strtoupper(substr($sort, $delimiterPosition + 1));
  1088. }
  1089. if ($delimiterPosition === false || !in_array($direction, array('ASC', 'DESC')))
  1090. {
  1091. $this->setError(400, 'The "sort" value has to be formed as this example: "field_ASC" or \'[field_1_DESC,field_2_ASC,field_3_ASC,...]\' ("field" has to be an available field)', 37);
  1092. return false;
  1093. }
  1094. elseif (!in_array($fieldName, $available_filters) && !in_array($fieldName, $i18n_available_filters))
  1095. {
  1096. $this->setError(400, 'Unable to filter by this field. However, these are available: '.implode(', ', $available_filters).', for i18n fields:'.implode(', ', $i18n_available_filters), 38);
  1097. return false;
  1098. }
  1099. // for sort on i18n field
  1100. elseif (in_array($fieldName, $i18n_available_filters))
  1101. {
  1102. if (!preg_match('#main_i18n#', $sql_join))
  1103. $sql_join .= 'LEFT JOIN `'._DB_PREFIX_.pSQL($this->resourceConfiguration['retrieveData']['table']).'_lang` AS main_i18n ON (main.`'.pSQL($this->resourceConfiguration['fields']['id']['sqlId']).'` = main_i18n.`'.pSQL($this->resourceConfiguration['fields']['id']['sqlId']).'`)'."\n";
  1104. $sql_sort .= 'main_i18n.`'.pSQL($this->resourceConfiguration['fields'][$fieldName]['sqlId']).'` '.$direction.', ';// ORDER BY main_i18n.`field` ASC|DESC
  1105. }
  1106. else
  1107. {
  1108. $object = new $this->resourceConfiguration['retrieveData']['className']();
  1109. $assoc = Shop::getAssoTable($this->resourceConfiguration['retrieveData']['table']);
  1110. if ($assoc !== false && $assoc['type'] == 'shop' && ($object->isMultiShopField($this->resourceConfiguration['fields'][$fieldName]['sqlId']) || $fieldName == 'id'))
  1111. $table_alias = 'multi_shop_'.$this->resourceConfiguration['retrieveData']['table'];
  1112. else
  1113. $table_alias = '';
  1114. $sql_sort .= (isset($this->resourceConfiguration['retrieveData']['tableAlias']) ? '`'.bqSQL($this->resourceConfiguration['retrieveData']['tableAlias']).'`.' : '`'.bqSQL($table_alias).'`.').'`'.pSQL($this->resourceConfiguration['fields'][$fieldName]['sqlId']).'` '.$direction.', ';// ORDER BY `field` ASC|DESC
  1115. }
  1116. }
  1117. $sql_sort = rtrim($sql_sort, ', ')."\n";
  1118. }
  1119. //construct SQL Limit
  1120. $sql_limit = '';
  1121. if (isset($this->urlFragments['limit']))
  1122. {
  1123. $limitArgs = explode(',', $this->urlFragments['limit']);
  1124. if (count($limitArgs) > 2)
  1125. {
  1126. $this->setError(400, 'The "limit" value has to be formed as this example: "5,25" or "10"', 39);
  1127. return false;
  1128. }
  1129. else
  1130. {
  1131. $sql_limit .= ' LIMIT '.(int)($limitArgs[0]).(isset($limitArgs[1]) ? ', '.(int)($limitArgs[1]) : '')."\n";// LIMIT X|X, Y
  1132. }
  1133. }
  1134. $filters['sql_join'] = $sql_join;
  1135. $filters['sql_filter'] = $sql_filter;
  1136. $filters['sql_sort'] = $sql_sort;
  1137. $filters['sql_limit'] = $sql_limit;
  1138. return $filters;
  1139. }
  1140. public function getFilteredObjectList()
  1141. {
  1142. $objects = array();
  1143. $filters = $this->manageFilters();
  1144. /* If we only need to display the synopsis, analyzing the first row is sufficient */
  1145. if (isset($this->urlFragments['schema']) && in_array($this->urlFragments['schema'], array('blank', 'synopsis')))
  1146. $filters = array('sql_join' => '', 'sql_filter' => '', 'sql_sort' => '', 'sql_limit' => ' LIMIT 1');
  1147. $this->resourceConfiguration['retrieveData']['params'][] = $filters['sql_join'];
  1148. $this->resourceConfiguration['retrieveData']['params'][] = $filters['sql_filter'];
  1149. $this->resourceConfiguration['retrieveData']['params'][] = $filters['sql_sort'];
  1150. $this->resourceConfiguration['retrieveData']['params'][] = $filters['sql_limit'];
  1151. //list entities
  1152. $tmp = new $this->resourceConfiguration['retrieveData']['className']();
  1153. $sqlObjects = call_user_func_array(array($tmp, $this->resourceConfiguration['retrieveData']['retrieveMethod']), $this->resourceConfiguration['retrieveData']['params']);
  1154. if ($sqlObjects)
  1155. {
  1156. foreach ($sqlObjects as $sqlObject)
  1157. {
  1158. if ($this->fieldsToDisplay == 'minimum')
  1159. {
  1160. $obj = new $this->resourceConfiguration['retrieveData']['className']();
  1161. $obj->id = (int)$sqlObject[$this->resourceConfiguration['fields']['id']['sqlId']];
  1162. $objects[] = $obj;
  1163. }
  1164. else
  1165. $objects[] = new $this->resourceConfiguration['retrieveData']['className']((int)$sqlObject[$this->resourceConfiguration['fields']['id']['sqlId']]);
  1166. }
  1167. return $objects;
  1168. }
  1169. }
  1170. public function getFilteredObjectDetails()
  1171. {
  1172. $objects = array();
  1173. if (!isset($this->urlFragments['display']))
  1174. $this->fieldsToDisplay = 'full';
  1175. //get entity details
  1176. $object = new $this->resourceConfiguration['retrieveData']['className']((int)$this->urlSegment[1]);
  1177. if ($object->id)
  1178. {
  1179. $objects[] = $object;
  1180. // Check if Object is accessible for this/those id_shop
  1181. $assoc = Shop::getAssoTable($this->resourceConfiguration['retrieveData']['table']);
  1182. if ($assoc !== false)
  1183. {
  1184. $check_shop_group = false;
  1185. $sql = 'SELECT 1
  1186. FROM `'.bqSQL(_DB_PREFIX_.$this->resourceConfiguration['retrieveData']['table']);
  1187. if ($assoc['type'] != 'fk_shop')
  1188. $sql .= '_'.$assoc['type'];
  1189. else
  1190. {
  1191. $def = ObjectModel::getDefinition($this->resourceConfiguration['retrieveData']['className']);
  1192. if (isset($def['fields']) && isset($def['fields']['id_shop_group']))
  1193. $check_shop_group = true;
  1194. }
  1195. $sql .= '`';
  1196. foreach (self::$shopIDs as $id_shop)
  1197. $OR[] = ' (id_shop = '.(int)$id_shop.($check_shop_group ? ' OR (id_shop = 0 AND id_shop_group='.(int)Shop::getGroupFromShop((int)$id_shop).')' : '').') ';
  1198. $check = ' WHERE ('.implode('OR', $OR).') AND `'.bqSQL($this->resourceConfiguration['fields']['id']['sqlId']).'` = '.(int)$this->urlSegment[1];
  1199. if (!Db::getInstance()->getValue($sql.$check))
  1200. $this->setError(404, 'This '.$this->resourceConfiguration['retrieveData']['className'].' ('.(int)$this->urlSegment[1].') does not exists on this shop', 131);
  1201. }
  1202. return $objects;
  1203. }
  1204. if (!count($this->errors))
  1205. {
  1206. $this->objOutput->setStatus(404);
  1207. $this->_outputEnabled = false;
  1208. return false;
  1209. }
  1210. }
  1211. /**
  1212. * Execute GET and HEAD requests
  1213. *
  1214. * Build filter
  1215. * Build fields display
  1216. * Build sort
  1217. * Build limit
  1218. *
  1219. * @return boolean
  1220. */
  1221. public function executeEntityGetAndHead()
  1222. {
  1223. if ($this->resourceConfiguration['objectsNodeName'] != 'resources')
  1224. {
  1225. if (!isset($this->urlSegment[1]) || !strlen($this->urlSegment[1]))
  1226. $return = $this->getFilteredObjectList();
  1227. else
  1228. $return = $this->getFilteredObjectDetails();
  1229. if (!$return)
  1230. return false;
  1231. else
  1232. $this->objects = $return;
  1233. }
  1234. return true;
  1235. }
  1236. /**
  1237. * Execute POST method on a PrestaShop entity
  1238. *
  1239. * @return boolean
  1240. */
  1241. protected function executeEntityPost()
  1242. {
  1243. return $this->saveEntityFromXml(201);
  1244. }
  1245. /**
  1246. * Execute PUT method on a PrestaShop entity
  1247. *
  1248. * @return boolean
  1249. */
  1250. protected function executeEntityPut()
  1251. {
  1252. return $this->saveEntityFromXml(200);
  1253. }
  1254. /**
  1255. * Execute DELETE method on a PrestaShop entity
  1256. *
  1257. * @return boolean
  1258. */
  1259. protected function executeEntityDelete()
  1260. {
  1261. $objects = array();
  1262. $arr_avoid_id = array();
  1263. $ids = array();
  1264. if (isset($this->urlFragments['id']))
  1265. {
  1266. preg_match('#^\[(.*)\]$#Ui', $this->urlFragments['id'], $matches);
  1267. if (count($matches) > 1)
  1268. $ids = explode(',', $matches[1]);
  1269. }
  1270. else
  1271. {
  1272. $ids[] = (int)$this->urlSegment[1];
  1273. }
  1274. if (!empty($ids))
  1275. {
  1276. foreach ($ids as $id)
  1277. {
  1278. $object = new $this->resourceConfiguration['retrieveData']['className']((int)$id);
  1279. if (!$object->id)
  1280. $arr_avoid_id[] = $id;
  1281. else
  1282. $objects[] = $object;
  1283. }
  1284. }
  1285. if (!empty($arr_avoid_id) || empty($ids))
  1286. {
  1287. $this->setError(404, 'Id(s) not exists: '.implode(', ', $arr_avoid_id), 87);
  1288. $this->_outputEnabled = true;
  1289. }
  1290. else
  1291. {
  1292. foreach ($objects as $object)
  1293. {
  1294. if (isset($this->resourceConfiguration['ob…

Large files files are truncated, but you can click here to view the full file