diff --git a/LICENSE b/LICENSE
index 55bfcd4..49e2a52 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2016 retailCRM
+Copyright (c) 2016 RetailDriver LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ad59ceb
--- /dev/null
+++ b/README.md
@@ -0,0 +1,35 @@
+UMI.CMS module
+==============
+
+Module allows integrate UMI.CMS with [RetailCRM](http://www.retailcrm.pro)
+
+#### Features:
+
+* Export orders to retailCRM & fetch changes back
+* Export product catalog into [ICML](http://www.retailcrm.pro/docs/Developers/ICML) format
+
+#### Setup
+
+* Copy directories "classes" & "images" into document root
+* Go to /admin/config/modules
+* Into "Modules" tab fill installation script path (classes/modules/RetailCRM/install.php)
+* Press setup button
+* Go to module page
+* Fill you api url & api key
+* Specify directories matching
+
+#### Setting product catalog export
+
+Add to cron:
+
+```
+* */4 * * * /usr/bin/php /path_to_site/public_html/cron.php RetailCRM icml
+```
+
+#### Getting changes in orders
+
+Add to cron:
+
+```
+*/7 * * * * /usr/bin/php /path_to_site/public_html/cron.php RetailCRM history
+```
diff --git a/README.ru.md b/README.ru.md
new file mode 100644
index 0000000..be9232b
--- /dev/null
+++ b/README.ru.md
@@ -0,0 +1,33 @@
+UMI.CMS module
+==============
+
+Модуль интеграции UMI.CMS c [RetailCRM](http://www.retailcrm.ru)
+
+#### Модуль позволяет:
+
+* Экспортировать в CRM данные о заказах и клиентах и получать обратно изменения по этим данным
+* Синхронизировать справочники (способы доставки и оплаты, статусы заказов и т.п.)
+* Выгружать каталог товаров в формате [ICML](http://retailcrm.ru/docs/Разработчики/ФорматICML) (IntaroCRM Markup Language)
+
+#### Настройка
+
+* Скопируйте директории classes и images в корень сайта
+* В разделе управления модулями (/admin/config/modules) укажите путь до инсталяционного файла: classes/modules/RetailCRM/install.php
+* На странице настроек модуля введите API url и API ключ, после этого установите соответствие справочников
+
+#### Выгрузка каталога
+
+Добавьте в крон запись вида
+
+```
+* */4 * * * /usr/bin/php /path_to_site/public_html/cron.php RetailCRM icml
+```
+
+#### Получение изменение из RetailCRM
+
+Добавьте в крон запись вида
+
+```
+*/7 * * * * /usr/bin/php /path_to_site/public_html/cron.php RetailCRM history
+```
+
diff --git a/classes/modules/RetailCRM/__admin.php b/classes/modules/RetailCRM/__admin.php
new file mode 100644
index 0000000..a35a6c7
--- /dev/null
+++ b/classes/modules/RetailCRM/__admin.php
@@ -0,0 +1,241 @@
+get('retailcrm', 'crmUrl');
+ $apiKey = (string)$config->get('retailcrm', 'apiKey');
+
+ $params = array(
+ 'access' => array(
+ 'string:crmUrl' => $crmUrl,
+ 'string:apiKey' => $apiKey
+ )
+ );
+
+ // Костыль для локализации полей
+ $translations = array();
+
+ if (!empty($apiKey) && !empty($crmUrl)) {
+ $api = new RCrmProxy(
+ $config->get('retailcrm', 'crmUrl'),
+ $config->get('retailcrm', 'apiKey'),
+ __DIR__ . '/../../../retailcrm.error.log'
+ );
+
+ if($api->paymentTypesList() !== false) {
+ /*
+ * Order Payment Types
+ */
+ /** @var RCrmApiClient $api */
+ $crmPaymentTypes = $api->paymentTypesList()->getPaymentTypes();
+ $umiPaymentTypes = new selector('objects');
+ $umiPaymentTypes->types('object-type')->name('emarket', 'payment');
+
+ $map = $this->getRelationMap($config->get('retailcrm', 'orderPaymentTypeMap'));
+
+ $orderPaymentsMapping = array();
+ foreach ($umiPaymentTypes->result() as $umiPaymentType) {
+ $umiPaymentTypeId = $umiPaymentType->getPropByName('payment_type_id')->getObjectId();
+ $umiPaymentTypeName = $umiPaymentType->getName();
+
+ $translations['order-payment-type-' . $umiPaymentTypeId] = $umiPaymentTypeName;
+
+ $orderPaymentsMapping['select:order-payment-type-' . $umiPaymentTypeId] = array();
+ $orderPaymentsMapping['select:order-payment-type-' . $umiPaymentTypeId]['value'] = $this->getRelationByMap($map,
+ $umiPaymentTypeId);
+
+ $orderPaymentsMapping['select:order-payment-type-' . $umiPaymentTypeId]['none'] = '';
+ foreach ($crmPaymentTypes as $crmPaymentType) {
+ $orderPaymentsMapping['select:order-payment-type-' . $umiPaymentTypeId][$crmPaymentType['code']] = $crmPaymentType['name'];
+ }
+ }
+ $params['orderPaymentsMapping'] = $orderPaymentsMapping;
+
+ /*
+ * Order Delivery Type
+ */
+ $crmDeliveryTypes = $api->deliveryTypesList()->getDeliveryTypes();
+ $umiDeliveryTypes = new selector('objects');
+ $umiDeliveryTypes->types('object-type')->name('emarket', 'delivery');
+
+ $map = $this->getRelationMap($config->get('retailcrm', 'orderDeliveryTypeMap'));
+
+ $orderDeliveryTypesMapping = array();
+ foreach ($umiDeliveryTypes as $umiDeliveryType) {
+ $umiDeliveryTypeId = $umiDeliveryType->getId();
+ $umiDeliveryTypeName = $umiDeliveryType->getName();
+
+ $translations['order-delivery-type-' . $umiDeliveryTypeId] = $umiDeliveryTypeName;
+
+ $orderDeliveryTypesMapping['select:order-delivery-type-' . $umiDeliveryTypeId] = array();
+ $orderDeliveryTypesMapping['select:order-delivery-type-' . $umiDeliveryTypeId]['value'] = $this->getRelationByMap($map,
+ $umiDeliveryTypeId);
+
+ $orderDeliveryTypesMapping['select:order-delivery-type-' . $umiDeliveryTypeId]['none'] = '';
+ foreach ($crmDeliveryTypes as $crmDeliveryType) {
+ $orderDeliveryTypesMapping['select:order-delivery-type-' . $umiDeliveryTypeId][$crmDeliveryType['code']] = $crmDeliveryType['name'];
+ }
+
+ }
+ $params['orderDeliveryTypesMapping'] = $orderDeliveryTypesMapping;
+
+ /*
+ * Order Payment Statuses
+ */
+ $crmPaymentStatuses = $api->paymentStatusesList()->getPaymentStatuses();
+ $umiPaymentStatuses = new selector('objects');
+ $umiPaymentStatuses->types('object-type')->name('emarket', 'order_payment_status');
+
+ $map = $this->getRelationMap($config->get('retailcrm', 'orderPaymentStatusMap'));
+
+ $orderPaymentStatusesMapping = array();
+ foreach ($umiPaymentStatuses->result() as $umiPaymentStatus) {
+ $umiPaymentStatusId = $umiPaymentStatus->getId();
+ $umiPaymentStatusName = $umiPaymentStatus->getName();
+
+ $translations['order-payment-status-' . $umiPaymentStatusId] = $umiPaymentStatusName;
+
+ $orderPaymentStatusesMapping['select:order-payment-status-' . $umiPaymentStatusId] = array();
+ $orderPaymentStatusesMapping['select:order-payment-status-' . $umiPaymentStatusId]['value'] = $this->getRelationByMap($map,
+ $umiPaymentStatusId);
+
+ $orderPaymentStatusesMapping['select:order-payment-status-' . $umiPaymentStatusId]['none'] = '';
+ foreach ($crmPaymentStatuses as $crmPaymentStatus) {
+ $orderPaymentStatusesMapping['select:order-payment-status-' . $umiPaymentStatusId][$crmPaymentStatus['code']] = $crmPaymentStatus['name'];
+ }
+ }
+ $params['orderPaymentStatusesMapping'] = $orderPaymentStatusesMapping;
+
+ /*
+ * Order Statuses
+ */
+ $crmOrderStatuses = $api->statusesList()->getStatuses();
+ $umiOrderStatuses = new selector('objects');
+ $umiOrderStatuses->types('object-type')->name('emarket', 'order_status');
+
+ $map = $this->getRelationMap($config->get('retailcrm', 'orderStatusMap'));
+
+ $params['orderStatusesMapping'] = array();
+ foreach ($umiOrderStatuses->result() as $umiOrderStatus) {
+ $translations['order-status-' . $umiOrderStatus->getPropByName('codename')->getValue()] = $umiOrderStatus->getName();
+
+ $params['orderStatusesMapping']['select:order-status-' . $umiOrderStatus->getPropByName('codename')->getValue()] = array();
+ $params['orderStatusesMapping']['select:order-status-' . $umiOrderStatus->getPropByName('codename')->getValue()]['value'] = $this->getRelationByMap($map,
+ $umiOrderStatus->getPropByName('codename')->getValue());
+
+ $params['orderStatusesMapping']['select:order-status-' . $umiOrderStatus->getPropByName('codename')->getValue()]['none'] = '';
+ foreach ($crmOrderStatuses as $crmOrderStatus) {
+ $params['orderStatusesMapping']['select:order-status-' . $umiOrderStatus->getPropByName('codename')->getValue()][$crmOrderStatus['code']] = $crmOrderStatus['name'];
+ }
+ }
+
+ $params['guidesMapping']['select:country'] = array();
+ $params['guidesMapping']['select:country']['value'] = $config->get('retailcrm', 'countryGuideId');
+ $params['guidesMapping']['select:country']['none'] = '';
+ $objectTypes = umiObjectTypesCollection::getInstance();
+ foreach ($objectTypes->getGuidesList() as $guideId => $guideName) {
+ $params['guidesMapping']['select:country'][$guideId] = $guideName;
+ }
+ } else {
+ // TODO: Добавить вывод ошибки, что данные некорректные
+ $params['incorrect-data'] = array();
+ }
+ }
+
+ $mode = getRequest("param0");
+ if ($mode == "do") {
+ $params = $this->expectParams($params);
+
+ $config->set('retailcrm', 'crmUrl', $params['access']['string:crmUrl']);
+ $config->set('retailcrm', 'apiKey', $params['access']['string:apiKey']);
+
+ if (!empty($params['access']['string:crmUrl']) && !empty($params['access']['string:apiKey'])) {
+ /*
+ * Order Statuses
+ */
+ if (!empty($params['orderStatusesMapping'])) {
+ $orderStatusMap = array();
+ foreach ($params['orderStatusesMapping'] as $umiOrderStatus => $crmOrderStatus) {
+ $umiOrderStatus = str_replace('select:order-status-', '', $umiOrderStatus);
+ $orderStatusMap[] = $umiOrderStatus . ' <-> ' . $crmOrderStatus;
+ }
+ $config->set('retailcrm', 'orderStatusMap', $orderStatusMap);
+ }
+
+ /*
+ * Order Payment Types
+ */
+ if (!empty($params['orderPaymentsMapping'])) {
+ $orderPaymentTypeMap = array();
+ foreach ($params['orderPaymentsMapping'] as $umiOrderPaymentType => $crmOrderPaymentType) {
+ $umiOrderPaymentType = str_replace('select:order-payment-type-', '', $umiOrderPaymentType);
+ $orderPaymentTypeMap[] = $umiOrderPaymentType . ' <-> ' . $crmOrderPaymentType;
+ }
+ $config->set('retailcrm', 'orderPaymentTypeMap', $orderPaymentTypeMap);
+ }
+
+ /*
+ * Order Payment Statuses
+ */
+ if (!empty($params['orderPaymentStatusesMapping'])) {
+ $orderPaymentStatusMap = array();
+ foreach ($params['orderPaymentStatusesMapping'] as $umiOrderPaymentStatus => $crmOrderPaymentStatus) {
+ $umiOrderPaymentStatus = str_replace('select:order-payment-status-', '', $umiOrderPaymentStatus);
+ $orderPaymentStatusMap[] = $umiOrderPaymentStatus . ' <-> ' . $crmOrderPaymentStatus;
+ }
+ $config->set('retailcrm', 'orderPaymentStatusMap', $orderPaymentStatusMap);
+ }
+
+ /*
+ * Order Delivery Types
+ */
+ if (!empty($params['orderDeliveryTypesMapping'])) {
+ $orderDeliveryTypeMap = array();
+ foreach ($params['orderDeliveryTypesMapping'] as $umiOrderDeliveryType => $crmOrderDeliveryType) {
+ $umiOrderDeliveryType = str_replace('select:order-delivery-type-', '', $umiOrderDeliveryType);
+ $orderDeliveryTypeMap[] = $umiOrderDeliveryType . ' <-> ' . $crmOrderDeliveryType;
+ }
+ $config->set('retailcrm', 'orderDeliveryTypeMap', $orderDeliveryTypeMap);
+ }
+
+ if (!empty($params['guidesMapping'])) {
+ foreach ($params['guidesMapping'] as $guideName => $guideId) {
+ $guideName = str_replace('select:', '', $guideName);
+ $config->set('retailcrm', $guideName . 'GuideId', $guideId);
+ }
+ }
+ }
+
+ $this->chooseRedirect();
+ }
+
+ $this->setDataType("settings");
+ $this->setActionType("modify");
+
+ $data = $this->prepareData($params, "settings");
+
+ // Реалзиация костыля для локализации полей
+ foreach ($data['nodes:group'] as $groupKey => $group) {
+ foreach ($group['nodes:option'] as $optionKey => $option) {
+ $label = $option['attribute:label'];
+
+ if (strpos($label, 'option-') > -1) {
+ $optionName = str_replace('option-', '', $label);
+
+ if (isset($translations[$optionName])) {
+ $data['nodes:group'][$groupKey]['nodes:option'][$optionKey]['attribute:label'] = $translations[$optionName];
+ }
+ }
+
+ }
+ }
+
+ $this->setData($data);
+ $this->doData();
+ }
+}
\ No newline at end of file
diff --git a/classes/modules/RetailCRM/__events.php b/classes/modules/RetailCRM/__events.php
new file mode 100644
index 0000000..1cc3956
--- /dev/null
+++ b/classes/modules/RetailCRM/__events.php
@@ -0,0 +1,57 @@
+generateICML();
+ }
+
+ public function onCronSyncHistory()
+ {
+ $history = new RCrmHistory();
+ $history->runCustomers();
+ $history->runOrders();
+ }
+
+ public function onOrderStatusChanged(umiEventPoint $eventPoint)
+ {
+ if ($eventPoint->getMode() != 'after') {
+ return;
+ }
+
+ $mode = $eventPoint->getParam('old-status-id') == null ? 'create' : 'edit';
+
+ /** @var order $order */
+ $order = $eventPoint->getRef('order');
+
+ RCrmActions::orderSend($order->getId(), $mode);
+ }
+
+ public function onModifyProperty(umiEventPoint $eventPoint)
+ {
+ /** @var umiEventPoint $eventPoint */
+ if ($eventPoint->getMode() != 'after') {
+ return;
+ }
+
+ /** @var umiObject $entity */
+ $entity = $eventPoint->getRef('entity');
+
+ RCrmActions::orderSend($entity->getId(), 'edit');
+ }
+
+ public function onModifyObject(umiEventPoint $eventPoint)
+ {
+ /** @var umiEventPoint $eventPoint */
+ if ($eventPoint->getMode() != 'after') {
+ return;
+ }
+
+ /** @var umiObject $object */
+ $object = $eventPoint->getRef('object');
+
+ RCrmActions::orderSend($object->getId(), 'edit');
+ }
+}
\ No newline at end of file
diff --git a/classes/modules/RetailCRM/class.php b/classes/modules/RetailCRM/class.php
new file mode 100644
index 0000000..66f0636
--- /dev/null
+++ b/classes/modules/RetailCRM/class.php
@@ -0,0 +1,35 @@
+getCurrentMode() == "admin") {
+ $this->__loadLib("__admin.php");
+ $this->__implement("__RetailCRM_adm");
+ }
+
+ $this->__loadLib("../emarket/includes.php");
+
+ // RetailCRM classes
+ $this->__loadLib("classes/retailcrm/RCrmActions.php");
+ $this->__loadLib("classes/retailcrm/RCrmApiClient.php");
+ $this->__loadLib("classes/retailcrm/RCrmApiResponse.php");
+ $this->__loadLib("classes/retailcrm/RCrmHistory.php");
+ $this->__loadLib("classes/retailcrm/RCrmHttpClient.php");
+ $this->__loadLib("classes/retailcrm/RCrmIcml.php");
+ $this->__loadLib("classes/retailcrm/RCrmProxy.php");
+
+ // Exceptions
+ $this->__loadLib("classes/retailcrm/RCrmCurlException.php");
+ $this->__loadLib("classes/retailcrm/RCrmJsonException.php");
+
+ // Helpers
+ $this->__loadLib("classes/retailcrm/RCrmHelpers.php");
+ $this->__implement("RCrmHelpers");
+
+ // Events
+ $this->__loadLib("__events.php");
+ $this->__implement("__RetailCRM_events");
+ }
+}
\ No newline at end of file
diff --git a/classes/modules/RetailCRM/classes/retailcrm/RCrmActions.php b/classes/modules/RetailCRM/classes/retailcrm/RCrmActions.php
new file mode 100644
index 0000000..1b7f6f5
--- /dev/null
+++ b/classes/modules/RetailCRM/classes/retailcrm/RCrmActions.php
@@ -0,0 +1,265 @@
+getVal('//modules/RetailCRM/IgnoreObjectUpdateEvent/' . $order->getObject()->getId());
+ if ($time == $order->getObject()->getUpdateTime() OR $time + 1 == $order->getObject()->getUpdateTime()) {
+ return;
+ }
+
+ $config = mainConfiguration::getInstance();
+
+ $umiOrderStatusCode = order::getCodeByStatus($order->getOrderStatus());
+
+ $retailcrm = new RetailCRM;
+
+ $relationMap = $retailcrm->getRelationMap($config->get('retailcrm', 'orderStatusMap'));
+ $crmOrderStatusCode = $retailcrm->getRelationByMap($relationMap, $umiOrderStatusCode);
+
+ if (!$crmOrderStatusCode) {
+ return;
+ }
+
+ $umiOrderPaymentType = $order->getObject()->getValue('payment_id');
+ $relationOrderPaymentTypesMap = $retailcrm->getRelationMap($config->get('retailcrm', 'orderPaymentTypeMap'));
+ $crmOrderPaymentType = $retailcrm->getRelationByMap($relationOrderPaymentTypesMap, $umiOrderPaymentType);
+
+ $umiOrderPaymentStatus = $order->getObject()->getValue('payment_status_id');
+ $relationOrderPaymentStatusesMap = $retailcrm->getRelationMap($config->get('retailcrm', 'orderPaymentStatusMap'));
+ $crmOrderPaymentStatus = $retailcrm->getRelationByMap($relationOrderPaymentStatusesMap, $umiOrderPaymentStatus);
+
+ $umiOrderDeliveryId = $order->getObject()->getValue('delivery_id');
+ $relationOrderDeliveryTypesMap = $retailcrm->getRelationMap($config->get('retailcrm', 'orderDeliveryTypeMap'));
+ $crmOrderDeliveryType = $retailcrm->getRelationByMap($relationOrderDeliveryTypesMap, $umiOrderDeliveryId);
+
+ $customer = customer::get($order->getCustomerId());
+ $orderItems = $order->getItems();
+
+ $orderItemsToCrm = array();
+
+ foreach ($orderItems as $orderItem) {
+ /** @var optionedOrderItem $orderItem */
+
+ $itemProperties = array();
+ foreach ($orderItem->getOptions() as $option) {
+ $option = new umiObject($option['option-id']);
+ $itemProperties[] = array(
+ 'name' => $option->getType()->getName(),
+ 'value' => $option->getName()
+ );
+ }
+
+ $optionGroups = $orderItem->getItemElement()->getObject()->getType()->getFieldsGroupByName('catalog_option_props')->getFields();
+ $optionGuidesToGroups = array();
+ foreach ($optionGroups as $optionGroup) {
+ /** @var umiField $optionGroup */
+ $optionGuidesToGroups[$optionGroup->getGuideId()] = $optionGroup->getId();
+ }
+
+ $options = array();
+ foreach ($orderItem->getOptions() as $option) {
+ $option = $objects->getObject($option['option-id']);
+ $options[] = $optionGuidesToGroups[$option->getTypeId()] . '_' . $option->getId();
+ }
+
+ $product = $orderItem->getItemElement();
+
+ if (!empty($options)) {
+ $productId = $product->getId() . '#' . implode('-', $options);
+ } else {
+ $productId = $product->getId();
+ }
+
+ $orderItemsToCrm[] = array(
+ 'initialPrice' => $orderItem->getItemPrice(),
+ 'discount' => $orderItem->getDiscount(),
+ 'quantity' => $orderItem->getAmount(),
+ 'productName' => $product->getName(),
+ 'properties' => $itemProperties,
+ 'offer' => array(
+ 'externalId' => $productId
+ )
+ );
+ }
+
+ /* One click order */
+ if ($order->getObject()->getValue('purchaser_one_click') !== null) {
+ $oneClickObj = new umiObject($order->getObject()->getValue('purchaser_one_click'));
+
+ $orderToCrm = array(
+ 'number' => $order->getObject()->getName(),
+ 'externalId' => $order->getId(),
+ 'phone' => $oneClickObj->getValue('phone'),
+ 'customer' => array(
+ 'externalId' => $customer->getId()
+ ),
+ 'paymentType' => $crmOrderPaymentType,
+ 'paymentStatus' => $crmOrderPaymentStatus,
+ 'status' => $crmOrderStatusCode,
+ 'items' => $orderItemsToCrm,
+ 'orderMethod' => 'one-click'
+ );
+ } else {
+ if ($order->getObject()->getValue('delivery_address') !== null) {
+ $deliveryObjId = $order->getObject()->getValue('delivery_address');
+ $deliveryObj = new umiObject($deliveryObjId);
+
+ if ($deliveryObj->getValue('country') !== false) {
+ $deliveryCountryObjId = $deliveryObj->getValue('country');
+ $deliveryCountryObj = new umiObject($deliveryCountryObjId);
+ $deliveryCountryIsoCode = $deliveryCountryObj->getValue('country_iso_code');
+ } else {
+ $deliveryCountryIsoCode = '';
+ }
+
+ $deliveryIndex = $deliveryObj->getValue('index');
+ $deliveryStreet = $deliveryObj->getValue('street');
+ $deliveryBuilding = $deliveryObj->getValue('house');
+ $deliveryHouse = $deliveryObj->getValue('house');
+ $deliveryFlat = $deliveryObj->getValue('flat');
+ $deliveryNotes = $deliveryObj->getValue('order_comments');
+
+ if ($deliveryObj->getValue('region') !== null) {
+ $deliveryRegionObj = new umiObject($deliveryObj->getValue('region'));
+ $deliveryRegion = $deliveryRegionObj->getName();
+ } else {
+ $deliveryRegion = '';
+ }
+
+ if ($deliveryObj->getValue('city') !== null) {
+ $deliveryCityObj = new umiObject($deliveryObj->getValue('city'));
+ $deliveryCity = $deliveryCityObj->getName();
+ } else {
+ $deliveryCity = '';
+ }
+
+ $addressToCrm = array(
+ 'countryIso' => $deliveryCountryIsoCode,
+ 'index' => $deliveryIndex,
+ 'region' => $deliveryRegion,
+ 'city' => $deliveryCity,
+ 'street' => $deliveryStreet,
+ 'building' => $deliveryBuilding,
+ 'flat' => $deliveryFlat,
+ 'house' => $deliveryHouse,
+ 'notes' => $deliveryNotes
+ );
+
+ } else {
+ $addressToCrm = array();
+ }
+
+ $orderToCrm = array(
+ 'number' => $order->getObject()->getName(),
+ 'externalId' => $order->getId(),
+ 'lastName' => $customer->getValue('lname'),
+ 'firstName' => $customer->getValue('fname'),
+ 'patronymic' => $customer->getValue('father_name'),
+ 'phone' => $customer->getValue('phone'),
+ 'email' => $customer->getValue('email'),
+ 'customer' => array(
+ 'externalId' => $customer->getId()
+ ),
+ 'paymentType' => $crmOrderPaymentType,
+ 'paymentStatus' => $crmOrderPaymentStatus,
+ 'status' => $crmOrderStatusCode,
+ 'items' => $orderItemsToCrm,
+ 'delivery' => array(
+ 'address' => $addressToCrm,
+ 'code' => $crmOrderDeliveryType
+ )
+ );
+ }
+
+ // TODO: есть возможность учитывать домен
+ $api = new RCrmProxy(
+ $config->get('retailcrm', 'crmUrl'),
+ $config->get('retailcrm', 'apiKey'),
+ __DIR__ . '/../../../retailcrm.error.log'
+ );
+
+ if ($mode == 'create') {
+ $orderToCrm = self::customerPrepare($orderToCrm);
+ $api->ordersCreate($orderToCrm);
+ } else if ($mode == 'edit') {
+ $api->ordersEdit($orderToCrm);
+ }
+ }
+
+ public static function customerPrepare($orderToCrm) {
+ $config = mainConfiguration::getInstance();
+
+ $api = new RCrmProxy(
+ $config->get('retailcrm', 'crmUrl'),
+ $config->get('retailcrm', 'apiKey'),
+ __DIR__ . '/../../../retailcrm.error.log'
+ );
+
+ $crmCustomer = $api->customersGet($orderToCrm['customer']['externalId']);
+
+ if (!$crmCustomer) {
+ $crmCustomers = $api->customersList(array(
+ 'name' => $orderToCrm['phone'],
+ 'email' => $orderToCrm['email']
+ ));
+
+ $foundedCustomerExternalId = false;
+ if ($crmCustomers) {
+ /** @var RCrmApiResponse $crmCustomers */
+ $crmCustomers = $crmCustomers->getCustomers();
+
+ if (count($crmCustomers) > 0) {
+ foreach ($crmCustomers as $crmCustomer) {
+ if (isset($crmCustomer['externalId']) && $crmCustomer['externalId'] > 0) {
+ $foundedCustomerExternalId = true;
+ $orderToCrm['customer']['externalId'] = $crmCustomer['externalId'];
+ break;
+ }
+ }
+
+ if (!$foundedCustomerExternalId) {
+ $crmCustomer = $crmCustomers[0];
+ $status = $api->customersFixExternalIds(array(
+ 'id' => $crmCustomer['id'],
+ 'externalId' => $crmCustomer['externalId']
+ ));
+
+ if (!$status) {
+ unset($orderToCrm['customer']);
+ }
+ }
+ } else {
+ $status = $api->customersCreate(array(
+ 'externalId' => $orderToCrm['customer']['externalId'],
+ 'firstName' => $orderToCrm['firstName'],
+ 'lastName' => $orderToCrm['lastName'],
+ 'patronymic' => $orderToCrm['patronymic'],
+ 'email' => $orderToCrm['email'],
+ 'phones' => array(
+ 'number' => $orderToCrm['phone']
+ )
+ ));
+
+ if (!$status) {
+ unset($orderToCrm['customer']);
+ }
+ }
+ } else {
+ unset($orderToCrm['customer']);
+ }
+ }
+
+ return $orderToCrm;
+ }
+}
\ No newline at end of file
diff --git a/classes/modules/RetailCRM/classes/retailcrm/RCrmApiClient.php b/classes/modules/RetailCRM/classes/retailcrm/RCrmApiClient.php
new file mode 100644
index 0000000..d35de61
--- /dev/null
+++ b/classes/modules/RetailCRM/classes/retailcrm/RCrmApiClient.php
@@ -0,0 +1,1825 @@
+
+ * @license https://opensource.org/licenses/MIT MIT License
+ * @link http://www.retailcrm.ru/docs/Developers/ApiVersion4
+ */
+class RCrmApiClient
+{
+
+ const VERSION = 'v4';
+
+ protected $client;
+
+ /**
+ * Site code
+ */
+ protected $siteCode;
+
+ /**
+ * Client creating
+ *
+ * @param string $url api url
+ * @param string $apiKey api key
+ * @param string $site site code
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct($url, $apiKey, $site = null)
+ {
+ if ('/' !== $url[strlen($url) - 1]) {
+ $url .= '/';
+ }
+
+ $url = $url . 'api/' . self::VERSION;
+
+ $this->client = new RCrmHttpClient($url, array('apiKey' => $apiKey));
+ $this->siteCode = $site;
+ }
+
+ /**
+ * Returns users list
+ *
+ * @param array $filter
+ * @param null $page
+ * @param null $limit
+ *
+ * @throws RCrmJsonException
+ * @throws RCrmCurlException
+ * @throws InvalidArgumentException
+ *
+ * @return RCrmApiResponse
+ */
+ public function usersList(array $filter = array(), $page = null, $limit = null)
+ {
+ $parameters = array();
+
+ if (count($filter)) {
+ $parameters['filter'] = $filter;
+ }
+ if (null !== $page) {
+ $parameters['page'] = (int) $page;
+ }
+ if (null !== $limit) {
+ $parameters['limit'] = (int) $limit;
+ }
+
+ return $this->client->makeRequest(
+ '/users',
+ RCrmHttpClient::METHOD_GET,
+ $parameters
+ );
+ }
+
+ /**
+ * Get user groups
+ *
+ * @param null $page
+ * @param null $limit
+ *
+ * @throws RCrmJsonException
+ * @throws RCrmCurlException
+ *
+ * @return RCrmApiResponse
+ */
+ public function usersGroups($page = null, $limit = null)
+ {
+ $parameters = array();
+
+ if (null !== $page) {
+ $parameters['page'] = (int) $page;
+ }
+ if (null !== $limit) {
+ $parameters['limit'] = (int) $limit;
+ }
+
+ return $this->client->makeRequest(
+ '/user-groups',
+ RCrmHttpClient::METHOD_GET,
+ $parameters
+ );
+ }
+
+ /**
+ * Returns user data
+ *
+ * @param integer $id user ID
+ *
+ * @throws RCrmJsonException
+ * @throws RCrmCurlException
+ * @throws InvalidArgumentException
+ *
+ * @return RCrmApiResponse
+ */
+ public function usersGet($id)
+ {
+ return $this->client->makeRequest("/users/$id", RCrmHttpClient::METHOD_GET);
+ }
+
+ /**
+ * Returns filtered orders list
+ *
+ * @param array $filter (default: array())
+ * @param int $page (default: null)
+ * @param int $limit (default: null)
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function ordersList(array $filter = array(), $page = null, $limit = null)
+ {
+ $parameters = array();
+
+ if (count($filter)) {
+ $parameters['filter'] = $filter;
+ }
+ if (null !== $page) {
+ $parameters['page'] = (int) $page;
+ }
+ if (null !== $limit) {
+ $parameters['limit'] = (int) $limit;
+ }
+
+ return $this->client->makeRequest(
+ '/orders',
+ RCrmHttpClient::METHOD_GET,
+ $parameters
+ );
+ }
+
+ /**
+ * Create a order
+ *
+ * @param array $order order data
+ * @param string $site (default: null)
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function ordersCreate(array $order, $site = null)
+ {
+ if (!count($order)) {
+ throw new InvalidArgumentException(
+ 'Parameter `order` must contains a data'
+ );
+ }
+
+ return $this->client->makeRequest(
+ '/orders/create',
+ RCrmHttpClient::METHOD_POST,
+ $this->fillSite($site, array('order' => json_encode($order)))
+ );
+ }
+
+ /**
+ * Save order IDs' (id and externalId) association in the CRM
+ *
+ * @param array $ids order identificators
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function ordersFixExternalIds(array $ids)
+ {
+ if (! count($ids)) {
+ throw new InvalidArgumentException(
+ 'Method parameter must contains at least one IDs pair'
+ );
+ }
+
+ return $this->client->makeRequest(
+ '/orders/fix-external-ids',
+ RCrmHttpClient::METHOD_POST,
+ array('orders' => json_encode($ids)
+ )
+ );
+ }
+
+ /**
+ * Returns statuses of the orders
+ *
+ * @param array $ids (default: array())
+ * @param array $externalIds (default: array())
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function ordersStatuses(array $ids = array(), array $externalIds = array())
+ {
+ $parameters = array();
+
+ if (count($ids)) {
+ $parameters['ids'] = $ids;
+ }
+ if (count($externalIds)) {
+ $parameters['externalIds'] = $externalIds;
+ }
+
+ return $this->client->makeRequest(
+ '/orders/statuses',
+ RCrmHttpClient::METHOD_GET,
+ $parameters
+ );
+ }
+
+ /**
+ * Upload array of the orders
+ *
+ * @param array $orders array of orders
+ * @param string $site (default: null)
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function ordersUpload(array $orders, $site = null)
+ {
+ if (!count($orders)) {
+ throw new InvalidArgumentException(
+ 'Parameter `orders` must contains array of the orders'
+ );
+ }
+
+ return $this->client->makeRequest(
+ '/orders/upload',
+ RCrmHttpClient::METHOD_POST,
+ $this->fillSite($site, array('orders' => json_encode($orders)))
+ );
+ }
+
+ /**
+ * Get order by id or externalId
+ *
+ * @param string $id order identificator
+ * @param string $by (default: 'externalId')
+ * @param string $site (default: null)
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function ordersGet($id, $by = 'externalId', $site = null)
+ {
+ $this->checkIdParameter($by);
+
+ return $this->client->makeRequest(
+ "/orders/$id",
+ RCrmHttpClient::METHOD_GET,
+ $this->fillSite($site, array('by' => $by))
+ );
+ }
+
+ /**
+ * Edit a order
+ *
+ * @param array $order order data
+ * @param string $by (default: 'externalId')
+ * @param string $site (default: null)
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function ordersEdit(array $order, $by = 'externalId', $site = null)
+ {
+ if (!count($order)) {
+ throw new InvalidArgumentException(
+ 'Parameter `order` must contains a data'
+ );
+ }
+
+ $this->checkIdParameter($by);
+
+ if (!array_key_exists($by, $order)) {
+ throw new InvalidArgumentException(
+ sprintf('Order array must contain the "%s" parameter.', $by)
+ );
+ }
+
+ return $this->client->makeRequest(
+ sprintf('/orders/%s/edit', $order[$by]),
+ RCrmHttpClient::METHOD_POST,
+ $this->fillSite(
+ $site,
+ array('order' => json_encode($order), 'by' => $by)
+ )
+ );
+ }
+
+ /**
+ * Get orders history
+ * @param array $filter
+ * @param null $page
+ * @param null $limit
+ *
+ * @return RCrmApiResponse
+ */
+ public function ordersHistory(array $filter = array(), $page = null, $limit = null)
+ {
+ $parameters = array();
+
+ if (count($filter)) {
+ $parameters['filter'] = $filter;
+ }
+ if (null !== $page) {
+ $parameters['page'] = (int) $page;
+ }
+ if (null !== $limit) {
+ $parameters['limit'] = (int) $limit;
+ }
+
+ return $this->client->makeRequest(
+ '/orders/history',
+ RCrmHttpClient::METHOD_GET,
+ $parameters
+ );
+ }
+
+ /**
+ * Returns filtered customers list
+ *
+ * @param array $filter (default: array())
+ * @param int $page (default: null)
+ * @param int $limit (default: null)
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function customersList(array $filter = array(), $page = null, $limit = null)
+ {
+ $parameters = array();
+
+ if (count($filter)) {
+ $parameters['filter'] = $filter;
+ }
+ if (null !== $page) {
+ $parameters['page'] = (int) $page;
+ }
+ if (null !== $limit) {
+ $parameters['limit'] = (int) $limit;
+ }
+
+ return $this->client->makeRequest(
+ '/customers',
+ RCrmHttpClient::METHOD_GET,
+ $parameters
+ );
+ }
+
+ /**
+ * Create a customer
+ *
+ * @param array $customer customer data
+ * @param string $site (default: null)
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function customersCreate(array $customer, $site = null)
+ {
+ if (! count($customer)) {
+ throw new InvalidArgumentException(
+ 'Parameter `customer` must contains a data'
+ );
+ }
+
+ return $this->client->makeRequest(
+ '/customers/create',
+ RCrmHttpClient::METHOD_POST,
+ $this->fillSite($site, array('customer' => json_encode($customer)))
+ );
+ }
+
+ /**
+ * Save customer IDs' (id and externalId) association in the CRM
+ *
+ * @param array $ids ids mapping
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function customersFixExternalIds(array $ids)
+ {
+ if (! count($ids)) {
+ throw new InvalidArgumentException(
+ 'Method parameter must contains at least one IDs pair'
+ );
+ }
+
+ return $this->client->makeRequest(
+ '/customers/fix-external-ids',
+ RCrmHttpClient::METHOD_POST,
+ array('customers' => json_encode($ids))
+ );
+ }
+
+ /**
+ * Upload array of the customers
+ *
+ * @param array $customers array of customers
+ * @param string $site (default: null)
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function customersUpload(array $customers, $site = null)
+ {
+ if (! count($customers)) {
+ throw new InvalidArgumentException(
+ 'Parameter `customers` must contains array of the customers'
+ );
+ }
+
+ return $this->client->makeRequest(
+ '/customers/upload',
+ RCrmHttpClient::METHOD_POST,
+ $this->fillSite($site, array('customers' => json_encode($customers)))
+ );
+ }
+
+ /**
+ * Get customer by id or externalId
+ *
+ * @param string $id customer identificator
+ * @param string $by (default: 'externalId')
+ * @param string $site (default: null)
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function customersGet($id, $by = 'externalId', $site = null)
+ {
+ $this->checkIdParameter($by);
+
+ return $this->client->makeRequest(
+ "/customers/$id",
+ RCrmHttpClient::METHOD_GET,
+ $this->fillSite($site, array('by' => $by))
+ );
+ }
+
+ /**
+ * Edit a customer
+ *
+ * @param array $customer customer data
+ * @param string $by (default: 'externalId')
+ * @param string $site (default: null)
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function customersEdit(array $customer, $by = 'externalId', $site = null)
+ {
+ if (!count($customer)) {
+ throw new InvalidArgumentException(
+ 'Parameter `customer` must contains a data'
+ );
+ }
+
+ $this->checkIdParameter($by);
+
+ if (!array_key_exists($by, $customer)) {
+ throw new InvalidArgumentException(
+ sprintf('Customer array must contain the "%s" parameter.', $by)
+ );
+ }
+
+ return $this->client->makeRequest(
+ sprintf('/customers/%s/edit', $customer[$by]),
+ RCrmHttpClient::METHOD_POST,
+ $this->fillSite(
+ $site,
+ array('customer' => json_encode($customer), 'by' => $by)
+ )
+ );
+ }
+
+ /**
+ * Get customers history
+ * @param array $filter
+ * @param null $page
+ * @param null $limit
+ *
+ * @return RCrmApiResponse
+ */
+ public function customersHistory(array $filter = array(), $page = null, $limit = null)
+ {
+ $parameters = array();
+
+ if (count($filter)) {
+ $parameters['filter'] = $filter;
+ }
+ if (null !== $page) {
+ $parameters['page'] = (int) $page;
+ }
+ if (null !== $limit) {
+ $parameters['limit'] = (int) $limit;
+ }
+
+ return $this->client->makeRequest(
+ '/customers/history',
+ RCrmHttpClient::METHOD_GET,
+ $parameters
+ );
+ }
+
+ /**
+ * Get orders assembly list
+ *
+ * @param array $filter (default: array())
+ * @param int $page (default: null)
+ * @param int $limit (default: null)
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function ordersPacksList(array $filter = array(), $page = null, $limit = null)
+ {
+ $parameters = array();
+
+ if (count($filter)) {
+ $parameters['filter'] = $filter;
+ }
+ if (null !== $page) {
+ $parameters['page'] = (int) $page;
+ }
+ if (null !== $limit) {
+ $parameters['limit'] = (int) $limit;
+ }
+
+ return $this->client->makeRequest(
+ '/orders/packs',
+ RCrmHttpClient::METHOD_GET,
+ $parameters
+ );
+ }
+
+ /**
+ * Create orders assembly
+ *
+ * @param array $pack pack data
+ * @param string $site (default: null)
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function ordersPacksCreate(array $pack, $site = null)
+ {
+ if (!count($pack)) {
+ throw new InvalidArgumentException(
+ 'Parameter `pack` must contains a data'
+ );
+ }
+
+ return $this->client->makeRequest(
+ '/orders/packs/create',
+ RCrmHttpClient::METHOD_POST,
+ $this->fillSite($site, array('pack' => json_encode($pack)))
+ );
+ }
+
+ /**
+ * Get orders assembly history
+ *
+ * @param array $filter (default: array())
+ * @param int $page (default: null)
+ * @param int $limit (default: null)
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function ordersPacksHistory(array $filter = array(), $page = null, $limit = null)
+ {
+ $parameters = array();
+
+ if (count($filter)) {
+ $parameters['filter'] = $filter;
+ }
+ if (null !== $page) {
+ $parameters['page'] = (int) $page;
+ }
+ if (null !== $limit) {
+ $parameters['limit'] = (int) $limit;
+ }
+
+ return $this->client->makeRequest(
+ '/orders/packs/history',
+ RCrmHttpClient::METHOD_GET,
+ $parameters
+ );
+ }
+
+ /**
+ * Get orders assembly by id
+ *
+ * @param string $id pack identificator
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function ordersPacksGet($id)
+ {
+ if (empty($id)) {
+ throw new InvalidArgumentException('Parameter `id` must be set');
+ }
+
+ return $this->client->makeRequest(
+ "/orders/packs/$id",
+ RCrmHttpClient::METHOD_GET
+ );
+ }
+
+ /**
+ * Delete orders assembly by id
+ *
+ * @param string $id pack identificator
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function ordersPacksDelete($id)
+ {
+ if (empty($id)) {
+ throw new InvalidArgumentException('Parameter `id` must be set');
+ }
+
+ return $this->client->makeRequest(
+ sprintf('/orders/packs/%s/delete', $id),
+ RCrmHttpClient::METHOD_POST
+ );
+ }
+
+ /**
+ * Edit orders assembly
+ *
+ * @param array $pack pack data
+ * @param string $site (default: null)
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function ordersPacksEdit(array $pack, $site = null)
+ {
+ if (!count($pack) || empty($pack['id'])) {
+ throw new InvalidArgumentException(
+ 'Parameter `pack` must contains a data & pack `id` must be set'
+ );
+ }
+
+ return $this->client->makeRequest(
+ sprintf('/orders/packs/%s/edit', $pack['id']),
+ RCrmHttpClient::METHOD_POST,
+ $this->fillSite($site, array('pack' => json_encode($pack)))
+ );
+ }
+
+ /**
+ * Get purchace prices & stock balance
+ *
+ * @param array $filter (default: array())
+ * @param int $page (default: null)
+ * @param int $limit (default: null)
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function storeInventories(array $filter = array(), $page = null, $limit = null)
+ {
+ $parameters = array();
+
+ if (count($filter)) {
+ $parameters['filter'] = $filter;
+ }
+ if (null !== $page) {
+ $parameters['page'] = (int) $page;
+ }
+ if (null !== $limit) {
+ $parameters['limit'] = (int) $limit;
+ }
+
+ return $this->client->makeRequest(
+ '/store/inventories',
+ RCrmHttpClient::METHOD_GET,
+ $parameters
+ );
+ }
+
+ /**
+ * Get store settings
+ *
+ * @param string $code get settings code
+ *
+ * @return RCrmApiResponse
+ * @throws RCrmJsonException
+ * @throws RCrmCurlException
+ * @throws InvalidArgumentException
+ *
+ * @return RCrmApiResponse
+ */
+ public function storeSettingsGet($code)
+ {
+ if (empty($code)) {
+ throw new InvalidArgumentException('Parameter `code` must be set');
+ }
+
+ return $this->client->makeRequest(
+ "/store/setting/$code",
+ RCrmHttpClient::METHOD_GET
+ );
+ }
+
+ /**
+ * Edit store configuration
+ *
+ * @param array $configuration
+ *
+ * @throws RCrmJsonException
+ * @throws RCrmCurlException
+ * @throws InvalidArgumentException
+ *
+ * @return RCrmApiResponse
+ */
+ public function storeSettingsEdit(array $configuration)
+ {
+ if (!count($configuration) || empty($configuration['code'])) {
+ throw new InvalidArgumentException(
+ 'Parameter `configuration` must contains a data & configuration `code` must be set'
+ );
+ }
+
+ return $this->client->makeRequest(
+ sprintf('/store/setting/%s/edit', $configuration['code']),
+ RCrmHttpClient::METHOD_POST,
+ array('configuration' => json_encode($configuration))
+ );
+ }
+
+ /**
+ * Upload store inventories
+ *
+ * @param array $offers offers data
+ * @param string $site (default: null)
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function storeInventoriesUpload(array $offers, $site = null)
+ {
+ if (!count($offers)) {
+ throw new InvalidArgumentException(
+ 'Parameter `offers` must contains array of the offers'
+ );
+ }
+
+ return $this->client->makeRequest(
+ '/store/inventories/upload',
+ RCrmHttpClient::METHOD_POST,
+ $this->fillSite($site, array('offers' => json_encode($offers)))
+ );
+ }
+
+ /**
+ * Upload store prices
+ *
+ * @param array $prices prices data
+ * @param string $site default: null)
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function storePricesUpload(array $prices, $site = null)
+ {
+ if (!count($prices)) {
+ throw new InvalidArgumentException(
+ 'Parameter `prices` must contains array of the prices'
+ );
+ }
+
+ return $this->client->makeRequest(
+ '/store/prices/upload',
+ RCrmHttpClient::METHOD_POST,
+ $this->fillSite($site, array('prices' => json_encode($prices)))
+ );
+ }
+
+ /**
+ * Get products
+ *
+ * @param array $filter (default: array())
+ * @param int $page (default: null)
+ * @param int $limit (default: null)
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function storeProducts(array $filter = array(), $page = null, $limit = null)
+ {
+ $parameters = array();
+
+ if (count($filter)) {
+ $parameters['filter'] = $filter;
+ }
+ if (null !== $page) {
+ $parameters['page'] = (int) $page;
+ }
+ if (null !== $limit) {
+ $parameters['limit'] = (int) $limit;
+ }
+
+ return $this->client->makeRequest(
+ '/store/products',
+ RCrmHttpClient::METHOD_GET,
+ $parameters
+ );
+ }
+
+ /**
+ * Get delivery settings
+ *
+ * @param string $code
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function deliverySettingsGet($code)
+ {
+ if (empty($code)) {
+ throw new InvalidArgumentException('Parameter `code` must be set');
+ }
+
+ return $this->client->makeRequest(
+ "/delivery/generic/setting/$code",
+ RCrmHttpClient::METHOD_GET
+ );
+ }
+
+ /**
+ * Edit delivery configuration
+ *
+ * @param array $configuration
+ *
+ * @throws RCrmJsonException
+ * @throws RCrmCurlException
+ * @throws InvalidArgumentException
+ *
+ * @return RCrmApiResponse
+ */
+ public function deliverySettingsEdit(array $configuration)
+ {
+ if (!count($configuration) || empty($configuration['code'])) {
+ throw new InvalidArgumentException(
+ 'Parameter `configuration` must contains a data & configuration `code` must be set'
+ );
+ }
+
+ return $this->client->makeRequest(
+ sprintf('/delivery/generic/setting/%s/edit', $configuration['code']),
+ RCrmHttpClient::METHOD_POST,
+ array('configuration' => json_encode($configuration))
+ );
+ }
+
+ /**
+ * Delivery tracking update
+ *
+ * @param string $code
+ * @param array $statusUpdate
+ *
+ * @throws RCrmJsonException
+ * @throws RCrmCurlException
+ * @throws InvalidArgumentException
+ *
+ * @return RCrmApiResponse
+ */
+ public function deliveryTracking($code, array $statusUpdate)
+ {
+ if (empty($code)) {
+ throw new InvalidArgumentException('Parameter `code` must be set');
+ }
+
+ if (!count($statusUpdate)) {
+ throw new InvalidArgumentException(
+ 'Parameter `statusUpdate` must contains a data'
+ );
+ }
+
+ return $this->client->makeRequest(
+ sprintf('/delivery/generic/%s/tracking', $code),
+ RCrmHttpClient::METHOD_POST,
+ $statusUpdate
+ );
+ }
+
+ /**
+ * Returns available county list
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function countriesList()
+ {
+ return $this->client->makeRequest(
+ '/reference/countries',
+ RCrmHttpClient::METHOD_GET
+ );
+ }
+
+ /**
+ * Returns deliveryServices list
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function deliveryServicesList()
+ {
+ return $this->client->makeRequest(
+ '/reference/delivery-services',
+ RCrmHttpClient::METHOD_GET
+ );
+ }
+
+ /**
+ * Edit deliveryService
+ *
+ * @param array $data delivery service data
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function deliveryServicesEdit(array $data)
+ {
+ if (!array_key_exists('code', $data)) {
+ throw new InvalidArgumentException(
+ 'Data must contain "code" parameter.'
+ );
+ }
+
+ return $this->client->makeRequest(
+ sprintf('/reference/delivery-services/%s/edit', $data['code']),
+ RCrmHttpClient::METHOD_POST,
+ array('deliveryService' => json_encode($data))
+ );
+ }
+
+ /**
+ * Returns deliveryTypes list
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function deliveryTypesList()
+ {
+ return $this->client->makeRequest(
+ '/reference/delivery-types',
+ RCrmHttpClient::METHOD_GET
+ );
+ }
+
+ /**
+ * Edit deliveryType
+ *
+ * @param array $data delivery type data
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function deliveryTypesEdit(array $data)
+ {
+ if (!array_key_exists('code', $data)) {
+ throw new InvalidArgumentException(
+ 'Data must contain "code" parameter.'
+ );
+ }
+
+ return $this->client->makeRequest(
+ sprintf('/reference/delivery-types/%s/edit', $data['code']),
+ RCrmHttpClient::METHOD_POST,
+ array('deliveryType' => json_encode($data))
+ );
+ }
+
+ /**
+ * Returns orderMethods list
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function orderMethodsList()
+ {
+ return $this->client->makeRequest(
+ '/reference/order-methods',
+ RCrmHttpClient::METHOD_GET
+ );
+ }
+
+ /**
+ * Edit orderMethod
+ *
+ * @param array $data order method data
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function orderMethodsEdit(array $data)
+ {
+ if (!array_key_exists('code', $data)) {
+ throw new InvalidArgumentException(
+ 'Data must contain "code" parameter.'
+ );
+ }
+
+ return $this->client->makeRequest(
+ sprintf('/reference/order-methods/%s/edit', $data['code']),
+ RCrmHttpClient::METHOD_POST,
+ array('orderMethod' => json_encode($data))
+ );
+ }
+
+ /**
+ * Returns orderTypes list
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function orderTypesList()
+ {
+ return $this->client->makeRequest(
+ '/reference/order-types',
+ RCrmHttpClient::METHOD_GET
+ );
+ }
+
+ /**
+ * Edit orderType
+ *
+ * @param array $data order type data
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function orderTypesEdit(array $data)
+ {
+ if (!array_key_exists('code', $data)) {
+ throw new InvalidArgumentException(
+ 'Data must contain "code" parameter.'
+ );
+ }
+
+ return $this->client->makeRequest(
+ sprintf('/reference/order-types/%s/edit', $data['code']),
+ RCrmHttpClient::METHOD_POST,
+ array('orderType' => json_encode($data))
+ );
+ }
+
+ /**
+ * Returns paymentStatuses list
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function paymentStatusesList()
+ {
+ return $this->client->makeRequest(
+ '/reference/payment-statuses',
+ RCrmHttpClient::METHOD_GET
+ );
+ }
+
+ /**
+ * Edit paymentStatus
+ *
+ * @param array $data payment status data
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function paymentStatusesEdit(array $data)
+ {
+ if (!array_key_exists('code', $data)) {
+ throw new InvalidArgumentException(
+ 'Data must contain "code" parameter.'
+ );
+ }
+
+ return $this->client->makeRequest(
+ sprintf('/reference/payment-statuses/%s/edit', $data['code']),
+ RCrmHttpClient::METHOD_POST,
+ array('paymentStatus' => json_encode($data))
+ );
+ }
+
+ /**
+ * Returns paymentTypes list
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function paymentTypesList()
+ {
+ return $this->client->makeRequest(
+ '/reference/payment-types',
+ RCrmHttpClient::METHOD_GET
+ );
+ }
+
+ /**
+ * Edit paymentType
+ *
+ * @param array $data payment type data
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function paymentTypesEdit(array $data)
+ {
+ if (!array_key_exists('code', $data)) {
+ throw new InvalidArgumentException(
+ 'Data must contain "code" parameter.'
+ );
+ }
+
+ return $this->client->makeRequest(
+ sprintf('/reference/payment-types/%s/edit', $data['code']),
+ RCrmHttpClient::METHOD_POST,
+ array('paymentType' => json_encode($data))
+ );
+ }
+
+ /**
+ * Returns productStatuses list
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function productStatusesList()
+ {
+ return $this->client->makeRequest(
+ '/reference/product-statuses',
+ RCrmHttpClient::METHOD_GET
+ );
+ }
+
+ /**
+ * Edit productStatus
+ *
+ * @param array $data product status data
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function productStatusesEdit(array $data)
+ {
+ if (!array_key_exists('code', $data)) {
+ throw new InvalidArgumentException(
+ 'Data must contain "code" parameter.'
+ );
+ }
+
+ return $this->client->makeRequest(
+ sprintf('/reference/product-statuses/%s/edit', $data['code']),
+ RCrmHttpClient::METHOD_POST,
+ array('productStatus' => json_encode($data))
+ );
+ }
+
+ /**
+ * Returns sites list
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function sitesList()
+ {
+ return $this->client->makeRequest(
+ '/reference/sites',
+ RCrmHttpClient::METHOD_GET
+ );
+ }
+
+ /**
+ * Edit site
+ *
+ * @param array $data site data
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function sitesEdit(array $data)
+ {
+ if (!array_key_exists('code', $data)) {
+ throw new InvalidArgumentException(
+ 'Data must contain "code" parameter.'
+ );
+ }
+
+ return $this->client->makeRequest(
+ sprintf('/reference/sites/%s/edit', $data['code']),
+ RCrmHttpClient::METHOD_POST,
+ array('site' => json_encode($data))
+ );
+ }
+
+ /**
+ * Returns statusGroups list
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function statusGroupsList()
+ {
+ return $this->client->makeRequest(
+ '/reference/status-groups',
+ RCrmHttpClient::METHOD_GET
+ );
+ }
+
+ /**
+ * Returns statuses list
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function statusesList()
+ {
+ return $this->client->makeRequest(
+ '/reference/statuses',
+ RCrmHttpClient::METHOD_GET
+ );
+ }
+
+ /**
+ * Edit order status
+ *
+ * @param array $data status data
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function statusesEdit(array $data)
+ {
+ if (!array_key_exists('code', $data)) {
+ throw new InvalidArgumentException(
+ 'Data must contain "code" parameter.'
+ );
+ }
+
+ return $this->client->makeRequest(
+ sprintf('/reference/statuses/%s/edit', $data['code']),
+ RCrmHttpClient::METHOD_POST,
+ array('status' => json_encode($data))
+ );
+ }
+
+ /**
+ * Returns stores list
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function storesList()
+ {
+ return $this->client->makeRequest(
+ '/reference/stores',
+ RCrmHttpClient::METHOD_GET
+ );
+ }
+
+ /**
+ * Edit store
+ *
+ * @param array $data site data
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function storesEdit(array $data)
+ {
+ if (!array_key_exists('code', $data)) {
+ throw new InvalidArgumentException(
+ 'Data must contain "code" parameter.'
+ );
+ }
+
+ if (!array_key_exists('name', $data)) {
+ throw new InvalidArgumentException(
+ 'Data must contain "name" parameter.'
+ );
+ }
+
+ return $this->client->makeRequest(
+ sprintf('/reference/stores/%s/edit', $data['code']),
+ RCrmHttpClient::METHOD_POST,
+ array('store' => json_encode($data))
+ );
+ }
+
+ /**
+ * Get prices types
+ *
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function pricesTypes()
+ {
+ return $this->client->makeRequest(
+ '/reference/price-types',
+ RCrmHttpClient::METHOD_GET
+ );
+ }
+
+ /**
+ * Edit price type
+ *
+ * @param array $data
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function pricesEdit(array $data)
+ {
+ if (!array_key_exists('code', $data)) {
+ throw new InvalidArgumentException(
+ 'Data must contain "code" parameter.'
+ );
+ }
+
+ if (!array_key_exists('name', $data)) {
+ throw new InvalidArgumentException(
+ 'Data must contain "name" parameter.'
+ );
+ }
+
+ return $this->client->makeRequest(
+ sprintf('/reference/price-types/%s/edit', $data['code']),
+ RCrmHttpClient::METHOD_POST,
+ array('priceType' => json_encode($data))
+ );
+ }
+
+ /**
+ * Get telephony settings
+ *
+ * @param string $code
+ *
+ * @throws RCrmJsonException
+ * @throws RCrmCurlException
+ * @throws InvalidArgumentException
+ *
+ * @return RCrmApiResponse
+ */
+ public function telephonySettingsGet($code)
+ {
+ if (empty($code)) {
+ throw new InvalidArgumentException('Parameter `code` must be set');
+ }
+
+ return $this->client->makeRequest(
+ "/telephony/setting/$code",
+ RCrmHttpClient::METHOD_GET
+ );
+ }
+
+ /**
+ * Edit telephony settings
+ *
+ * @param string $code symbolic code
+ * @param string $clientId client id
+ * @param boolean $active telephony activity
+ * @param mixed $name service name
+ * @param mixed $makeCallUrl service init url
+ * @param mixed $image service logo url(svg file)
+ *
+ * @param array $additionalCodes
+ * @param array $externalPhones
+ * @param bool $allowEdit
+ * @param bool $inputEventSupported
+ * @param bool $outputEventSupported
+ * @param bool $hangupEventSupported
+ * @param bool $changeUserStatusUrl
+ *
+ * @return RCrmApiResponse
+ */
+ public function telephonySettingsEdit(
+ $code,
+ $clientId,
+ $active = false,
+ $name = false,
+ $makeCallUrl = false,
+ $image = false,
+ $additionalCodes = array(),
+ $externalPhones = array(),
+ $allowEdit = false,
+ $inputEventSupported = false,
+ $outputEventSupported = false,
+ $hangupEventSupported = false,
+ $changeUserStatusUrl = false
+ )
+ {
+ if (!isset($code)) {
+ throw new InvalidArgumentException('Code must be set');
+ }
+
+ $parameters['code'] = $code;
+
+ if (!isset($clientId)) {
+ throw new InvalidArgumentException('client id must be set');
+ }
+
+ $parameters['clientId'] = $clientId;
+
+ if (!isset($active)) {
+ $parameters['active'] = false;
+ } else {
+ $parameters['active'] = $active;
+ }
+
+ if (!isset($name)) {
+ throw new InvalidArgumentException('name must be set');
+ }
+
+ if (isset($name)) {
+ $parameters['name'] = $name;
+ }
+
+ if (isset($makeCallUrl)) {
+ $parameters['makeCallUrl'] = $makeCallUrl;
+ }
+
+ if (isset($image)) {
+ $parameters['image'] = $image;
+ }
+
+ if (isset($additionalCodes)) {
+ $parameters['additionalCodes'] = $additionalCodes;
+ }
+
+ if (isset($externalPhones)) {
+ $parameters['externalPhones'] = $externalPhones;
+ }
+
+ if (isset($allowEdit)) {
+ $parameters['allowEdit'] = $allowEdit;
+ }
+
+ if (isset($inputEventSupported)) {
+ $parameters['inputEventSupported'] = $inputEventSupported;
+ }
+
+ if (isset($outputEventSupported)) {
+ $parameters['outputEventSupported'] = $outputEventSupported;
+ }
+
+ if (isset($hangupEventSupported)) {
+ $parameters['hangupEventSupported'] = $hangupEventSupported;
+ }
+
+ if (isset($changeUserStatusUrl)) {
+ $parameters['changeUserStatusUrl'] = $changeUserStatusUrl;
+ }
+
+ return $this->client->makeRequest(
+ "/telephony/setting/$code/edit",
+ RCrmHttpClient::METHOD_POST,
+ array('configuration' => json_encode($parameters))
+ );
+ }
+
+ /**
+ * Call event
+ *
+ * @param string $phone phone number
+ * @param string $type call type
+ * @param array $codes
+ * @param string $hangupStatus
+ * @param string $externalPhone
+ * @param array $webAnalyticsData
+ *
+ * @return RCrmApiResponse
+ * @internal param string $code additional phone code
+ * @internal param string $status call status
+ *
+ */
+ public function telephonyCallEvent(
+ $phone,
+ $type,
+ $codes,
+ $hangupStatus,
+ $externalPhone = null,
+ $webAnalyticsData = array()
+ )
+ {
+ if (!isset($phone)) {
+ throw new InvalidArgumentException('Phone number must be set');
+ }
+
+ if (!isset($type)) {
+ throw new InvalidArgumentException('Type must be set (in|out|hangup)');
+ }
+
+ if (empty($codes)) {
+ throw new InvalidArgumentException('Codes array must be set');
+ }
+
+ $parameters['phone'] = $phone;
+ $parameters['type'] = $type;
+ $parameters['codes'] = $codes;
+ $parameters['hangupStatus'] = $hangupStatus;
+ $parameters['callExternalId'] = $externalPhone;
+ $parameters['webAnalyticsData'] = $webAnalyticsData;
+
+ return $this->client->makeRequest(
+ '/telephony/call/event',
+ RCrmHttpClient::METHOD_POST,
+ array('event' => json_encode($parameters))
+ );
+ }
+
+ /**
+ * Upload calls
+ *
+ * @param array $calls calls data
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function telephonyCallsUpload(array $calls)
+ {
+ if (!count($calls)) {
+ throw new InvalidArgumentException(
+ 'Parameter `calls` must contains array of the calls'
+ );
+ }
+
+ return $this->client->makeRequest(
+ '/telephony/calls/upload',
+ RCrmHttpClient::METHOD_POST,
+ array('calls' => json_encode($calls))
+ );
+ }
+
+ /**
+ * Get call manager
+ *
+ * @param string $phone phone number
+ * @param bool $details detailed information
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function telephonyCallManager($phone, $details)
+ {
+ if (!isset($phone)) {
+ throw new InvalidArgumentException('Phone number must be set');
+ }
+
+ $parameters['phone'] = $phone;
+ $parameters['details'] = isset($details) ? $details : 0;
+
+ return $this->client->makeRequest(
+ '/telephony/manager',
+ RCrmHttpClient::METHOD_GET,
+ $parameters
+ );
+ }
+
+ /**
+ * Update CRM basic statistic
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function statisticUpdate()
+ {
+ return $this->client->makeRequest(
+ '/statistic/update',
+ RCrmHttpClient::METHOD_GET
+ );
+ }
+
+ /**
+ * Return current site
+ *
+ * @return string
+ */
+ public function getSite()
+ {
+ return $this->siteCode;
+ }
+
+ /**
+ * Set site
+ *
+ * @param string $site site code
+ *
+ * @return void
+ */
+ public function setSite($site)
+ {
+ $this->siteCode = $site;
+ }
+
+ /**
+ * Check ID parameter
+ *
+ * @param string $by identify by
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return bool
+ */
+ protected function checkIdParameter($by)
+ {
+ $allowedForBy = array(
+ 'externalId',
+ 'id'
+ );
+
+ if (!in_array($by, $allowedForBy, false)) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'Value "%s" for "by" param is not valid. Allowed values are %s.',
+ $by,
+ implode(', ', $allowedForBy)
+ )
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Fill params by site value
+ *
+ * @param string $site site code
+ * @param array $params input parameters
+ *
+ * @return array
+ */
+ protected function fillSite($site, array $params)
+ {
+ if ($site) {
+ $params['site'] = $site;
+ } elseif ($this->siteCode) {
+ $params['site'] = $this->siteCode;
+ }
+
+ return $params;
+ }
+}
diff --git a/classes/modules/RetailCRM/classes/retailcrm/RCrmApiResponse.php b/classes/modules/RetailCRM/classes/retailcrm/RCrmApiResponse.php
new file mode 100644
index 0000000..aaa5580
--- /dev/null
+++ b/classes/modules/RetailCRM/classes/retailcrm/RCrmApiResponse.php
@@ -0,0 +1,164 @@
+
+ * @license https://opensource.org/licenses/MIT MIT License
+ * @link http://www.retailcrm.ru/docs/Developers/ApiVersion4
+ */
+class RCrmApiResponse implements ArrayAccess
+{
+ // HTTP response status code
+ protected $statusCode;
+
+ // response assoc array
+ protected $response;
+
+ /**
+ * ApiResponse constructor.
+ *
+ * @param int $statusCode HTTP status code
+ * @param mixed $responseBody HTTP body
+ *
+ * @throws RCrmJsonException
+ */
+ public function __construct($statusCode, $responseBody = null)
+ {
+ $this->statusCode = (int)$statusCode;
+
+ if (!empty($responseBody)) {
+ $response = json_decode($responseBody, true);
+
+ if (!$response && JSON_ERROR_NONE !== ($error = json_last_error())) {
+ throw new RCrmJsonException(
+ "Invalid JSON in the API response body. Error code #$error",
+ $error
+ );
+ }
+
+ $this->response = $response;
+ }
+ }
+
+ /**
+ * Return HTTP response status code
+ *
+ * @return int
+ */
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+
+ /**
+ * HTTP request was successful
+ *
+ * @return bool
+ */
+ public function isSuccessful()
+ {
+ return $this->statusCode < 400;
+ }
+
+ /**
+ * Allow to access for the property throw class method
+ *
+ * @param string $name method name
+ * @param mixed $arguments method parameters
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return mixed
+ */
+ public function __call($name, $arguments)
+ {
+ // convert getSomeProperty to someProperty
+ $propertyName = strtolower(substr($name, 3, 1)) . substr($name, 4);
+
+ if (!isset($this->response[$propertyName])) {
+ throw new InvalidArgumentException("Method \"$name\" not found");
+ }
+
+ return $this->response[$propertyName];
+ }
+
+ /**
+ * Allow to access for the property throw object property
+ *
+ * @param string $name property name
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ if (!isset($this->response[$name])) {
+ throw new InvalidArgumentException("Property \"$name\" not found");
+ }
+
+ return $this->response[$name];
+ }
+
+ /**
+ * Offset set
+ *
+ * @param mixed $offset offset
+ * @param mixed $value value
+ *
+ * @throws BadMethodCallException
+ * @return void
+ */
+ public function offsetSet($offset, $value)
+ {
+ throw new BadMethodCallException('This activity not allowed');
+ }
+
+ /**
+ * Offset unset
+ *
+ * @param mixed $offset offset
+ *
+ * @throws BadMethodCallException
+ * @return void
+ */
+ public function offsetUnset($offset)
+ {
+ throw new BadMethodCallException('This call not allowed');
+ }
+
+ /**
+ * Check offset
+ *
+ * @param mixed $offset offset
+ *
+ * @return bool
+ */
+ public function offsetExists($offset)
+ {
+ return isset($this->response[$offset]);
+ }
+
+ /**
+ * Get offset
+ *
+ * @param mixed $offset offset
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return mixed
+ */
+ public function offsetGet($offset)
+ {
+ if (!isset($this->response[$offset])) {
+ throw new InvalidArgumentException("Property \"$offset\" not found");
+ }
+
+ return $this->response[$offset];
+ }
+}
\ No newline at end of file
diff --git a/classes/modules/RetailCRM/classes/retailcrm/RCrmCurlException.php b/classes/modules/RetailCRM/classes/retailcrm/RCrmCurlException.php
new file mode 100644
index 0000000..0fe29a8
--- /dev/null
+++ b/classes/modules/RetailCRM/classes/retailcrm/RCrmCurlException.php
@@ -0,0 +1,5 @@
+ ', $mapItem);
+ $map[$mapItem[0]] = $mapItem[1];
+ }
+
+ return $map;
+ }
+
+ /**
+ * @param $map array
+ * @param $item string
+ * @param $reversed bool
+ * @return string|null
+ */
+ public function getRelationByMap($map, $item, $reversed = false)
+ {
+ if (!$reversed) {
+ if (isset($map[$item]) && !empty($map[$item])) {
+ return $map[$item];
+ } else {
+ return null;
+ }
+ } else {
+ foreach ($map as $umiStatusOrder => $crmStatusOrder) {
+ if ($crmStatusOrder == $item) {
+ return $umiStatusOrder;
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * @param $orderHistory array
+ * @return array
+ */
+ public function getAssemblyOrder($orderHistory)
+ {
+ if (file_exists(__DIR__ . '/../../data/objects.xml')) {
+ $objects = simplexml_load_file(__DIR__ . '/../../data/objects.xml');
+ foreach ($objects->fields->field as $object) {
+ $fields[(string)$object["group"]][(string)$object["id"]] = (string)$object;
+ }
+ }
+
+ $orders = array();
+
+ foreach ($orderHistory as $change) {
+ $change['order'] = $this->removeEmpty($change['order']);
+
+ $orderId = $change['order']['id'];
+
+ if (isset($change['order']['items'])) {
+ $items = array();
+ foreach ($change['order']['items'] as $item) {
+ if (isset($change['created'])) {
+ $item['created'] = 1;
+ }
+ $items[$item['id']] = $item;
+ }
+ $change['order']['items'] = $items;
+ }
+
+ if (isset($change['order']['contragent']['contragentType'])) {
+ $change['order']['contragentType'] = $change['order']['contragent']['contragentType'];
+ unset($change['order']['contragent']);
+ }
+
+ if (isset($orders[$orderId])) {
+ $orders[$orderId] = array_merge($orders[$orderId], $change['order']);
+ } else {
+ $orders[$orderId] = $change['order'];
+ }
+
+ if (isset($change['item'])) {
+ $itemId = $change['item']['id'];
+
+ if ($orders[$orderId]['items'][$itemId]) {
+ $orders[$orderId]['items'][$itemId] = array_merge($orders[$orderId]['items'][$itemId],
+ $change['item']);
+ } else {
+ $orders[$orderId]['items'][$itemId] = $change['item'];
+ }
+
+ if (empty($change['oldValue']) && $change['field'] == 'order_product') {
+ $orders[$orderId]['items'][$itemId]['created'] = true;
+ }
+
+ if (empty($change['newValue']) && $change['field'] == 'order_product') {
+ $orders[$orderId]['items'][$itemId]['deleted'] = true;
+ }
+
+ if (!$orders[$orderId]['items'][$itemId]['created'] && $fields['item'][$change['field']]) {
+ $orders[$orderId]['items'][$itemId][$fields['item'][$change['field']]] = $change['newValue'];
+ }
+ } else {
+ if (isset($fields['delivery'][$change['field']]) && $fields['delivery'][$change['field']] == 'service') {
+ $orders[$orderId]['delivery']['service']['code'] = $this->historyNewValue($change['newValue']);
+ } elseif (isset($fields['delivery'][$change['field']])) {
+ $orders[$orderId]['delivery'][$fields['delivery'][$change['field']]] = $this->historyNewValue($change['newValue']);
+ } elseif (isset($fields['orderAddress'][$change['field']])) {
+ $orders[$orderId]['delivery']['address'][$fields['orderAddress'][$change['field']]] = $change['newValue'];
+ } elseif (isset($fields['integrationDelivery'][$change['field']])) {
+ $orders[$orderId]['delivery']['service'][$fields['integrationDelivery'][$change['field']]] = $this->historyNewValue($change['newValue']);
+ } elseif (isset($fields['customerContragent'][$change['field']])) {
+ $orders[$orderId][$fields['customerContragent'][$change['field']]] = $this->historyNewValue($change['newValue']);
+ } elseif (strripos($change['field'], 'custom_') !== false) {
+ $orders[$orderId]['customFields'][str_replace('custom_', '', $change['field'])] = $this->historyNewValue($change['newValue']);
+ } elseif (isset($fields['order'][$change['field']])) {
+ $orders[$orderId][$fields['order'][$change['field']]] = $this->historyNewValue($change['newValue']);
+ }
+
+ if (isset($change['created'])) {
+ $orders[$orderId]['created'] = 1;
+ }
+
+ if (isset($change['deleted'])) {
+ $orders[$orderId]['deleted'] = 1;
+ }
+ }
+ }
+
+ return array_values($orders);
+ }
+
+ /**
+ * @param $value mixed
+ * @return string
+ */
+ public function historyNewValue($value)
+ {
+ if (isset($value['code'])) {
+ return $value['code'];
+ } else {
+ return $value;
+ }
+ }
+
+ /**
+ * @param $inputArray mixed
+ * @return array
+ */
+ public function removeEmpty($inputArray)
+ {
+ $outputArray = array();
+ if (!empty($inputArray)) {
+ foreach ($inputArray as $key => $element) {
+ if (!empty($element) || $element === 0 || $element === '0') {
+ if (is_array($element)) {
+ $element = $this->removeEmpty($element);
+ }
+ $outputArray[$key] = $element;
+ }
+ }
+ }
+
+ return $outputArray;
+ }
+}
\ No newline at end of file
diff --git a/classes/modules/RetailCRM/classes/retailcrm/RCrmHistory.php b/classes/modules/RetailCRM/classes/retailcrm/RCrmHistory.php
new file mode 100644
index 0000000..23bcdce
--- /dev/null
+++ b/classes/modules/RetailCRM/classes/retailcrm/RCrmHistory.php
@@ -0,0 +1,470 @@
+api = new RCrmProxy(
+ $config->get('retailcrm', 'crmUrl'),
+ $config->get('retailcrm', 'apiKey'),
+ __DIR__ . '/../../../retailcrm.error.log'
+ );
+ }
+
+ public function runOrders()
+ {
+ $retailcrm = new RetailCRM;
+ $regedit = regedit::getInstance();
+ $config = mainConfiguration::getInstance();
+
+ $historyLastId = $config->get('retailcrm', 'lastHistorySinceId');
+
+ if (!$historyLastId) {
+ $historyLastId = 0;
+ }
+
+ $historyPage = 1;
+ $historyArray = array();
+
+ do {
+ $response = $this->api->ordersHistory(array('sinceId' => $historyLastId), $historyPage);
+ $historyPage++;
+
+ if (!is_null($response) && count($response['history'])) {
+ $historyArray = array_merge($historyArray, $response['history']);
+ } else {
+ break;
+ }
+ } while ($response['pagination']['currentPage'] != $response['pagination']['totalPageCount']);
+
+ if (count($historyArray)) {
+ $lastChange = end($historyArray);
+
+ $crmOrders = $retailcrm->getAssemblyOrder($historyArray);
+
+ $objectTypes = umiObjectTypesCollection::getInstance();
+ $objects = umiObjectsCollection::getInstance();
+
+ foreach ($crmOrders as $crmOrder) {
+ $order = null;
+
+ if (isset($crmOrder['externalId'])) {
+ $order = order::get($crmOrder['externalId']);
+ }
+
+ if ((!isset($crmOrder['externalId']) || !$order) && isset($crmOrder['created'])) {
+ if (isset($crmOrder['deleted']) && $crmOrder['deleted']) {
+ continue;
+ }
+
+ $order = order::create();
+
+ /* Order create date */
+ $order->getObject()->getPropByName('order_date')->setValue(umiDate::getTimeStamp($crmOrder['createdAt']));
+
+ $crmCustomer = $crmOrder['customer'];
+ if (isset($crmCustomer['externalId']) && $crmCustomer['externalId'] > 0) {
+ // TODO: проверить существует ли такой пользователь в системе, если нет, то создать, т.к. принимая customer externalId мы считаем, что такой пользователь уже есть
+ $order->getObject()->getPropByName('customer_id')->setValue($crmCustomer['externalId']);
+ } else {
+ $customer = $objects->getObjectByName($crmCustomer['id'] . '-retailcrm');
+
+ if (!$customer) {
+ $customerObjectTypeId = $objectTypes->getBaseType('emarket', 'customer');
+
+ $customerId = $objects->addObject(
+ $crmCustomer['id'] . '-retailcrm',
+ $customerObjectTypeId
+ );
+
+ $customer = $objects->getObject($customerId);
+ $customer->setOwnerId($objects->getObjectIdByGUID('system-guest'));
+ $customer->commit();
+
+ $expirations = umiObjectsExpiration::getInstance();
+ $expirations->add($customerId, customer::$defaultExpiration);
+
+ if (!empty($crmCustomer['firstName'])) {
+ $customer->getPropByName('fname')->setValue($crmCustomer['firstName']);
+ }
+
+ if (!empty($crmCustomer['lastName'])) {
+ $customer->getPropByName('lname')->setValue($crmCustomer['lastName']);
+ }
+
+ if (!empty($crmCustomer['patronymic'])) {
+ $customer->getPropByName('father_name')->setValue($crmCustomer['patronymic']);
+ }
+
+ if (!empty($crmCustomer['email'])) {
+ $customer->getPropByName('email')->setValue($crmCustomer['email']);
+ }
+
+ if (isset($crmCustomer['phones']) && count($crmCustomer['phones']) > 0) {
+ $customer->getPropByName('phone')->setValue($crmCustomer['phones'][0]['number']);
+ }
+
+ if (isset($crmCustomer['address'])) {
+ $deliveryTypeId = $objectTypes->getTypeIdByGUID('emarket-deliveryaddress');
+
+ $deliveryObjectId = $objects->addObject(
+ 'Address for customer ' . $customer->getId(),
+ $deliveryTypeId
+ );
+
+ $deliveryObject = $objects->getObject($deliveryObjectId);
+
+ $crmCustomerAddress = $crmCustomer['address'];
+
+ if (!empty($crmCustomerAddress['countryIso'])) {
+ $selector = new selector('objects');
+ $selector->types('object-type')->id($config->get('retailcrm', 'countryGuideId'));
+
+ $countries = $selector->result();
+
+ foreach ($countries as $country) {
+ /** @var umiObject $country */
+
+ $countryCode = $country->getValue('country_iso_code');
+
+ if ($crmCustomerAddress['countryIso'] == $countryCode) {
+ $deliveryObject->getPropByName('country')->setValue($country->getId());
+ break;
+ }
+ }
+ }
+ if (!empty($crmCustomerAddress['index'])) {
+ $deliveryObject->getPropByName('index')->setValue($crmCustomerAddress['index']);
+ }
+
+ if (!empty($crmCustomerAddress['region'])) {
+ $deliveryObject->getPropByName('region')->setValue($crmCustomerAddress['region']);
+ }
+
+ if (!empty($crmCustomerAddress['city'])) {
+ $deliveryObject->getPropByName('city')->setValue($crmCustomerAddress['city']);
+ }
+
+ if (!empty($crmCustomerAddress['street'])) {
+ $deliveryObject->getPropByName('street')->setValue($crmCustomerAddress['street']);
+ }
+
+ if (!empty($crmCustomerAddress['building'])) {
+ $deliveryObject->getPropByName('house')->setValue($crmCustomerAddress['building']);
+ }
+
+ if (!empty($crmCustomerAddress['flat'])) {
+ $deliveryObject->getPropByName('flat')->setValue($crmCustomerAddress['flat']);
+ }
+
+ $deliveryAddresses = array(
+ 0 => $deliveryObject->getId()
+ );
+
+ $customer->getPropByName('delivery_addresses')->setValue($deliveryAddresses);
+ }
+ }
+
+ $order->getObject()->getPropByName('customer_id')->setValue($customer->getId());
+ }
+
+ $orderItems = $order->getItems();
+ foreach ($orderItems as $orderItem) {
+ $order->removeItem($orderItem);
+ }
+
+ $crmItems = $crmOrder['items'];
+ foreach ($crmItems as $crmItem) {
+ if (isset($crmItem['deleted']) && $crmItem['deleted'] == true) {
+ continue;
+ }
+ if (!isset($crmItem['offer']['externalId'])) {
+ continue;
+ }
+
+ if (mb_strpos($crmItem['offer']['externalId'], '#')) {
+ $data = explode('#', $crmItem['offer']['externalId']);
+ $itemId = $data[0];
+
+ $orderItem = orderItem::create($itemId);
+
+ $itemOptions = $data[1];
+ $itemOptions = explode('-', $itemOptions);
+ foreach ($itemOptions as $itemOption) {
+ $itemOption = explode('_', $itemOption);
+ $itemOptionGroupId = $itemOption[0];
+ $itemOptionValue = $itemOption[1];
+ $itemOptionObject = new umiField($itemOptionGroupId);
+
+ $orderItem->appendOption($itemOptionObject->getName(), $itemOptionValue);
+ }
+ } else {
+ $orderItem = orderItem::create($crmItem['offer']['externalId']);
+ }
+
+ /** @var optionedOrderItem $orderItem */
+ $orderItem->setAmount($crmItem['quantity']);
+
+ $order->appendItem($orderItem);
+ }
+
+ if (isset($crmOrder['paymentType'])) {
+ $relationOrderPaymentTypesMap = $retailcrm->getRelationMap($config->get('retailcrm', 'orderPaymentTypeMap'));
+ $umiOrderPaymentType = $retailcrm->getRelationByMap($relationOrderPaymentTypesMap, $crmOrder['paymentType'], true);
+ $order->getObject()->getPropByName('payment_id')->setValue($umiOrderPaymentType);
+ }
+
+ if (isset($crmOrder['paymentStatus'])) {
+ $relationOrderPaymentStatusesMap = $retailcrm->getRelationMap($config->get('retailcrm', 'orderPaymentStatusMap'));
+ $umiOrderPaymentStatus = $retailcrm->getRelationByMap($relationOrderPaymentStatusesMap, $crmOrder['paymentStatus'], true);
+ $order->getObject()->getPropByName('payment_status_id')->setValue($umiOrderPaymentStatus);
+ }
+
+ if (isset($crmOrder['delivery']['code'])) {
+ $relationOrderDeliveryTypesMap = $retailcrm->getRelationMap($config->get('retailcrm', 'orderDeliveryTypeMap'));
+ $umiOrderDeliveryType = $retailcrm->getRelationByMap($relationOrderDeliveryTypesMap, $crmOrder['delivery']['code'], true);
+ $order->getObject()->getPropByName('delivery_id')->setValue($umiOrderDeliveryType);
+ }
+
+ if (isset($crmOrder['delivery']['address']) && count($crmOrder['delivery']['address'])) {
+ $crmDeliveryAddress = $crmOrder['delivery']['address'];
+
+ $deliveryTypeId = $objectTypes->getTypeIdByGUID('emarket-deliveryaddress');
+ $deliveryObjectId = $objects->addObject('Address for order ' . $order->getId(), $deliveryTypeId);
+ $deliveryObject = $objects->getObject($deliveryObjectId);
+
+ if (!empty($crmDeliveryAddress['countryIso'])) {
+ $selector = new selector('objects');
+ try {
+ $selector->types('object-type')->id($config->get('retailcrm', 'countryGuideId'));
+ $countries = $selector->result();
+
+ foreach ($countries as $country) {
+ /** @var umiObject $country */
+
+ $countryCode = $country->getValue('country_iso_code');
+
+ if ($crmDeliveryAddress['countryIso'] == $countryCode) {
+ $deliveryObject->getPropByName('country')->setValue($country->getId());
+ break;
+ }
+ }
+ } catch (selectorException $e) {}
+ }
+
+ if (!empty($crmDeliveryAddress['index'])) {
+ $deliveryObject->getPropByName('index')->setValue($crmDeliveryAddress['index']);
+ }
+
+ if (!empty($crmDeliveryAddress['region'])) {
+ $deliveryObject->getPropByName('region')->setValue($crmDeliveryAddress['region']);
+ }
+
+ if (!empty($crmDeliveryAddress['city'])) {
+ $deliveryObject->getPropByName('city')->setValue($crmDeliveryAddress['city']);
+ }
+
+ if (!empty($crmDeliveryAddress['street'])) {
+ $deliveryObject->getPropByName('street')->setValue($crmDeliveryAddress['street']);
+ }
+
+ if (!empty($crmDeliveryAddress['building'])) {
+ $deliveryObject->getPropByName('house')->setValue($crmDeliveryAddress['building']);
+ }
+
+ if (!empty($crmDeliveryAddress['flat'])) {
+ $deliveryObject->getPropByName('flat')->setValue($crmDeliveryAddress['flat']);
+ }
+
+ $order->getObject()->getPropByName('delivery_address')->setValue($deliveryObject->getId());
+ }
+
+ if (!empty($crmOrder['number'])) {
+ $order->setName($crmOrder['number']);
+ } else {
+ $order->generateNumber();
+ $order->setName($order->getName() . ' ' . $crmOrder['id'] . "-retailcrm");
+ }
+
+ $this->api->ordersFixExternalIds(array(
+ array(
+ 'id' => $crmOrder['id'],
+ 'externalId' => $order->getId()
+ )
+ ));
+ } else {
+ if (!$order) {
+ continue;
+ }
+
+ if (isset($crmOrder['items']) && count($crmOrder['items']) > 0) {
+ $orderItems = $order->getItems();
+ foreach ($orderItems as $orderItem) {
+ $order->removeItem($orderItem);
+ }
+
+ $crmOrderForItems = $this->api->ordersGet($crmOrder['externalId'])->getOrder();
+ $crmItems = $crmOrderForItems['items'];
+
+ foreach ($crmItems as $crmItem) {
+ if (isset($crmItem['deleted']) && $crmItem['deleted'] == true) {
+ continue;
+ }
+ if (!isset($crmItem['offer']['externalId'])) {
+ continue;
+ }
+
+ if (mb_strpos($crmItem['offer']['externalId'], '#')) {
+ $data = explode('#', $crmItem['offer']['externalId']);
+ $itemId = $data[0];
+
+ $orderItem = orderItem::create($itemId);
+
+ $itemOptions = $data[1];
+ $itemOptions = explode('-', $itemOptions);
+ foreach ($itemOptions as $itemOption) {
+ $itemOption = explode('_', $itemOption);
+ $itemOptionGroupId = $itemOption[0];
+ $itemOptionValue = $itemOption[1];
+ $itemOptionObject = new umiField($itemOptionGroupId);
+
+ $orderItem->appendOption($itemOptionObject->getName(), $itemOptionValue);
+ }
+ } else {
+ $orderItem = orderItem::create($crmItem['offer']['externalId']);
+ }
+
+ /** @var optionedOrderItem $orderItem */
+ $orderItem->setAmount($crmItem['quantity']);
+ $order->appendItem($orderItem);
+ }
+ }
+
+ $customer = $objects->getObject($order->getCustomerId());
+
+ if (isset($crmOrder['phone'])) {
+ $customer->getPropByName('phone')->setValue($crmOrder['phone']);
+ }
+
+ if (isset($crmOrder['lastName'])) {
+ $customer->getPropByName('lname')->setValue($crmOrder['lastName']);
+ }
+
+ if (isset($crmOrder['firstName'])) {
+ $customer->getPropByName('fname')->setValue($crmOrder['firstName']);
+ }
+
+ if (isset($crmOrder['patronymic'])) {
+ $customer->getPropByName('father_name')->setValue($crmOrder['patronymic']);
+ }
+
+ if (isset($crmOrder['e-mail'])) {
+ $customer->getPropByName('e-mail')->setValue($crmOrder['e-mail']);
+ }
+
+ if (isset($crmOrder['paymentType'])) {
+ $relationOrderPaymentTypesMap = $retailcrm->getRelationMap($config->get('retailcrm', 'orderPaymentTypeMap'));
+ $umiOrderPaymentType = $retailcrm->getRelationByMap($relationOrderPaymentTypesMap, $crmOrder['paymentType'], true);
+ $order->getObject()->getPropByName('payment_id')->setValue($umiOrderPaymentType);
+ }
+
+ if (isset($crmOrder['paymentStatus'])) {
+ $relationOrderPaymentStatusesMap = $retailcrm->getRelationMap($config->get('retailcrm', 'orderPaymentStatusMap'));
+ $umiOrderPaymentStatus = $retailcrm->getRelationByMap($relationOrderPaymentStatusesMap, $crmOrder['paymentStatus'], true);
+ $order->getObject()->getPropByName('payment_status_id')->setValue($umiOrderPaymentStatus);
+ }
+
+ if (isset($crmOrder['delivery']['code'])) {
+ $relationOrderDeliveryTypesMap = $retailcrm->getRelationMap($config->get('retailcrm', 'orderDeliveryTypeMap'));
+ $umiOrderDeliveryType = $retailcrm->getRelationByMap($relationOrderDeliveryTypesMap, $crmOrder['delivery']['code'], true);
+ $order->getObject()->getPropByName('delivery_id')->setValue($umiOrderDeliveryType);
+ }
+
+ if (isset($crmOrder['delivery']['address']) && count($crmOrder['delivery']['address'])) {
+ $crmDeliveryAddress = $crmOrder['delivery']['address'];
+
+ $deliveryTypeId = $objectTypes->getTypeIdByGUID('emarket-deliveryaddress');
+ $deliveryObject = $objects->getObjectByName('Address for order ' . $order->getId());
+ if (!$deliveryObject) {
+ $deliveryObjectId = $objects->addObject('Address for order ' . $order->getId(), $deliveryTypeId);
+ $deliveryObject = $objects->getObject($deliveryObjectId);
+ }
+
+ if (!empty($crmDeliveryAddress['countryIso'])) {
+ $selector = new selector('objects');
+ $selector->types('object-type')->id($config->get('retailcrm', 'countryGuideId'));
+
+ $countries = $selector->result();
+
+ foreach ($countries as $country) {
+ /** @var umiObject $country */
+
+ $countryCode = $country->getValue('country_iso_code');
+
+ if ($crmDeliveryAddress['countryIso'] == $countryCode) {
+ $deliveryObject->getPropByName('country')->setValue($country->getId());
+ break;
+ }
+ }
+ }
+
+ if (!empty($crmDeliveryAddress['index'])) {
+ $deliveryObject->getPropByName('index')->setValue($crmDeliveryAddress['index']);
+ }
+
+ if (!empty($crmDeliveryAddress['region'])) {
+ $deliveryObject->getPropByName('region')->setValue($crmDeliveryAddress['region']);
+ }
+
+ if (!empty($crmDeliveryAddress['city'])) {
+ $deliveryObject->getPropByName('city')->setValue($crmDeliveryAddress['city']);
+ }
+
+ if (!empty($crmDeliveryAddress['street'])) {
+ $deliveryObject->getPropByName('street')->setValue($crmDeliveryAddress['street']);
+ }
+
+ if (!empty($crmDeliveryAddress['building'])) {
+ $deliveryObject->getPropByName('house')->setValue($crmDeliveryAddress['building']);
+ }
+
+ if (!empty($crmDeliveryAddress['flat'])) {
+ $deliveryObject->getPropByName('flat')->setValue($crmDeliveryAddress['flat']);
+ }
+
+ $order->getObject()->getPropByName('delivery_address')->setValue($deliveryObject->getId());
+ }
+ }
+
+ if (!$order) {
+ continue;
+ }
+
+ $regedit->setVal('//modules/RetailCRM/IgnoreObjectUpdateEvent/' . $order->getObject()->getId(), time());
+
+ if (isset($crmOrder['status'])) {
+ $relationMap = $retailcrm->getRelationMap($config->get('retailcrm', 'orderStatusMap'));
+ $umiOrderStatusCode = $retailcrm->getRelationByMap($relationMap, $crmOrder['status'], true);
+ if ($umiOrderStatusCode) {
+ // меняем дату редактирования заказа, для того, чтобы его не перехватил хенлдер и не выплюнул обратно в црм
+ $order->getObject()->setUpdateTime(time());
+ $order->setOrderStatus($umiOrderStatusCode);
+ }
+ }
+
+ $order->refresh();
+ $config->set('retailcrm', 'lastHistorySinceId', $lastChange['id']);
+ }
+ }
+ }
+
+ public static function runCustomers()
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/classes/modules/RetailCRM/classes/retailcrm/RCrmHttpClient.php b/classes/modules/RetailCRM/classes/retailcrm/RCrmHttpClient.php
new file mode 100644
index 0000000..d3038fb
--- /dev/null
+++ b/classes/modules/RetailCRM/classes/retailcrm/RCrmHttpClient.php
@@ -0,0 +1,110 @@
+
+ * @license https://opensource.org/licenses/MIT MIT License
+ * @link http://www.retailcrm.ru/docs/Developers/ApiVersion4
+ */
+class RCrmHttpClient
+{
+ const METHOD_GET = 'GET';
+ const METHOD_POST = 'POST';
+
+ protected $url;
+ protected $defaultParameters;
+
+ /**
+ * Client constructor.
+ *
+ * @param string $url api url
+ * @param array $defaultParameters array of parameters
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct($url, array $defaultParameters = array())
+ {
+ if (false === stripos($url, 'https://')) {
+ throw new InvalidArgumentException(
+ 'API schema requires HTTPS protocol'
+ );
+ }
+
+ $this->url = $url;
+ $this->defaultParameters = $defaultParameters;
+ }
+
+ /**
+ * Make HTTP request
+ *
+ * @param string $path request url
+ * @param string $method (default: 'GET')
+ * @param array $parameters (default: array())
+ *
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
+ *
+ * @throws InvalidArgumentException
+ * @throws RCrmCurlException
+ * @throws RCrmJsonException
+ *
+ * @return RCrmApiResponse
+ */
+ public function makeRequest(
+ $path,
+ $method,
+ array $parameters = array()
+ ) {
+ $allowedMethods = array(self::METHOD_GET, self::METHOD_POST);
+
+ if (!in_array($method, $allowedMethods, false)) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'Method "%s" is not valid. Allowed methods are %s',
+ $method,
+ implode(', ', $allowedMethods)
+ )
+ );
+ }
+
+ $parameters = array_merge($this->defaultParameters, $parameters);
+
+ $url = $this->url . $path;
+
+ if (self::METHOD_GET === $method && count($parameters)) {
+ $url .= '?' . http_build_query($parameters, '', '&');
+ }
+
+ $curlHandler = curl_init();
+ curl_setopt($curlHandler, CURLOPT_URL, $url);
+ curl_setopt($curlHandler, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($curlHandler, CURLOPT_FOLLOWLOCATION, 1);
+ curl_setopt($curlHandler, CURLOPT_FAILONERROR, false);
+ curl_setopt($curlHandler, CURLOPT_SSL_VERIFYPEER, false);
+ curl_setopt($curlHandler, CURLOPT_SSL_VERIFYHOST, false);
+ curl_setopt($curlHandler, CURLOPT_TIMEOUT, 30);
+ curl_setopt($curlHandler, CURLOPT_CONNECTTIMEOUT, 30);
+
+ if (self::METHOD_POST === $method) {
+ curl_setopt($curlHandler, CURLOPT_POST, true);
+ curl_setopt($curlHandler, CURLOPT_POSTFIELDS, $parameters);
+ }
+
+ $responseBody = curl_exec($curlHandler);
+ $statusCode = curl_getinfo($curlHandler, CURLINFO_HTTP_CODE);
+ $errno = curl_errno($curlHandler);
+ $error = curl_error($curlHandler);
+
+ curl_close($curlHandler);
+
+ if ($errno) {
+ throw new RCrmCurlException($error, $errno);
+ }
+
+ return new RCrmApiResponse($statusCode, $responseBody);
+ }
+}
diff --git a/classes/modules/RetailCRM/classes/retailcrm/RCrmIcml.php b/classes/modules/RetailCRM/classes/retailcrm/RCrmIcml.php
new file mode 100644
index 0000000..a784184
--- /dev/null
+++ b/classes/modules/RetailCRM/classes/retailcrm/RCrmIcml.php
@@ -0,0 +1,309 @@
+getList();
+ $domainCollection = $domainsCollectionList[1];
+ $serverProtocol = mainConfiguration::getInstance()->get('system', 'server-protocol') . '://';
+ $this->shopUrl = $serverProtocol . $domainCollection->getHost();
+ }
+
+ public function generateICML()
+ {
+ $string = '
+
+
+ ' . $this->shopName . '
+
+
+
+
+ ';
+
+ $xml = new SimpleXMLElement(
+ $string,
+ LIBXML_NOENT | LIBXML_NOCDATA | LIBXML_COMPACT | LIBXML_PARSEHUGE
+ );
+
+ $this->dd = new DOMDocument();
+ $this->dd->preserveWhiteSpace = false;
+ $this->dd->formatOutput = true;
+ $this->dd->loadXML($xml->asXML());
+
+ $this->eCategories = $this->dd->getElementsByTagName('categories')->item(0);
+ $this->eOffers = $this->dd->getElementsByTagName('offers')->item(0);
+
+ $this->addCategories();
+ $this->addOffers();
+
+ $downloadPath = __DIR__ . '/../../../../../';
+
+ if (!file_exists($downloadPath)) {
+ mkdir($downloadPath, 0755);
+ }
+
+ $this->dd->saveXML();
+ $this->dd->save($downloadPath . 'retailcrm.xml');
+ }
+
+ /**
+ *
+ */
+ private function addCategories()
+ {
+ $categories = new selector('pages');
+ $categories->types('hierarchy-type')->name('catalog', 'category');
+
+ $result = $categories->result();
+
+ foreach ($result as $category) {
+ /** @var umiHierarchyElement $category */
+
+ /** @var DOMElement $e */
+ $e = $this->eCategories->appendChild(
+ $this->dd->createElement(
+ 'category', $category->getName()
+ )
+ );
+
+ $e->setAttribute('id', $category->getId());
+
+ if ($category->getRel() > 0) {
+ $e->setAttribute('parentId', $category->getRel());
+ }
+ }
+ }
+
+ private function getObjectUrl(umiHierarchyElement $obj)
+ {
+ $url = '/' . $obj->getAltName();
+
+ $parent = new umiHierarchyElement($obj->getRel());
+ while (true) {
+ $url = '/' . $parent->getAltName() . $url;
+
+ if ($parent->getRel() != 0) {
+ $parent = new umiHierarchyElement($parent->getRel());
+ } else {
+ break;
+ }
+ }
+
+ $url = $this->shopUrl . $url;
+
+ return $url;
+ }
+
+ private function getCombinationsFromMultyArray($sourceData)
+ {
+ $sourceDataKeys = array();
+ foreach ($sourceData as $key => $value) {
+ $sourceDataKeys[] = $key;
+ }
+
+ $data = array();
+ $data[] = '';
+ for ($i = 0; $i < count($sourceData); $i++) {
+ $oldData = $data;
+ $data = array();
+
+ foreach ($oldData as $value) {
+ foreach ($sourceData[$sourceDataKeys[$i]] as $value2) {
+ $data[] = (!empty($value) ? $value . ',' : '') . $sourceDataKeys[$i] . '-' . $value2;
+ }
+ }
+ }
+
+ $resultData = array();
+ foreach ($data as $value) {
+ $items = explode(',', $value);
+ $columns = array();
+
+ foreach ($items as $item) {
+ $item = explode('-', $item);
+ $columns[$item[0]] = $item[1];
+ }
+
+ $resultData[] = $columns;
+ }
+
+ return $resultData;
+ }
+
+ private function addOffers()
+ {
+ $offers = new selector('pages');
+ $offers->types('hierarchy-type')->name('catalog', 'object');
+
+ $result = $offers->result();
+
+ foreach ($result as $offer) {
+ /** @var umiHierarchyElement $offer */
+
+ $objects = umiObjectsCollection::getInstance();
+
+ $offerObject = new umiObject($offer->getObjectId());
+
+ /** @var umiFieldsGroup $optionsObject */
+ $optionsObject = $offerObject->getType()->getFieldsGroupByName('catalog_option_props');
+
+ $options = array();
+ $optionValues = array();
+ $optionGroups = array();
+ $optionPrices = array();
+ foreach ($optionsObject->getFields() as $optionField) {
+ /** @var umiField $optionField */
+
+ $optionGroups[$optionField->getId()] = $optionField;
+
+ $values = $offerObject->getValue($optionField->getName());
+
+ foreach ($values as $value) {
+ $valueObject = $objects->getObject($value['rel']);
+ $options[$optionField->getId()][] = $valueObject->getId();
+
+ $optionPrices[$valueObject->getId()] = $value['float'];
+ $optionValues[$valueObject->getId()] = $valueObject;
+ }
+ }
+
+ if (count($options)) {
+ $offerOptions = $this->getCombinationsFromMultyArray($options);
+ } else {
+ // Если нет опционных товаров(товарных предложений) передаём массив с 1 пустым элементом - базовый товар
+ $offerOptions = array('');
+ }
+
+ foreach ($offerOptions as $offerOption) {
+ if (!empty($offerOption)) {
+ $options = array();
+ foreach ($offerOption as $offerOptionId => $offerOptionValue) {
+ $options[] = $offerOptionId . '_' . $offerOptionValue;
+ }
+
+ $offerId = $offer->getId() . '#' . implode('-', $options);
+ } else {
+ $offerId = $offer->getId();
+ }
+
+ /** @var DOMElement $e */
+ $e = $this->eOffers->appendChild($this->dd->createElement('offer'));
+ $e->setAttribute('id', $offerId);
+ $e->setAttribute('productId', $offer->getId());
+ $quantity = $offerObject->getPropByName('common_quantity')->getValue();
+ $e->setAttribute('quantity', !empty($quantity) ? $quantity : 0);
+
+ /**
+ * Offer activity
+ */
+ $activity = $offer->getIsActive() == 1 ? 'Y' : 'N';
+ $e->appendChild(
+ $this->dd->createElement('productActivity')
+ )->appendChild(
+ $this->dd->createTextNode($activity)
+ );
+
+ /**
+ * Offer category
+ */
+ $e->appendChild($this->dd->createElement('categoryId'))
+ ->appendChild(
+ $this->dd->createTextNode($offer->getRel())
+ );
+
+ /**
+ * Name & price
+ */
+ if (!empty($offerOption)) {
+ $options = array();
+ foreach ($offerOption as $offerOptionId => $offerOptionValue) {
+ $options[] = $optionGroups[$offerOptionId]->getTitle() . ': ' . $optionValues[$offerOptionValue]->getName();
+ }
+ $offerName = $offer->getName() . ' (' . implode(', ', $options) . ')';
+ } else {
+ $offerName = $offer->getName();
+ }
+
+ $e->appendChild($this->dd->createElement('name'))
+ ->appendChild($this->dd->createTextNode($offerName));
+
+ $e->appendChild($this->dd->createElement('productName'))
+ ->appendChild($this->dd->createTextNode($offer->getName()));
+
+ $price = $offerObject->getPropByName('price')->getValue();
+
+ if (!empty($offerOption)) {
+ foreach ($offerOption as $offerOptionId => $offerOptionValue) {
+ $price += $optionPrices[$offerOptionValue];
+ }
+ }
+
+ $e->appendChild($this->dd->createElement('price'))
+ ->appendChild($this->dd->createTextNode($price));
+
+ /**
+ * Options
+ */
+ if (!empty($offerOption)) {
+ foreach ($offerOption as $offerOptionId => $offerOptionValue) {
+ $option = $this->dd->createElement('param');
+ $option->setAttribute('code', $optionGroups[$offerOptionId]->getName());
+ $option->setAttribute('name', $optionGroups[$offerOptionId]->getTitle());
+ $option->appendChild($this->dd->createTextNode($optionValues[$offerOptionValue]->getName()));
+ $e->appendChild($option);
+ }
+ }
+
+ /**
+ * Image
+ */
+ /** @var umiImageFile $photo */
+ if ($offerObject->getPropByName('images') !== null) {
+ $photos = $offerObject->getPropByName('images')->getValue();
+
+ if (is_array($photos) && count($photos)) {
+ $photo = reset($photos);
+ $photoPath = $this->shopUrl . $photo->getFilePath(true);
+
+ $e->appendChild($this->dd->createElement('picture'))
+ ->appendChild($this->dd->createTextNode($photoPath));
+ }
+ }
+
+ /**
+ * Url
+ */
+ $url = $this->getObjectUrl($offer);
+ $e->appendChild($this->dd->createElement('url'))
+ ->appendChild(
+ $this->dd->createTextNode($url)
+ );
+
+ /**
+ * Additional characteristics
+ */
+ if ($offerObject->getPropByName('weight')) {
+ $weight = $this->dd->createElement('param');
+ $weight->setAttribute('code', 'weight');
+ $weight->setAttribute('name', 'Вес');
+ $weight->appendChild($this->dd->createTextNode($offerObject->getPropByName('weight')->getValue() * 1000));
+ $e->appendChild($weight);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/classes/modules/RetailCRM/classes/retailcrm/RCrmJsonException.php b/classes/modules/RetailCRM/classes/retailcrm/RCrmJsonException.php
new file mode 100644
index 0000000..7a34a34
--- /dev/null
+++ b/classes/modules/RetailCRM/classes/retailcrm/RCrmJsonException.php
@@ -0,0 +1,5 @@
+api = new RCrmApiClient($url, $key);
+ $this->log = $log;
+ }
+
+ public function __call($method, $arguments)
+ {
+ $accessLog = date('H:m:i') . ' [' . $method . '] -> ' . json_encode($arguments) . "\n";
+ error_log($accessLog, 3, $this->log);
+
+ try {
+ $response = call_user_func_array(array($this->api, $method), $arguments);
+ if (!$response->isSuccessful()) {
+ error_log("[$method] " . $response->getErrorMsg() . "\n", 3, $this->log);
+ if (isset($response['errors'])) {
+ $error = implode("\n", $response['errors']);
+ error_log($error . "\n", 3, $this->log);
+ }
+ $response = false;
+ }
+ return $response;
+ } catch (RCrmCurlException $e) {
+ error_log("[$method] " . $e->getMessage() . "\n", 3, $this->log);
+ return false;
+ } catch (RCrmJsonException $e) {
+ error_log("[$method] " . $e->getMessage() . "\n", 3, $this->log);
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/classes/modules/RetailCRM/classes/retailcrm/RetailcrmApiClient.php b/classes/modules/RetailCRM/classes/retailcrm/RetailcrmApiClient.php
new file mode 100644
index 0000000..a479845
--- /dev/null
+++ b/classes/modules/RetailCRM/classes/retailcrm/RetailcrmApiClient.php
@@ -0,0 +1,811 @@
+client = new RetailcrmHttpClient($url, array('apiKey' => $apiKey));
+ $this->siteCode = $site;
+ }
+
+ /**
+ * Create a order
+ *
+ * @param array $order
+ * @param string $site (default: null)
+ * @return RetailcrmApiResponse
+ */
+ public function ordersCreate(array $order, $site = null)
+ {
+ if (!sizeof($order)) {
+ throw new InvalidArgumentException('Parameter `order` must contains a data');
+ }
+
+ return $this->client->makeRequest("/orders/create", RetailcrmHttpClient::METHOD_POST, $this->fillSite($site, array(
+ 'order' => json_encode($order)
+ )));
+ }
+
+ /**
+ * Edit a order
+ *
+ * @param array $order
+ * @param string $by
+ * @param string $site (default: null)
+ * @return RetailcrmApiResponse
+ */
+ public function ordersEdit(array $order, $by = 'externalId', $site = null)
+ {
+ if (!sizeof($order)) {
+ throw new InvalidArgumentException('Parameter `order` must contains a data');
+ }
+
+ $this->checkIdParameter($by);
+
+ if (!isset($order[$by])) {
+ throw new InvalidArgumentException(sprintf('Order array must contain the "%s" parameter.', $by));
+ }
+
+ return $this->client->makeRequest(
+ "/orders/" . $order[$by] . "/edit",
+ RetailcrmHttpClient::METHOD_POST,
+ $this->fillSite($site, array(
+ 'order' => json_encode($order),
+ 'by' => $by,
+ ))
+ );
+ }
+
+ /**
+ * Upload array of the orders
+ *
+ * @param array $orders
+ * @param string $site (default: null)
+ * @return RetailcrmApiResponse
+ */
+ public function ordersUpload(array $orders, $site = null)
+ {
+ if (!sizeof($orders)) {
+ throw new InvalidArgumentException('Parameter `orders` must contains array of the orders');
+ }
+
+ return $this->client->makeRequest("/orders/upload", RetailcrmHttpClient::METHOD_POST, $this->fillSite($site, array(
+ 'orders' => json_encode($orders),
+ )));
+ }
+
+ /**
+ * Get order by id or externalId
+ *
+ * @param string $id
+ * @param string $by (default: 'externalId')
+ * @param string $site (default: null)
+ * @return RetailcrmApiResponse
+ */
+ public function ordersGet($id, $by = 'externalId', $site = null)
+ {
+ $this->checkIdParameter($by);
+
+ return $this->client->makeRequest("/orders/$id", RetailcrmHttpClient::METHOD_GET, $this->fillSite($site, array(
+ 'by' => $by
+ )));
+ }
+
+ /**
+ * Returns a orders history
+ *
+ * @param DateTime $startDate (default: null)
+ * @param DateTime $endDate (default: null)
+ * @param int $limit (default: 100)
+ * @param int $offset (default: 0)
+ * @param bool $skipMyChanges (default: true)
+ * @return RetailcrmApiResponse
+ */
+ public function ordersHistory(
+ DateTime $startDate = null,
+ DateTime $endDate = null,
+ $limit = 100,
+ $offset = 0,
+ $skipMyChanges = true
+ ) {
+ $parameters = array();
+
+ if ($startDate) {
+ $parameters['startDate'] = $startDate->format('Y-m-d H:i:s');
+ }
+ if ($endDate) {
+ $parameters['endDate'] = $endDate->format('Y-m-d H:i:s');
+ }
+ if ($limit) {
+ $parameters['limit'] = (int) $limit;
+ }
+ if ($offset) {
+ $parameters['offset'] = (int) $offset;
+ }
+ if ($skipMyChanges) {
+ $parameters['skipMyChanges'] = (bool) $skipMyChanges;
+ }
+
+ return $this->client->makeRequest('/orders/history', RetailcrmHttpClient::METHOD_GET, $parameters);
+ }
+
+ /**
+ * Returns filtered orders list
+ *
+ * @param array $filter (default: array())
+ * @param int $page (default: null)
+ * @param int $limit (default: null)
+ * @return RetailcrmApiResponse
+ */
+ public function ordersList(array $filter = array(), $page = null, $limit = null)
+ {
+ $parameters = array();
+
+ if (sizeof($filter)) {
+ $parameters['filter'] = $filter;
+ }
+ if (null !== $page) {
+ $parameters['page'] = (int) $page;
+ }
+ if (null !== $limit) {
+ $parameters['limit'] = (int) $limit;
+ }
+
+ return $this->client->makeRequest('/orders', RetailcrmHttpClient::METHOD_GET, $parameters);
+ }
+
+ /**
+ * Returns statuses of the orders
+ *
+ * @param array $ids (default: array())
+ * @param array $externalIds (default: array())
+ * @return RetailcrmApiResponse
+ */
+ public function ordersStatuses(array $ids = array(), array $externalIds = array())
+ {
+ $parameters = array();
+
+ if (sizeof($ids)) {
+ $parameters['ids'] = $ids;
+ }
+ if (sizeof($externalIds)) {
+ $parameters['externalIds'] = $externalIds;
+ }
+
+ return $this->client->makeRequest('/orders/statuses', RetailcrmHttpClient::METHOD_GET, $parameters);
+ }
+
+ /**
+ * Save order IDs' (id and externalId) association in the CRM
+ *
+ * @param array $ids
+ * @return RetailcrmApiResponse
+ */
+ public function ordersFixExternalIds(array $ids)
+ {
+ if (!sizeof($ids)) {
+ throw new InvalidArgumentException('Method parameter must contains at least one IDs pair');
+ }
+
+ return $this->client->makeRequest("/orders/fix-external-ids", RetailcrmHttpClient::METHOD_POST, array(
+ 'orders' => json_encode($ids),
+ ));
+ }
+
+ /**
+ * Get orders assembly history
+ *
+ * @param array $filter (default: array())
+ * @param int $page (default: null)
+ * @param int $limit (default: null)
+ * @return RetailcrmApiResponse
+ */
+ public function ordersPacksHistory(array $filter = array(), $page = null, $limit = null)
+ {
+ $parameters = array();
+
+ if (sizeof($filter)) {
+ $parameters['filter'] = $filter;
+ }
+ if (null !== $page) {
+ $parameters['page'] = (int) $page;
+ }
+ if (null !== $limit) {
+ $parameters['limit'] = (int) $limit;
+ }
+
+ return $this->client->makeRequest('/orders/packs/history', RetailcrmHttpClient::METHOD_GET, $parameters);
+ }
+
+ /**
+ * Create a customer
+ *
+ * @param array $customer
+ * @param string $site (default: null)
+ * @return RetailcrmApiResponse
+ */
+ public function customersCreate(array $customer, $site = null)
+ {
+ if (!sizeof($customer)) {
+ throw new InvalidArgumentException('Parameter `customer` must contains a data');
+ }
+
+ return $this->client->makeRequest("/customers/create", RetailcrmHttpClient::METHOD_POST, $this->fillSite($site, array(
+ 'customer' => json_encode($customer)
+ )));
+ }
+
+ /**
+ * Edit a customer
+ *
+ * @param array $customer
+ * @param string $by (default: 'externalId')
+ * @param string $site (default: null)
+ * @return RetailcrmApiResponse
+ */
+ public function customersEdit(array $customer, $by = 'externalId', $site = null)
+ {
+ if (!sizeof($customer)) {
+ throw new InvalidArgumentException('Parameter `customer` must contains a data');
+ }
+
+ $this->checkIdParameter($by);
+
+ if (!isset($customer[$by])) {
+ throw new InvalidArgumentException(sprintf('Customer array must contain the "%s" parameter.', $by));
+ }
+
+ return $this->client->makeRequest(
+ "/customers/" . $customer[$by] . "/edit",
+ RetailcrmHttpClient::METHOD_POST,
+ $this->fillSite(
+ $site,
+ array(
+ 'customer' => json_encode($customer),
+ 'by' => $by
+ )
+ )
+ );
+ }
+
+ /**
+ * Upload array of the customers
+ *
+ * @param array $customers
+ * @param string $site (default: null)
+ * @return RetailcrmApiResponse
+ */
+ public function customersUpload(array $customers, $site = null)
+ {
+ if (!sizeof($customers)) {
+ throw new InvalidArgumentException('Parameter `customers` must contains array of the customers');
+ }
+
+ return $this->client->makeRequest("/customers/upload", RetailcrmHttpClient::METHOD_POST, $this->fillSite($site, array(
+ 'customers' => json_encode($customers),
+ )));
+ }
+
+ /**
+ * Get customer by id or externalId
+ *
+ * @param string $id
+ * @param string $by (default: 'externalId')
+ * @param string $site (default: null)
+ * @return RetailcrmApiResponse
+ */
+ public function customersGet($id, $by = 'externalId', $site = null)
+ {
+ $this->checkIdParameter($by);
+
+ return $this->client->makeRequest("/customers/$id", RetailcrmHttpClient::METHOD_GET, $this->fillSite($site, array(
+ 'by' => $by
+ )));
+ }
+
+ /**
+ * Returns filtered customers list
+ *
+ * @param array $filter (default: array())
+ * @param int $page (default: null)
+ * @param int $limit (default: null)
+ * @return RetailcrmApiResponse
+ */
+ public function customersList(array $filter = array(), $page = null, $limit = null)
+ {
+ $parameters = array();
+
+ if (sizeof($filter)) {
+ $parameters['filter'] = $filter;
+ }
+ if (null !== $page) {
+ $parameters['page'] = (int) $page;
+ }
+ if (null !== $limit) {
+ $parameters['limit'] = (int) $limit;
+ }
+
+ return $this->client->makeRequest('/customers', RetailcrmHttpClient::METHOD_GET, $parameters);
+ }
+
+ /**
+ * Save customer IDs' (id and externalId) association in the CRM
+ *
+ * @param array $ids
+ * @return RetailcrmApiResponse
+ */
+ public function customersFixExternalIds(array $ids)
+ {
+ if (!sizeof($ids)) {
+ throw new InvalidArgumentException('Method parameter must contains at least one IDs pair');
+ }
+
+ return $this->client->makeRequest("/customers/fix-external-ids", RetailcrmHttpClient::METHOD_POST, array(
+ 'customers' => json_encode($ids),
+ ));
+ }
+
+ /**
+ * Get purchace prices & stock balance
+ *
+ * @param array $filter (default: array())
+ * @param int $page (default: null)
+ * @param int $limit (default: null)
+ * @param string $site (default: null)
+ * @return RetailcrmApiResponse
+ */
+ public function storeInventories(array $filter = array(), $page = null, $limit = null, $site = null)
+ {
+ $parameters = array();
+
+ if (sizeof($filter)) {
+ $parameters['filter'] = $filter;
+ }
+ if (null !== $page) {
+ $parameters['page'] = (int) $page;
+ }
+ if (null !== $limit) {
+ $parameters['limit'] = (int) $limit;
+ }
+
+ return $this->client->makeRequest('/store/inventories', RetailcrmHttpClient::METHOD_GET, $this->fillSite($site, $parameters));
+ }
+
+ /**
+ * Upload store inventories
+ *
+ * @param array $offers
+ * @param string $site (default: null)
+ * @return RetailcrmApiResponse
+ */
+ public function storeInventoriesUpload(array $offers, $site = null)
+ {
+ if (!sizeof($offers)) {
+ throw new InvalidArgumentException('Parameter `offers` must contains array of the customers');
+ }
+
+ return $this->client->makeRequest(
+ "/store/inventories/upload",
+ RetailcrmHttpClient::METHOD_POST,
+ $this->fillSite($site, array('offers' => json_encode($offers)))
+ );
+ }
+
+ /**
+ * Returns deliveryServices list
+ *
+ * @return RetailcrmApiResponse
+ */
+ public function deliveryServicesList()
+ {
+ return $this->client->makeRequest('/reference/delivery-services', RetailcrmHttpClient::METHOD_GET);
+ }
+
+ /**
+ * Returns deliveryTypes list
+ *
+ * @return RetailcrmApiResponse
+ */
+ public function deliveryTypesList()
+ {
+ return $this->client->makeRequest('/reference/delivery-types', RetailcrmHttpClient::METHOD_GET);
+ }
+
+ /**
+ * Returns orderMethods list
+ *
+ * @return RetailcrmApiResponse
+ */
+ public function orderMethodsList()
+ {
+ return $this->client->makeRequest('/reference/order-methods', RetailcrmHttpClient::METHOD_GET);
+ }
+
+ /**
+ * Returns orderTypes list
+ *
+ * @return RetailcrmApiResponse
+ */
+ public function orderTypesList()
+ {
+ return $this->client->makeRequest('/reference/order-types', RetailcrmHttpClient::METHOD_GET);
+ }
+
+ /**
+ * Returns paymentStatuses list
+ *
+ * @return RetailcrmApiResponse
+ */
+ public function paymentStatusesList()
+ {
+ return $this->client->makeRequest('/reference/payment-statuses', RetailcrmHttpClient::METHOD_GET);
+ }
+
+ /**
+ * Returns paymentTypes list
+ *
+ * @return RetailcrmApiResponse
+ */
+ public function paymentTypesList()
+ {
+ return $this->client->makeRequest('/reference/payment-types', RetailcrmHttpClient::METHOD_GET);
+ }
+
+ /**
+ * Returns productStatuses list
+ *
+ * @return RetailcrmApiResponse
+ */
+ public function productStatusesList()
+ {
+ return $this->client->makeRequest('/reference/product-statuses', RetailcrmHttpClient::METHOD_GET);
+ }
+
+ /**
+ * Returns statusGroups list
+ *
+ * @return RetailcrmApiResponse
+ */
+ public function statusGroupsList()
+ {
+ return $this->client->makeRequest('/reference/status-groups', RetailcrmHttpClient::METHOD_GET);
+ }
+
+ /**
+ * Returns statuses list
+ *
+ * @return RetailcrmApiResponse
+ */
+ public function statusesList()
+ {
+ return $this->client->makeRequest('/reference/statuses', RetailcrmHttpClient::METHOD_GET);
+ }
+
+ /**
+ * Returns sites list
+ *
+ * @return RetailcrmApiResponse
+ */
+ public function sitesList()
+ {
+ return $this->client->makeRequest('/reference/sites', RetailcrmHttpClient::METHOD_GET);
+ }
+
+ /**
+ * Returns stores list
+ *
+ * @return RetailcrmApiResponse
+ */
+ public function storesList()
+ {
+ return $this->client->makeRequest('/reference/stores', RetailcrmHttpClient::METHOD_GET);
+ }
+
+ /**
+ * Edit deliveryService
+ *
+ * @param array $data delivery service data
+ * @return RetailcrmApiResponse
+ */
+ public function deliveryServicesEdit(array $data)
+ {
+ if (!isset($data['code'])) {
+ throw new InvalidArgumentException('Data must contain "code" parameter.');
+ }
+
+ return $this->client->makeRequest(
+ '/reference/delivery-services/' . $data['code'] . '/edit',
+ RetailcrmHttpClient::METHOD_POST,
+ array(
+ 'deliveryService' => json_encode($data)
+ )
+ );
+ }
+
+ /**
+ * Edit deliveryType
+ *
+ * @param array $data delivery type data
+ * @return RetailcrmApiResponse
+ */
+ public function deliveryTypesEdit(array $data)
+ {
+ if (!isset($data['code'])) {
+ throw new InvalidArgumentException('Data must contain "code" parameter.');
+ }
+
+ return $this->client->makeRequest(
+ '/reference/delivery-types/' . $data['code'] . '/edit',
+ RetailcrmHttpClient::METHOD_POST,
+ array(
+ 'deliveryType' => json_encode($data)
+ )
+ );
+ }
+
+ /**
+ * Edit orderMethod
+ *
+ * @param array $data order method data
+ * @return RetailcrmApiResponse
+ */
+ public function orderMethodsEdit(array $data)
+ {
+ if (!isset($data['code'])) {
+ throw new InvalidArgumentException('Data must contain "code" parameter.');
+ }
+
+ return $this->client->makeRequest(
+ '/reference/order-methods/' . $data['code'] . '/edit',
+ RetailcrmHttpClient::METHOD_POST,
+ array(
+ 'orderMethod' => json_encode($data)
+ )
+ );
+ }
+
+ /**
+ * Edit orderType
+ *
+ * @param array $data order type data
+ * @return RetailcrmApiResponse
+ */
+ public function orderTypesEdit(array $data)
+ {
+ if (!isset($data['code'])) {
+ throw new InvalidArgumentException('Data must contain "code" parameter.');
+ }
+
+ return $this->client->makeRequest(
+ '/reference/order-types/' . $data['code'] . '/edit',
+ RetailcrmHttpClient::METHOD_POST,
+ array(
+ 'orderType' => json_encode($data)
+ )
+ );
+ }
+
+ /**
+ * Edit paymentStatus
+ *
+ * @param array $data payment status data
+ * @return RetailcrmApiResponse
+ */
+ public function paymentStatusesEdit(array $data)
+ {
+ if (!isset($data['code'])) {
+ throw new InvalidArgumentException('Data must contain "code" parameter.');
+ }
+
+ return $this->client->makeRequest(
+ '/reference/payment-statuses/' . $data['code'] . '/edit',
+ RetailcrmHttpClient::METHOD_POST,
+ array(
+ 'paymentStatus' => json_encode($data)
+ )
+ );
+ }
+
+ /**
+ * Edit paymentType
+ *
+ * @param array $data payment type data
+ * @return RetailcrmApiResponse
+ */
+ public function paymentTypesEdit(array $data)
+ {
+ if (!isset($data['code'])) {
+ throw new InvalidArgumentException('Data must contain "code" parameter.');
+ }
+
+ return $this->client->makeRequest(
+ '/reference/payment-types/' . $data['code'] . '/edit',
+ RetailcrmHttpClient::METHOD_POST,
+ array(
+ 'paymentType' => json_encode($data)
+ )
+ );
+ }
+
+ /**
+ * Edit productStatus
+ *
+ * @param array $data product status data
+ * @return RetailcrmApiResponse
+ */
+ public function productStatusesEdit(array $data)
+ {
+ if (!isset($data['code'])) {
+ throw new InvalidArgumentException('Data must contain "code" parameter.');
+ }
+
+ return $this->client->makeRequest(
+ '/reference/product-statuses/' . $data['code'] . '/edit',
+ RetailcrmHttpClient::METHOD_POST,
+ array(
+ 'productStatus' => json_encode($data)
+ )
+ );
+ }
+
+ /**
+ * Edit order status
+ *
+ * @param array $data status data
+ * @return RetailcrmApiResponse
+ */
+ public function statusesEdit(array $data)
+ {
+ if (!isset($data['code'])) {
+ throw new InvalidArgumentException('Data must contain "code" parameter.');
+ }
+
+ return $this->client->makeRequest(
+ '/reference/statuses/' . $data['code'] . '/edit',
+ RetailcrmHttpClient::METHOD_POST,
+ array(
+ 'status' => json_encode($data)
+ )
+ );
+ }
+
+ /**
+ * Edit site
+ *
+ * @param array $data site data
+ * @return RetailcrmApiResponse
+ */
+ public function sitesEdit(array $data)
+ {
+ if (!isset($data['code'])) {
+ throw new InvalidArgumentException('Data must contain "code" parameter.');
+ }
+
+ return $this->client->makeRequest(
+ '/reference/sites/' . $data['code'] . '/edit',
+ RetailcrmHttpClient::METHOD_POST,
+ array(
+ 'site' => json_encode($data)
+ )
+ );
+ }
+
+ /**
+ * Edit store
+ *
+ * @param array $data site data
+ * @return RetailcrmApiResponse
+ */
+ public function storesEdit(array $data)
+ {
+ if (!isset($data['code'])) {
+ throw new InvalidArgumentException('Data must contain "code" parameter.');
+ }
+
+ if (!isset($data['name'])) {
+ throw new InvalidArgumentException('Data must contain "name" parameter.');
+ }
+
+ return $this->client->makeRequest(
+ '/reference/stores/' . $data['code'] . '/edit',
+ RetailcrmHttpClient::METHOD_POST,
+ array(
+ 'store' => json_encode($data)
+ )
+ );
+ }
+
+ /**
+ * Update CRM basic statistic
+ *
+ * @return RetailcrmApiResponse
+ */
+ public function statisticUpdate()
+ {
+ return $this->client->makeRequest('/statistic/update', RetailcrmHttpClient::METHOD_GET);
+ }
+
+ /**
+ * Return current site
+ *
+ * @return string
+ */
+ public function getSite()
+ {
+ return $this->siteCode;
+ }
+
+ /**
+ * Set site
+ *
+ * @param string $site
+ * @return void
+ */
+ public function setSite($site)
+ {
+ $this->siteCode = $site;
+ }
+
+ /**
+ * Check ID parameter
+ *
+ * @param string $by
+ * @return bool
+ */
+ protected function checkIdParameter($by)
+ {
+ $allowedForBy = array('externalId', 'id');
+ if (!in_array($by, $allowedForBy)) {
+ throw new InvalidArgumentException(sprintf(
+ 'Value "%s" for parameter "by" is not valid. Allowed values are %s.',
+ $by,
+ implode(', ', $allowedForBy)
+ ));
+ }
+
+ return true;
+ }
+
+ /**
+ * Fill params by site value
+ *
+ * @param string $site
+ * @param array $params
+ * @return array
+ */
+ protected function fillSite($site, array $params)
+ {
+ if ($site) {
+ $params['site'] = $site;
+ } elseif ($this->siteCode) {
+ $params['site'] = $this->siteCode;
+ }
+
+ return $params;
+ }
+}
\ No newline at end of file
diff --git a/classes/modules/RetailCRM/classes/retailcrm/RetailcrmApiResponse.php b/classes/modules/RetailCRM/classes/retailcrm/RetailcrmApiResponse.php
new file mode 100644
index 0000000..6d169d2
--- /dev/null
+++ b/classes/modules/RetailCRM/classes/retailcrm/RetailcrmApiResponse.php
@@ -0,0 +1,122 @@
+statusCode = (int) $statusCode;
+
+ if (!empty($responseBody)) {
+ $response = json_decode($responseBody, true);
+
+ if (!$response && JSON_ERROR_NONE !== ($error = json_last_error())) {
+ throw new InvalidJsonException(
+ "Invalid JSON in the API response body. Error code #$error",
+ $error
+ );
+ }
+
+ $this->response = $response;
+ }
+ }
+
+ /**
+ * Return HTTP response status code
+ *
+ * @return int
+ */
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+
+ /**
+ * HTTP request was successful
+ *
+ * @return bool
+ */
+ public function isSuccessful()
+ {
+ return $this->statusCode < 400;
+ }
+
+ /**
+ * Allow to access for the property throw class method
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function __call($name, $arguments)
+ {
+ // convert getSomeProperty to someProperty
+ $propertyName = strtolower(substr($name, 3, 1)) . substr($name, 4);
+
+ if (!isset($this->response[$propertyName])) {
+ throw new InvalidArgumentException("Method \"$name\" not found");
+ }
+
+ return $this->response[$propertyName];
+ }
+
+ /**
+ * Allow to access for the property throw object property
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ if (!isset($this->response[$name])) {
+ throw new InvalidArgumentException("Property \"$name\" not found");
+ }
+
+ return $this->response[$name];
+ }
+
+ /**
+ * @param mixed $offset
+ * @param mixed $value
+ */
+ public function offsetSet($offset, $value)
+ {
+ throw new BadMethodCallException('This activity not allowed');
+ }
+
+ /**
+ * @param mixed $offset
+ */
+ public function offsetUnset($offset)
+ {
+ throw new BadMethodCallException('This call not allowed');
+ }
+
+ /**
+ * @param mixed $offset
+ * @return bool
+ */
+ public function offsetExists($offset)
+ {
+ return isset($this->response[$offset]);
+ }
+
+ /**
+ * @param mixed $offset
+ * @return mixed
+ */
+ public function offsetGet($offset)
+ {
+ if (!isset($this->response[$offset])) {
+ throw new InvalidArgumentException("Property \"$offset\" not found");
+ }
+
+ return $this->response[$offset];
+ }
+}
\ No newline at end of file
diff --git a/classes/modules/RetailCRM/classes/retailcrm/RetailcrmHelpers.php b/classes/modules/RetailCRM/classes/retailcrm/RetailcrmHelpers.php
new file mode 100644
index 0000000..aad9620
--- /dev/null
+++ b/classes/modules/RetailCRM/classes/retailcrm/RetailcrmHelpers.php
@@ -0,0 +1,41 @@
+ ', $mapItem);
+ $map[$mapItem[0]] = $mapItem[1];
+ }
+
+ return $map;
+ }
+
+ /**
+ * @param $map array
+ * @param $item string
+ * @param $reversed bool
+ * @return string|null
+ */
+ public function getRelationByMap($map, $item, $reversed = false) {
+ if(!$reversed) {
+ if(isset($map[$item]) && !empty($map[$item]))
+ return $map[$item];
+ else
+ return null;
+ } else {
+ foreach ($map as $umiStatusOrder => $crmStatusOrder) {
+ if($crmStatusOrder == $item)
+ return $umiStatusOrder;
+ }
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/classes/modules/RetailCRM/classes/retailcrm/RetailcrmHttpClient.php b/classes/modules/RetailCRM/classes/retailcrm/RetailcrmHttpClient.php
new file mode 100644
index 0000000..820d518
--- /dev/null
+++ b/classes/modules/RetailCRM/classes/retailcrm/RetailcrmHttpClient.php
@@ -0,0 +1,113 @@
+url = $url;
+ $this->defaultParameters = $defaultParameters;
+ $this->retry = 0;
+ }
+
+ /**
+ * Make HTTP request
+ *
+ * @param string $path
+ * @param string $method (default: 'GET')
+ * @param array $parameters (default: array())
+ * @param int $timeout
+ * @param bool $verify
+ * @param bool $debug
+ * @return RetailcrmApiResponse
+ */
+ public function makeRequest(
+ $path,
+ $method,
+ array $parameters = array(),
+ $timeout = 30,
+ $verify = false,
+ $debug = false
+ ) {
+ $allowedMethods = array(self::METHOD_GET, self::METHOD_POST);
+ if (!in_array($method, $allowedMethods)) {
+ throw new InvalidArgumentException(sprintf(
+ 'Method "%s" is not valid. Allowed methods are %s',
+ $method,
+ implode(', ', $allowedMethods)
+ ));
+ }
+
+ $parameters = array_merge($this->defaultParameters, $parameters);
+
+ $url = $this->url . $path;
+
+ if (self::METHOD_GET === $method && sizeof($parameters)) {
+ $url .= '?' . http_build_query($parameters, '', '&');
+ }
+
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
+ curl_setopt($ch, CURLOPT_FAILONERROR, false);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $verify);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $verify);
+
+ if (!$debug) {
+ curl_setopt($ch, CURLOPT_TIMEOUT, (int) $timeout);
+ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, (int) $timeout);
+ } else {
+ curl_setopt($ch, CURLOPT_TIMEOUT_MS, (int) $timeout + ($this->retry * 2000));
+ }
+
+ if (self::METHOD_POST === $method) {
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $parameters);
+ }
+
+ $responseBody = curl_exec($ch);
+ $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ $errno = curl_errno($ch);
+ $error = curl_error($ch);
+
+ curl_close($ch);
+
+ if ($errno && in_array($errno, array(6, 7, 28, 34, 35)) && $this->retry < 3) {
+ $errno = null;
+ $error = null;
+ $this->retry += 1;
+ $this->makeRequest(
+ $path,
+ $method,
+ $parameters,
+ $timeout,
+ $verify,
+ $debug
+ );
+ }
+
+ if ($errno) {
+ throw new CurlException($error, $errno);
+ }
+
+ return new RetailcrmApiResponse($statusCode, $responseBody);
+ }
+
+ public function getRetry()
+ {
+ return $this->retry;
+ }
+}
\ No newline at end of file
diff --git a/classes/modules/RetailCRM/classes/retailcrm/RetailcrmProxy.php b/classes/modules/RetailCRM/classes/retailcrm/RetailcrmProxy.php
new file mode 100644
index 0000000..e866baf
--- /dev/null
+++ b/classes/modules/RetailCRM/classes/retailcrm/RetailcrmProxy.php
@@ -0,0 +1,39 @@
+api = new RetailcrmApiClient($url, $key);
+ $this->log = $log;
+ }
+ public function __call($method, $arguments)
+ {
+ $accessLog = date('H:m:i') . ' [' . $method . '] -> ' . json_encode($arguments) . "\n";
+ error_log($accessLog, 3, $this->log);
+
+ try {
+ $response = call_user_func_array(array($this->api, $method), $arguments);
+ if (!$response->isSuccessful()) {
+ error_log("[$method] " . $response->getErrorMsg() . "\n", 3, $this->log);
+ if (isset($response['errors'])) {
+ $error = implode("\n", $response['errors']);
+ error_log($error . "\n", 3, $this->log);
+ }
+ $response = false;
+ }
+ return $response;
+ } catch (CurlException $e) {
+ error_log("[$method] " . $e->getMessage() . "\n", 3, $this->log);
+ return false;
+ } catch (InvalidJsonException $e) {
+ error_log("[$method] " . $e->getMessage() . "\n", 3, $this->log);
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/classes/modules/RetailCRM/classes/retailcrm/icml.php b/classes/modules/RetailCRM/classes/retailcrm/icml.php
new file mode 100644
index 0000000..cc20895
--- /dev/null
+++ b/classes/modules/RetailCRM/classes/retailcrm/icml.php
@@ -0,0 +1,294 @@
+getList();
+ $domainCollection = $domainsCollectionList[1];
+ $serverProtocol = mainConfiguration::getInstance()->get('system', 'server-protocol') . '://';
+ $this->shopUrl = $serverProtocol . $domainCollection->getHost();
+ }
+
+ public function generateICML()
+ {
+ $string = '
+
+
+ '.$this->shopName.'
+
+
+
+
+ ';
+ $xml = new SimpleXMLElement(
+ $string,
+ LIBXML_NOENT |LIBXML_NOCDATA | LIBXML_COMPACT | LIBXML_PARSEHUGE
+ );
+ $this->dd = new DOMDocument();
+ $this->dd->preserveWhiteSpace = false;
+ $this->dd->formatOutput = true;
+ $this->dd->loadXML($xml->asXML());
+ $this->eCategories = $this->dd
+ ->getElementsByTagName('categories')->item(0);
+ $this->eOffers = $this->dd
+ ->getElementsByTagName('offers')->item(0);
+
+ $this->addCategories();
+ $this->addOffers();
+ $this->dd->saveXML();
+ $downloadPath = __DIR__ . '/../../../../../';
+ if (!file_exists($downloadPath)) {
+ mkdir($downloadPath, 0755);
+ }
+ $this->dd->save($downloadPath . 'retailcrm.xml');
+ }
+
+ /**
+ *
+ */
+ private function addCategories()
+ {
+ $categories = new selector('pages');
+ $categories->types('hierarchy-type')->name('catalog', 'category');
+
+ $result = $categories->result();
+
+ foreach($result as $category) {
+ /** @var umiHierarchyElement $category */
+
+ /** @var DOMElement $e */
+ $e = $this->eCategories->appendChild(
+ $this->dd->createElement(
+ 'category', $category->getName()
+ )
+ );
+ $e->setAttribute('id', $category->getId());
+ if ($category->getRel() > 0) {
+ $e->setAttribute('parentId', $category->getRel());
+ }
+ }
+ }
+ private function getObjectUrl(umiHierarchyElement $obj) {
+ $url = '';
+ $url = '/' . $obj->getAltName() . $url;
+
+ $parent = new umiHierarchyElement($obj->getRel());
+ while(true) {
+ $url = '/' . $parent->getAltName() . $url;
+
+ if($parent->getRel() != 0) {
+ $parent = new umiHierarchyElement($parent->getRel());
+ } else {
+ break;
+ }
+ }
+
+ $url = $this->shopUrl . $url;
+
+ return $url;
+ }
+
+ private function getCombinationsFromMultyArray($sourceData) {
+ $sourceDataKeys = array();
+ foreach ($sourceData as $key=>$value) {
+ $sourceDataKeys[] = $key;
+ }
+
+ $data = array();
+ $data[] = '';
+ for($i = 0; $i < count($sourceData); $i++) {
+ $oldData = $data;
+ $data = array();
+
+ foreach($oldData as $value) {
+ foreach ($sourceData[$sourceDataKeys[$i]] as $value2) {
+ $data[] = (!empty($value) ? $value.',' : '') . $sourceDataKeys[$i] . '-' . $value2;
+ }
+ }
+ }
+
+ $resultData = array();
+ foreach ($data as $value) {
+ $items = explode(',', $value);
+ $columns = array();
+ foreach ($items as $item) {
+ $item = explode('-', $item);
+ $columns[$item[0]] = $item[1];
+ }
+ $resultData[] = $columns;
+ }
+
+ return $resultData;
+ }
+
+ private function addOffers()
+ {
+ $offers = new selector('pages');
+ $offers->types('hierarchy-type')->name('catalog', 'object');
+
+ $result = $offers->result();
+
+ foreach ($result as $offer) {
+ /** @var umiHierarchyElement $offer */
+
+ $objects = umiObjectsCollection::getInstance();
+
+ $offerObject = new umiObject($offer->getObjectId());
+
+ /** @var umiFieldsGroup $optionsObject */
+ $optionsObject = $offerObject->getType()->getFieldsGroupByName('catalog_option_props');
+
+ $options = array();
+ $optionValues = array();
+ $optionGroups = array();
+ $optionPrices = array();
+ foreach ($optionsObject->getFields() as $optionField) {
+ /** @var umiField $optionField */
+
+ $optionGroups[$optionField->getId()] = $optionField;
+
+ $values = $offerObject->getValue($optionField->getName());
+
+ foreach ($values as $value) {
+ $valueObject = $objects->getObject($value['rel']);
+ $options[$optionField->getId()][] = $valueObject->getId();
+
+ $optionPrices[$valueObject->getId()] = $value['float'];
+
+ $optionValues[$valueObject->getId()] = $valueObject;
+ }
+ }
+
+ if(count($options))
+ $offerOptions = $this->getCombinationsFromMultyArray($options);
+ else {
+ // Если нет опционных товаров(товарных предложений) передаём массив с 1 пустым элементом - базовый товар
+ $offerOptions = array();
+ $offerOptions[] = '';
+ }
+
+ foreach ($offerOptions as $offerOption) {
+ if(!empty($offerOption)) {
+ $options = array();
+ foreach ($offerOption as $offerOptionId => $offerOptionValue) {
+ $options[] = $offerOptionId . '_' . $offerOptionValue;
+ }
+
+ $offerId = $offer->getId() . '#' . implode('-', $options);
+ } else
+ $offerId = $offer->getId();
+
+
+ /** @var DOMElement $e */
+ $e = $this->eOffers->appendChild($this->dd->createElement('offer'));
+ $e->setAttribute('id', $offerId);
+ $e->setAttribute('productId', $offer->getId());
+ $quantity = $offerObject->getPropByName('common_quantity')->getValue();
+ $e->setAttribute('quantity', !empty($quantity) ? $quantity : 0);
+
+ /**
+ * Offer activity
+ */
+ $activity = $offer->getIsActive() == 1 ? 'Y' : 'N';
+ $e->appendChild(
+ $this->dd->createElement('productActivity')
+ )->appendChild(
+ $this->dd->createTextNode($activity)
+ );
+
+ /**
+ * Offer category
+ */
+ $e->appendChild($this->dd->createElement('categoryId'))
+ ->appendChild(
+ $this->dd->createTextNode($offer->getRel())
+ );
+
+ /**
+ * Name & price
+ */
+ if(!empty($offerOption)) {
+ $options = array();
+ foreach ($offerOption as $offerOptionId => $offerOptionValue) {
+ $options[] = $optionGroups[$offerOptionId]->getTitle() . ': ' . $optionValues[$offerOptionValue]->getName();
+ }
+ $offerName = $offer->getName() . ' (' . implode(', ', $options) . ')';
+ } else
+ $offerName = $offer->getName();
+ $e->appendChild($this->dd->createElement('name'))
+ ->appendChild($this->dd->createTextNode($offerName));
+
+ $e->appendChild($this->dd->createElement('productName'))
+ ->appendChild($this->dd->createTextNode($offer->getName()));
+
+ $price = $offerObject->getPropByName('price')->getValue();
+ if(!empty($offerOption)) {
+ foreach ($offerOption as $offerOptionId => $offerOptionValue) {
+ $price += $optionPrices[$offerOptionValue];
+ }
+ }
+ $e->appendChild($this->dd->createElement('price'))
+ ->appendChild($this->dd->createTextNode($price));
+
+ /**
+ * Options
+ */
+ if(!empty($offerOption)) {
+ $options = array();
+ foreach ($offerOption as $offerOptionId => $offerOptionValue) {
+ $option = $this->dd->createElement('param');
+ $option->setAttribute('code', $optionGroups[$offerOptionId]->getName());
+ $option->setAttribute('name', $optionGroups[$offerOptionId]->getTitle());
+ $option->appendChild($this->dd->createTextNode($optionValues[$offerOptionValue]->getName()));
+ $e->appendChild($option);
+ }
+ }
+
+ /**
+ * Image
+ */
+ /** @var umiImageFile $photo */
+ $photo = $offerObject->getPropByName('photo')->getValue();
+ $photoPath = $photo->getFilePath(true);
+ $photoFullPath = $this->shopUrl . $photoPath;
+ $e->appendChild($this->dd->createElement('picture'))
+ ->appendChild($this->dd->createTextNode($photoFullPath));
+
+ /**
+ * Url
+ */
+ $url = $this->getObjectUrl($offer);
+ $e->appendChild($this->dd->createElement('url'))
+ ->appendChild(
+ $this->dd->createTextNode($url)
+ );
+
+ /**
+ * Additional characteristics
+ */
+ if ($offerObject->getPropByName('weight')) {
+ $weight = $this->dd->createElement('param');
+ $weight->setAttribute('code', 'weight');
+ $weight->setAttribute('name', 'Вес');
+ $weight->appendChild($this->dd->createTextNode($offerObject->getPropByName('weight')->getValue() * 1000));
+ $e->appendChild($weight);
+ }
+ }
+ }
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/classes/modules/RetailCRM/data/objects.xml b/classes/modules/RetailCRM/data/objects.xml
new file mode 100644
index 0000000..3226d51
--- /dev/null
+++ b/classes/modules/RetailCRM/data/objects.xml
@@ -0,0 +1,100 @@
+
+
+
+ id
+ firstName
+ lastName
+ patronymic
+ email
+ birthday
+ phones
+ manager
+
+ externalId
+ cumulativeDiscount
+ personalDiscount
+ discountCardNumber
+
+ index
+ country
+ region
+ city
+ street
+ building
+ house
+ block
+ flat
+ floor
+ intercomCode
+ metro
+ notes
+
+ contragentType
+ legalName
+ legalAddress
+ certificateNumber
+ certificateDate
+ bank
+ bankAddress
+ corrAccount
+ bankAccount
+
+ id
+ createdAt
+ orderType
+ orderMethod
+ site
+ status
+ manager
+ firstName
+ lastName
+ patronymic
+ phone
+ additionalPhone
+ email
+ paymentType
+ paymentStatus
+ paymentDetail
+ discount
+ discountPercent
+ prepaySum
+
+
+ shipmentStore
+ shipmentDate
+ shipped
+
+
+ id
+ initialPrice
+ discount
+ discountPercent
+ quantity
+ status
+
+ code
+ service
+ date
+ time
+ cost
+ netCost
+
+ country
+ index
+ region
+ city
+ street
+ building
+ house
+ block
+ flat
+ floor
+ intercomCode
+ metro
+ notes
+
+ status
+ trackNumber
+ courier
+
+
\ No newline at end of file
diff --git a/classes/modules/RetailCRM/events.php b/classes/modules/RetailCRM/events.php
new file mode 100644
index 0000000..43a9abd
--- /dev/null
+++ b/classes/modules/RetailCRM/events.php
@@ -0,0 +1,15 @@
+
\ No newline at end of file
diff --git a/classes/modules/RetailCRM/i18n.php b/classes/modules/RetailCRM/i18n.php
new file mode 100644
index 0000000..c37bc46
--- /dev/null
+++ b/classes/modules/RetailCRM/i18n.php
@@ -0,0 +1,28 @@
+ 'Ключ API',
+ 'option-crmUrl' => 'Url RetailCRM API',
+
+ 'module-RetailCRM' => 'RetailCRM',
+ 'header-RetailCRM-manage' => 'Управление',
+
+ 'group-access' => 'Настройка доступа',
+ 'group-guidesMapping' => 'Справочники',
+ 'group-orderStatusesMapping' => 'Статусы',
+ 'group-orderPaymentsMapping' => 'Способы оплаты',
+ 'group-orderPaymentStatusesMapping' => 'Статусы оплаты',
+ 'group-orderDeliveryTypesMapping' => 'Способы доставки',
+ 'group-incorrect-data' => 'Проверьте корректность заполненных полей',
+
+ 'option-country' => 'Страны',
+
+ 'notification-status-payment-initialized' => 'ожидает оплаты',
+ 'notification-status-payment-accepted' => 'оплачен',
+ 'notification-status-payment-validated' => 'обновлен: оплата подтверждена',
+ 'notification-status-payment-declined' => 'обновлен: оплата отклонена',
+
+ 'notification-status-delivery-waiting_shipping' => 'ожидает отгрузки',
+ 'notification-status-delivery-shipping' => 'доставляется',
+ 'notification-status-delivery-ready' => 'доставлен',
+);
\ No newline at end of file
diff --git a/classes/modules/RetailCRM/install.php b/classes/modules/RetailCRM/install.php
new file mode 100644
index 0000000..fe9ba43
--- /dev/null
+++ b/classes/modules/RetailCRM/install.php
@@ -0,0 +1,26 @@
+