PageRenderTime 104ms CodeModel.GetById 29ms app.highlight 61ms RepoModel.GetById 1ms app.codeStats 1ms

/classes/webservice/WebserviceRequest.php

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