From 006bd960e315b08f7c09e3254adf6fc31b17f992 Mon Sep 17 00:00:00 2001 From: Neur0toxine Date: Thu, 16 Apr 2020 15:26:03 +0300 Subject: [PATCH] V3 major update * New design * Corporate clients * Online consultant * Job manager (cron replacement) * Improved compatibility with 1.6 * Possibility to toggle inventory & history uploads * Better error logging * Abandoned carts improvements * Drop inactive category branch from ICML generation --- .gitignore | 6 + CHANGELOG.md | 10 + README.md | 35 +- README.ru.md | 54 - VERSION | 2 +- retailcrm/bootstrap.php | 31 +- retailcrm/controllers/front/Consultant.php | 109 + .../controllers/front/DaemonCollector.php | 103 + retailcrm/controllers/front/Jobs.php | 100 + retailcrm/controllers/front/index.php | 8 + retailcrm/controllers/index.php | 8 + retailcrm/index.php | 9 +- retailcrm/job/abandonedCarts.php | 92 - retailcrm/job/export.php | 76 - retailcrm/job/icml.php | 21 - retailcrm/job/index.php | 1 - retailcrm/job/inventories.php | 28 - retailcrm/job/jobs.php | 18 - retailcrm/job/missing.php | 167 - retailcrm/job/sync.php | 31 - retailcrm/lib/CurlException.php | 15 - retailcrm/lib/InvalidJsonException.php | 15 - retailcrm/lib/JobManager.php | 444 - .../lib/RetailcrmAbstractDataBuilder.php | 109 + retailcrm/lib/RetailcrmAddressBuilder.php | 299 + retailcrm/lib/RetailcrmApiClientV4.php | 1750 ---- retailcrm/lib/RetailcrmApiErrors.php | 77 - .../lib/RetailcrmCachedSettingExtractor.php | 132 + retailcrm/lib/RetailcrmCartUploader.php | 390 + retailcrm/lib/RetailcrmCatalog.php | 112 +- .../lib/RetailcrmConsultantRcctExtractor.php | 105 + retailcrm/lib/RetailcrmDaemonCollector.php | 52 - retailcrm/lib/RetailcrmHistory.php | 752 +- retailcrm/lib/RetailcrmHistoryHelper.php | 138 +- retailcrm/lib/RetailcrmIcml.php | 37 +- retailcrm/lib/RetailcrmInventories.php | 40 +- retailcrm/lib/RetailcrmJobManager.php | 472 + retailcrm/lib/RetailcrmLogger.php | 118 + retailcrm/lib/RetailcrmOrderBuilder.php | 1213 +++ retailcrm/lib/RetailcrmPrestashopLoader.php | 42 + retailcrm/lib/RetailcrmProxy.php | 55 - retailcrm/lib/RetailcrmReferences.php | 58 +- retailcrm/lib/RetailcrmService.php | 41 - retailcrm/lib/RetailcrmTools.php | 463 + retailcrm/lib/api/CurlException.php | 40 + retailcrm/lib/api/InvalidJsonException.php | 40 + .../lib/{ => api}/RetailcrmApiClientV5.php | 871 +- retailcrm/lib/api/RetailcrmApiErrors.php | 108 + .../lib/api/RetailcrmApiPaginatedRequest.php | 218 + .../lib/{ => api}/RetailcrmApiResponse.php | 51 +- .../lib/{ => api}/RetailcrmHttpClient.php | 61 +- retailcrm/lib/api/RetailcrmProxy.php | 111 + retailcrm/lib/api/index.php | 8 + .../events/RetailcrmAbandonedCartsEvent.php | 70 + .../lib/events/RetailcrmEventInterface.php | 47 + retailcrm/lib/events/RetailcrmExportEvent.php | 108 + retailcrm/lib/events/RetailcrmIcmlEvent.php | 57 + .../lib/events/RetailcrmInventoriesEvent.php | 70 + .../lib/events/RetailcrmMissingEvent.php | 202 + retailcrm/lib/events/RetailcrmSyncEvent.php | 73 + retailcrm/lib/events/index.php | 8 + .../RetailcrmJobManagerException.php | 84 + retailcrm/lib/exceptions/index.php | 8 + retailcrm/lib/index.php | 9 +- .../templates/RetailcrmAbstractTemplate.php | 202 + .../lib/templates/RetailcrmBaseTemplate.php | 77 + .../templates/RetailcrmSettingsTemplate.php | 102 + .../templates/RetailcrmTemplateFactory.php | 70 + retailcrm/lib/templates/index.php | 8 + retailcrm/logo.gif | Bin retailcrm/logo.png | Bin retailcrm/objects.xml | 14 + retailcrm/public/css/.gitignore | 1 - retailcrm/public/css/retailcrm-upload.css | 29 - retailcrm/public/js/.gitignore | 1 - retailcrm/public/js/exec-jobs.js | 6 - retailcrm/public/js/retailcrm-upload.js | 66 - retailcrm/retailcrm.php | 2073 +++-- retailcrm/translations/es.php | 213 +- retailcrm/translations/index.php | 0 retailcrm/translations/ru.php | 170 +- retailcrm/upgrade/index.php | 8 + retailcrm/upgrade/upgrade-3.0.1.php | 87 + retailcrm/upgrade/upgrade-3.0.2.php | 57 + retailcrm/views/css/fonts.min.css | 1 + retailcrm/views/css/index.php | 8 + retailcrm/views/css/less/fonts.less | 23 + retailcrm/views/css/less/index.php | 8 + .../views/css/less/retailcrm-upload.less | 29 + retailcrm/views/css/less/styles.less | 557 ++ .../views/css/less/sumoselect-custom.less | 52 + retailcrm/views/css/retailcrm-upload.min.css | 1 + retailcrm/views/css/styles.min.css | 1 + retailcrm/views/css/sumoselect-custom.min.css | 1 + retailcrm/views/css/vendor/index.php | 8 + retailcrm/views/css/vendor/sumoselect.min.css | 1 + retailcrm/views/fonts/OpenSans/index.php | 8 + .../views/fonts/OpenSans/opensans-regular.eot | Bin 0 -> 95250 bytes .../views/fonts/OpenSans/opensans-regular.svg | 7651 +++++++++++++++++ .../views/fonts/OpenSans/opensans-regular.ttf | Bin 0 -> 95152 bytes .../fonts/OpenSans/opensans-regular.woff | Bin 0 -> 47896 bytes .../fonts/OpenSans/opensans-regular.woff2 | Bin 0 -> 34280 bytes retailcrm/views/fonts/OpenSansBold/index.php | 8 + .../fonts/OpenSansBold/opensans-bold.eot | Bin 0 -> 97202 bytes .../fonts/OpenSansBold/opensans-bold.svg | 7651 +++++++++++++++++ .../fonts/OpenSansBold/opensans-bold.ttf | Bin 0 -> 97116 bytes .../fonts/OpenSansBold/opensans-bold.woff | Bin 0 -> 48168 bytes .../fonts/OpenSansBold/opensans-bold.woff2 | Bin 0 -> 35328 bytes retailcrm/views/fonts/index.php | 8 + retailcrm/views/img/index.php | 8 + retailcrm/views/img/video-bg.jpg | Bin 0 -> 80428 bytes retailcrm/views/index.php | 8 + retailcrm/views/js/index.php | 8 + retailcrm/views/js/retailcrm-collector.js | 101 + retailcrm/views/js/retailcrm-collector.min.js | 36 + retailcrm/views/js/retailcrm-compat.js | 53 + retailcrm/views/js/retailcrm-compat.min.js | 36 + retailcrm/views/js/retailcrm-consultant.js | 91 + .../views/js/retailcrm-consultant.min.js | 36 + retailcrm/views/js/retailcrm-jobs.js | 57 + retailcrm/views/js/retailcrm-jobs.min.js | 36 + retailcrm/views/js/retailcrm-tabs.js | 240 + retailcrm/views/js/retailcrm-tabs.min.js | 36 + retailcrm/views/js/retailcrm-upload.js | 116 + retailcrm/views/js/retailcrm-upload.min.js | 36 + retailcrm/views/js/retailcrm.js | 195 + retailcrm/views/js/retailcrm.min.js | 36 + retailcrm/views/js/vendor/index.php | 8 + retailcrm/views/js/vendor/jquery-3.4.0.min.js | 2 + .../views/js/vendor/jquery.sumoselect.min.js | 6 + retailcrm/views/templates/admin/index.php | 8 + retailcrm/views/templates/admin/index.tpl | 214 + .../views/templates/admin/module_messages.tpl | 108 + retailcrm/views/templates/admin/settings.tpl | 229 + retailcrm/views/templates/index.php | 8 + tests/RetailcrmDaemonCollectorTest.php | 40 - tests/RetailcrmHistoryTest.php | 58 +- tests/RetailcrmInventoriesTest.php | 13 +- tests/RetailcrmTest.php | 18 +- 139 files changed, 26400 insertions(+), 4723 deletions(-) delete mode 100644 README.ru.md mode change 100755 => 100644 retailcrm/bootstrap.php create mode 100644 retailcrm/controllers/front/Consultant.php create mode 100644 retailcrm/controllers/front/DaemonCollector.php create mode 100644 retailcrm/controllers/front/Jobs.php create mode 100644 retailcrm/controllers/front/index.php create mode 100644 retailcrm/controllers/index.php mode change 100755 => 100644 retailcrm/index.php delete mode 100755 retailcrm/job/abandonedCarts.php delete mode 100755 retailcrm/job/export.php delete mode 100755 retailcrm/job/icml.php delete mode 100755 retailcrm/job/index.php delete mode 100755 retailcrm/job/inventories.php delete mode 100755 retailcrm/job/jobs.php delete mode 100755 retailcrm/job/missing.php delete mode 100755 retailcrm/job/sync.php delete mode 100755 retailcrm/lib/CurlException.php delete mode 100755 retailcrm/lib/InvalidJsonException.php delete mode 100755 retailcrm/lib/JobManager.php create mode 100644 retailcrm/lib/RetailcrmAbstractDataBuilder.php create mode 100644 retailcrm/lib/RetailcrmAddressBuilder.php delete mode 100755 retailcrm/lib/RetailcrmApiClientV4.php delete mode 100755 retailcrm/lib/RetailcrmApiErrors.php create mode 100644 retailcrm/lib/RetailcrmCachedSettingExtractor.php create mode 100644 retailcrm/lib/RetailcrmCartUploader.php mode change 100755 => 100644 retailcrm/lib/RetailcrmCatalog.php create mode 100644 retailcrm/lib/RetailcrmConsultantRcctExtractor.php delete mode 100755 retailcrm/lib/RetailcrmDaemonCollector.php mode change 100755 => 100644 retailcrm/lib/RetailcrmHistory.php mode change 100755 => 100644 retailcrm/lib/RetailcrmHistoryHelper.php mode change 100755 => 100644 retailcrm/lib/RetailcrmIcml.php mode change 100755 => 100644 retailcrm/lib/RetailcrmInventories.php create mode 100644 retailcrm/lib/RetailcrmJobManager.php create mode 100644 retailcrm/lib/RetailcrmLogger.php create mode 100644 retailcrm/lib/RetailcrmOrderBuilder.php create mode 100644 retailcrm/lib/RetailcrmPrestashopLoader.php delete mode 100755 retailcrm/lib/RetailcrmProxy.php mode change 100755 => 100644 retailcrm/lib/RetailcrmReferences.php delete mode 100755 retailcrm/lib/RetailcrmService.php create mode 100644 retailcrm/lib/RetailcrmTools.php create mode 100644 retailcrm/lib/api/CurlException.php create mode 100644 retailcrm/lib/api/InvalidJsonException.php rename retailcrm/lib/{ => api}/RetailcrmApiClientV5.php (70%) mode change 100755 => 100644 create mode 100644 retailcrm/lib/api/RetailcrmApiErrors.php create mode 100644 retailcrm/lib/api/RetailcrmApiPaginatedRequest.php rename retailcrm/lib/{ => api}/RetailcrmApiResponse.php (64%) mode change 100755 => 100644 rename retailcrm/lib/{ => api}/RetailcrmHttpClient.php (57%) mode change 100755 => 100644 create mode 100644 retailcrm/lib/api/RetailcrmProxy.php create mode 100644 retailcrm/lib/api/index.php create mode 100644 retailcrm/lib/events/RetailcrmAbandonedCartsEvent.php create mode 100644 retailcrm/lib/events/RetailcrmEventInterface.php create mode 100644 retailcrm/lib/events/RetailcrmExportEvent.php create mode 100644 retailcrm/lib/events/RetailcrmIcmlEvent.php create mode 100644 retailcrm/lib/events/RetailcrmInventoriesEvent.php create mode 100644 retailcrm/lib/events/RetailcrmMissingEvent.php create mode 100644 retailcrm/lib/events/RetailcrmSyncEvent.php create mode 100644 retailcrm/lib/events/index.php create mode 100644 retailcrm/lib/exceptions/RetailcrmJobManagerException.php create mode 100644 retailcrm/lib/exceptions/index.php mode change 100755 => 100644 retailcrm/lib/index.php create mode 100644 retailcrm/lib/templates/RetailcrmAbstractTemplate.php create mode 100644 retailcrm/lib/templates/RetailcrmBaseTemplate.php create mode 100644 retailcrm/lib/templates/RetailcrmSettingsTemplate.php create mode 100644 retailcrm/lib/templates/RetailcrmTemplateFactory.php create mode 100644 retailcrm/lib/templates/index.php mode change 100755 => 100644 retailcrm/logo.gif mode change 100755 => 100644 retailcrm/logo.png mode change 100755 => 100644 retailcrm/objects.xml delete mode 100644 retailcrm/public/css/.gitignore delete mode 100755 retailcrm/public/css/retailcrm-upload.css delete mode 100644 retailcrm/public/js/.gitignore delete mode 100755 retailcrm/public/js/exec-jobs.js delete mode 100755 retailcrm/public/js/retailcrm-upload.js mode change 100755 => 100644 retailcrm/retailcrm.php mode change 100755 => 100644 retailcrm/translations/es.php mode change 100755 => 100644 retailcrm/translations/index.php mode change 100755 => 100644 retailcrm/translations/ru.php create mode 100644 retailcrm/upgrade/index.php create mode 100644 retailcrm/upgrade/upgrade-3.0.1.php create mode 100644 retailcrm/upgrade/upgrade-3.0.2.php create mode 100644 retailcrm/views/css/fonts.min.css create mode 100644 retailcrm/views/css/index.php create mode 100644 retailcrm/views/css/less/fonts.less create mode 100644 retailcrm/views/css/less/index.php create mode 100644 retailcrm/views/css/less/retailcrm-upload.less create mode 100644 retailcrm/views/css/less/styles.less create mode 100644 retailcrm/views/css/less/sumoselect-custom.less create mode 100644 retailcrm/views/css/retailcrm-upload.min.css create mode 100644 retailcrm/views/css/styles.min.css create mode 100644 retailcrm/views/css/sumoselect-custom.min.css create mode 100644 retailcrm/views/css/vendor/index.php create mode 100644 retailcrm/views/css/vendor/sumoselect.min.css create mode 100644 retailcrm/views/fonts/OpenSans/index.php create mode 100644 retailcrm/views/fonts/OpenSans/opensans-regular.eot create mode 100644 retailcrm/views/fonts/OpenSans/opensans-regular.svg create mode 100644 retailcrm/views/fonts/OpenSans/opensans-regular.ttf create mode 100644 retailcrm/views/fonts/OpenSans/opensans-regular.woff create mode 100644 retailcrm/views/fonts/OpenSans/opensans-regular.woff2 create mode 100644 retailcrm/views/fonts/OpenSansBold/index.php create mode 100644 retailcrm/views/fonts/OpenSansBold/opensans-bold.eot create mode 100644 retailcrm/views/fonts/OpenSansBold/opensans-bold.svg create mode 100644 retailcrm/views/fonts/OpenSansBold/opensans-bold.ttf create mode 100644 retailcrm/views/fonts/OpenSansBold/opensans-bold.woff create mode 100644 retailcrm/views/fonts/OpenSansBold/opensans-bold.woff2 create mode 100644 retailcrm/views/fonts/index.php create mode 100644 retailcrm/views/img/index.php create mode 100644 retailcrm/views/img/video-bg.jpg create mode 100644 retailcrm/views/index.php create mode 100644 retailcrm/views/js/index.php create mode 100644 retailcrm/views/js/retailcrm-collector.js create mode 100644 retailcrm/views/js/retailcrm-collector.min.js create mode 100644 retailcrm/views/js/retailcrm-compat.js create mode 100644 retailcrm/views/js/retailcrm-compat.min.js create mode 100644 retailcrm/views/js/retailcrm-consultant.js create mode 100644 retailcrm/views/js/retailcrm-consultant.min.js create mode 100644 retailcrm/views/js/retailcrm-jobs.js create mode 100644 retailcrm/views/js/retailcrm-jobs.min.js create mode 100644 retailcrm/views/js/retailcrm-tabs.js create mode 100644 retailcrm/views/js/retailcrm-tabs.min.js create mode 100644 retailcrm/views/js/retailcrm-upload.js create mode 100644 retailcrm/views/js/retailcrm-upload.min.js create mode 100644 retailcrm/views/js/retailcrm.js create mode 100644 retailcrm/views/js/retailcrm.min.js create mode 100644 retailcrm/views/js/vendor/index.php create mode 100644 retailcrm/views/js/vendor/jquery-3.4.0.min.js create mode 100644 retailcrm/views/js/vendor/jquery.sumoselect.min.js create mode 100644 retailcrm/views/templates/admin/index.php create mode 100644 retailcrm/views/templates/admin/index.tpl create mode 100644 retailcrm/views/templates/admin/module_messages.tpl create mode 100644 retailcrm/views/templates/admin/settings.tpl create mode 100644 retailcrm/views/templates/index.php delete mode 100644 tests/RetailcrmDaemonCollectorTest.php diff --git a/.gitignore b/.gitignore index 9f11b75..01681dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,7 @@ .idea/ +retailcrm/views/css/less/*.css +retailcrm/views/css/less/*.map +retailcrm/views/css/*.css +retailcrm/views/css/*.map +!retailcrm/views/css/*.min.css +retailcrm/config_*.xml \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cb7b69..c998e69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## v3.0.2 +* Подкатегории неактивной категории и товары в них больше не попадают в ICML + +## v3.0.1 +* Исправлена ошибка, приводившая к приостановке работы системы регулярных задач + +## v3.0.0 +* Новый интерфейс в модуле +* Добавлена возможность подключения онлайн-консультанта + ## v2.5.1 * Исправлена ошибка с некорректным значением скидки в заказе по истории diff --git a/README.md b/README.md index 109e894..cf98c11 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,12 @@ Prestashop module ================= -Module allows integrate CMS Prestashop with [retailCRM](http://www.retailcrm.pro) +Module allows integrate CMS Prestashop with [retailCRM](https://www.retailcrm.pro) #### Features: * Export orders to retailCRM & fetch changes back -* Export product catalog into [ICML](http://www.retailcrm.pro/docs/Developers/ICML) format +* Export product catalog into [ICML](https://help.retailcrm.pro/Developers/ICML) format #### Setup @@ -19,32 +19,7 @@ Module allows integrate CMS Prestashop with [retailCRM](http://www.retailcrm.pro * Fill you api url & api key * Specify directories matching -#### Setting product catalog export +#### Notice -Add to cron: - -``` -* */4 * * * /usr/bin/php /path/to/your/site/modules/retailcrm/job/icml.php -``` - -#### Getting changes in orders - -Add to cron: - -``` -*/7 * * * * /usr/bin/php /path/to/your/site/modules/retailcrm/job/sync.php -``` - -#### Receiving balances from retailCRM - -Add to cron: - -``` -*/15 * * * * /usr/bin/php /path/to/your/site/modules/retailcrm/job/inventories.php -``` - -#### Single orders archive export to retailCRM - -``` -/usr/bin/php /path/to/your/site/modules/retailcrm/job/export.php -``` +* This release contains an experimental feature "corporate customers". Use at your own risk. +* This release only supports retailCRM API v5. diff --git a/README.ru.md b/README.ru.md deleted file mode 100644 index 978ebb9..0000000 --- a/README.ru.md +++ /dev/null @@ -1,54 +0,0 @@ -Prestashop module -================= - -Модуль интеграции CMS Prestashop c [retailCRM](http://www.retailcrm.com) - -#### Модуль позволяет: - -* Экспортировать в CRM данные о заказах и клиентах и получать обратно изменения по этим данным -* Синхронизировать справочники (способы доставки и оплаты, статусы заказов и т.п.) -* Выгружать каталог товаров в формате [ICML](http://retailcrm.ru/docs/Разработчики/ФорматICML) (IntaroCRM Markup Language) - -#### Настройка - -На странице настроек модуля введите API url и API ключ, после этого установите соответствие справочников - -#### Выгрузка каталога - -Добавьте в крон запись вида - -``` -* */4 * * * /usr/bin/php /path/to/your/site/modules/retailcrm/job/icml.php -``` - -#### Получение изменение из retailCRM - -Добавьте в крон запись вида - -``` -*/7 * * * * /usr/bin/php /path/to/your/site/modules/retailcrm/job/sync.php -``` - -#### Получение остатков из retailCRM - -Добавьте в крон запись вида - -``` -*/15 * * * * /usr/bin/php /path/to/your/site/modules/retailcrm/job/inventories.php -``` - -#### Единоразовая выгрузка архива клиентов и заказов в retailCRM - -``` -/usr/bin/php /path/to/your/site/modules/retailcrm/job/export.php -``` - -#### Рекомендации - -Для более полной выгрузки заказов из CRM в магазин рекомендуется заполнять следующие поля: - -1. Покупатель -..* Фамилия - если не указано, то в магазин выгрузится в виде прочерка(дефиса). -..* E-mail - если не указано, то в магазин выгрузится в виде md5(Имя)@retailcrm.ru -2. Доставка - diff --git a/VERSION b/VERSION index 73462a5..b502146 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.5.1 +3.0.2 diff --git a/retailcrm/bootstrap.php b/retailcrm/bootstrap.php old mode 100755 new mode 100644 index bf5d642..d867b64 --- a/retailcrm/bootstrap.php +++ b/retailcrm/bootstrap.php @@ -1,13 +1,8 @@ + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. */ /** @@ -67,7 +75,10 @@ class RetailcrmAutoloader $filename = $className . self::$fileExt; foreach ($fileIterator as $file) { - if (Tools::strtolower($file->getFilename()) === Tools::strtolower($filename) && $file->isReadable()) { + if (Tools::strtolower($file->getFilename()) === Tools::strtolower($filename) + && $file->isReadable() + && !class_exists($className) + ) { include_once $file->getPathname(); } } diff --git a/retailcrm/controllers/front/Consultant.php b/retailcrm/controllers/front/Consultant.php new file mode 100644 index 0000000..bfbf738 --- /dev/null +++ b/retailcrm/controllers/front/Consultant.php @@ -0,0 +1,109 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +class RetailcrmConsultantModuleFrontController extends ModuleFrontController +{ + /** + * Universal render function for 1.6 and 1.7. + * + * @param string $response + */ + private function renderData($response) + { + if (property_exists($this, 'ajax')) { + $this->ajax = true; + } + + header('Content-Type: application/json'); + + if (Tools::substr(_PS_VERSION_, 0, 3) == '1.6') { + echo $response; + } else { + try { + parent::initContent(); + $this->ajaxRender($response); + } catch (\Exception $exception) { + // Sometimes ps_shoppingcart throws exception which breaks our controller. + // We don't care about ps_shoppingcart here, so, we will fallback to old way. + echo $response; + } + } + } + + /** + * {@inheritDoc} + */ + public function initContent() + { + $this->renderData(json_encode($this->getData())); + } + + /** + * PrestaShop 1.6 compatibility + */ + public function run() + { + $this->initContent(); + } + + /** + * Responds RCCT with site + */ + protected function getData() + { + $rcctExtractor = new RetailcrmCachedSettingExtractor(); + $rcct = $rcctExtractor + ->setCachedAndConfigKey(RetailCRM::CONSULTANT_RCCT) + ->getData(); + + if (empty($rcct)) { + $script = trim(Configuration::get(RetailCRM::CONSULTANT_SCRIPT)); + + if (!empty($script)) { + $rcctBuilder = new RetailcrmConsultantRcctExtractor(); + $rcct = $rcctBuilder->setConsultantScript($script)->build()->getDataString(); + + if (!empty($rcct)) { + Cache::getInstance()->set(RetailCRM::CONSULTANT_RCCT, $rcct); + } + } + } + + return array('rcct' => empty($rcct) ? '' : $rcct); + } +} diff --git a/retailcrm/controllers/front/DaemonCollector.php b/retailcrm/controllers/front/DaemonCollector.php new file mode 100644 index 0000000..3a79481 --- /dev/null +++ b/retailcrm/controllers/front/DaemonCollector.php @@ -0,0 +1,103 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +class RetailcrmDaemonCollectorModuleFrontController extends ModuleFrontController +{ + /** + * Universal render function for 1.6 and 1.7 + * + * @param string $response + */ + private function renderData($response) + { + if (property_exists($this, 'ajax')) { + $this->ajax = true; + } + + header('Content-Type: application/json'); + + if (Tools::substr(_PS_VERSION_, 0, 3) == '1.6') { + echo $response; + } else { + try { + parent::initContent(); + $this->ajaxRender($response); + } catch (\Exception $exception) { + // Sometimes ps_shoppingcart throws exception which breaks our controller. + // We don't care about ps_shoppingcart here, so, we will fallback to old way. + echo $response; + } + } + } + + /** + * {@inheritDoc} + */ + public function initContent() + { + $this->renderData(json_encode($this->getData())); + } + + /** + * PrestaShop 1.6 compatibility + */ + public function run() + { + $this->initContent(); + } + + /** + * Returns controller data + * + * @return array + */ + private function getData() + { + $isActive = Configuration::get(RetailCRM::COLLECTOR_ACTIVE); + $siteKey = Configuration::get(RetailCRM::COLLECTOR_KEY); + $collectorConfigured = $isActive && $siteKey; + + $params = array('siteKey' => !$collectorConfigured ? '' : $siteKey); + + if ($collectorConfigured && !empty($this->context->customer) && $this->context->customer->id) { + $params['customerId'] = $this->context->customer->id; + } + + return $params; + } +} diff --git a/retailcrm/controllers/front/Jobs.php b/retailcrm/controllers/front/Jobs.php new file mode 100644 index 0000000..bd9f022 --- /dev/null +++ b/retailcrm/controllers/front/Jobs.php @@ -0,0 +1,100 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +class RetailcrmJobsModuleFrontController extends ModuleFrontController +{ + /** + * Universal render function for 1.6 and 1.7 + * + * @param string $response + */ + private function renderData($response) + { + if (property_exists($this, 'ajax')) { + $this->ajax = true; + } + + header('Content-Type: application/json'); + + if (Tools::substr(_PS_VERSION_, 0, 3) == '1.6') { + echo $response; + } else { + try { + parent::initContent(); + $this->ajaxRender($response); + } catch (\Exception $exception) { + // Sometimes ps_shoppingcart throws exception which breaks our controller. + // We don't care about ps_shoppingcart here, so, we will fallback to old way. + echo $response; + } + } + } + + /** + * {@inheritDoc} + */ + public function initContent() + { + $this->renderData(json_encode($this->getData())); + } + + /** + * PrestaShop 1.6 compatibility + */ + public function run() + { + $this->initContent(); + } + + /** + * Runs jobs + */ + protected function getData() + { + RetailcrmJobManager::startJobs( + array( + 'RetailcrmAbandonedCartsEvent' => null, + 'RetailcrmIcmlEvent' => new \DateInterval('PT4H'), + 'RetailcrmSyncEvent' => new \DateInterval('PT7M'), + 'RetailcrmInventoriesEvent' => new \DateInterval('PT15M') + ) + ); + + return array('success' => true); + } +} diff --git a/retailcrm/controllers/front/index.php b/retailcrm/controllers/front/index.php new file mode 100644 index 0000000..6a1c957 --- /dev/null +++ b/retailcrm/controllers/front/index.php @@ -0,0 +1,8 @@ + 0) { - $time = intval($time); -} else { - $time = 0; -} - -$now = new DateTime(); -$sql = 'SELECT c.id_cart, c.date_upd - FROM '._DB_PREFIX_.'cart AS c - WHERE id_customer != 0 - AND TIME_TO_SEC(TIMEDIFF(\''.pSQL($now->format('Y-m-d H:i:s')).'\', date_upd)) >= '.$time.' - AND c.id_cart NOT IN(SELECT id_cart from '._DB_PREFIX_.'orders);'; -$rows = Db::getInstance()->executeS($sql); -$status = Configuration::get('RETAILCRM_API_SYNCHRONIZED_CART_STATUS'); -$paymentTypes = array_keys(json_decode(Configuration::get('RETAILCRM_API_PAYMENT'), true)); - -if (empty($rows) - || empty($status) - || !$api - || (count($paymentTypes) < 1) -) { - return; -} - -foreach ($rows as $cartId) { - $cart = new Cart($cartId['id_cart']); - $cartExternalId = RetailCRM::getCartOrderExternalId($cart); - - $response = $api->ordersGet($cartExternalId); - - if ($response === false) { - $api->customersCreate(RetailCRM::buildCrmCustomer(new Customer($cart->id_customer))); - $order = RetailCRM::buildCrmOrderFromCart($cart, $cartExternalId, $paymentTypes[0], $status); - - if (empty($order)) { - continue; - } - - if ($api->ordersCreate($order) !== false) { - $cart->date_upd = date('Y-m-d H:i:s'); - $cart->save(); - } - - continue; - } - - if (isset($response['order']) && !empty($response['order'])) { - $order = RetailCRM::buildCrmOrderFromCart($cart, $response['order']['externalId'], $paymentTypes[0], $status); - - if (empty($order)) { - continue; - } - - if ($api->ordersEdit($order) !== false) { - $cart->date_upd = date('Y-m-d H:i:s'); - $cart->save(); - } - } -} diff --git a/retailcrm/job/export.php b/retailcrm/job/export.php deleted file mode 100755 index 7f2c0da..0000000 --- a/retailcrm/job/export.php +++ /dev/null @@ -1,76 +0,0 @@ - $value) { - $order->$property = $value; - } - - $order->id = $record['id_order']; - - $orders[$record['id_order']] = RetailCRM::buildCrmOrder( - $order, - null, - null, - true - ); -} - -unset($orderRecords); - -$customers = array_chunk($customers, 50); - -foreach ($customers as $chunk) { - $api->customersUpload($chunk); - time_nanosleep(0, 200000000); -} - -$orders = array_chunk($orders, 50); - -foreach ($orders as $chunk) { - $api->ordersUpload($chunk); - time_nanosleep(0, 200000000); -} diff --git a/retailcrm/job/icml.php b/retailcrm/job/icml.php deleted file mode 100755 index 680767f..0000000 --- a/retailcrm/job/icml.php +++ /dev/null @@ -1,21 +0,0 @@ -getData(); - -$icml = new RetailcrmIcml(Configuration::get('PS_SHOP_NAME'), _PS_ROOT_DIR_ . '/retailcrm.xml'); -$icml->generate($data[0], $data[1]); diff --git a/retailcrm/job/index.php b/retailcrm/job/index.php deleted file mode 100755 index e71af0e..0000000 --- a/retailcrm/job/index.php +++ /dev/null @@ -1 +0,0 @@ - null - ), - true -); diff --git a/retailcrm/job/missing.php b/retailcrm/job/missing.php deleted file mode 100755 index efa419a..0000000 --- a/retailcrm/job/missing.php +++ /dev/null @@ -1,167 +0,0 @@ - $orderInstance->id, - 'createdAt' => $orderInstance->date_add, -); - -/** - * Add order customer info - * - */ - -if (!empty($orderInstance->id_customer)) { - $orderCustomer = new Customer($orderInstance->id_customer); - $customer = array( - 'externalId' => $orderCustomer->id, - 'firstName' => $orderCustomer->firstname, - 'lastname' => $orderCustomer->lastname, - 'email' => $orderCustomer->email, - 'createdAt' => $orderCustomer->date_add - ); - - $response = $api->customersEdit($customer); - - if ($response) { - $order['customer']['externalId'] = $orderCustomer->id; - $order['firstName'] = $orderCustomer->firstname; - $order['lastName'] = $orderCustomer->lastname; - $order['email'] = $orderCustomer->email; - } else { - exit(); - } -} - - -/** - * Add order status - * - */ - -if ($orderInstance->current_state == 0) { - $order['status'] = 'completed'; -} else { - $order['status'] = array_key_exists($orderInstance->current_state, $status) - ? $status[$orderInstance->current_state] - : 'completed' - ; -} - -/** - * Add order address data - * - */ - -$cart = new Cart($orderInstance->getCartIdStatic($orderInstance->id)); -$addressCollection = $cart->getAddressCollection(); -$address = array_shift($addressCollection); - -if ($address instanceof Address) { - $phone = is_null($address->phone) - ? is_null($address->phone_mobile) ? '' : $address->phone_mobile - : $address->phone - ; - - $postcode = $address->postcode; - $city = $address->city; - $addres_line = sprintf("%s %s", $address->address1, $address->address2); -} - -if (!empty($postcode)) { - $order['delivery']['address']['index'] = $postcode; -} - -if (!empty($city)) { - $order['delivery']['address']['city'] = $city; -} - -if (!empty($addres_line)) { - $order['delivery']['address']['text'] = $addres_line; -} - -if (!empty($phone)) { - $order['phone'] = $phone; -} - -/** - * Add payment & shippment data - */ - -if (Module::getInstanceByName('advancedcheckout') === false) { - $paymentType = $orderInstance->module; -} else { - $paymentType = $orderInstance->payment; -} - -if (array_key_exists($paymentType, $payment) && !empty($payment[$paymentType])) { - $order['paymentType'] = $payment[$paymentType]; -} - -if (array_key_exists($orderInstance->id_carrier, $delivery) && !empty($delivery[$orderInstance->id_carrier])) { - $order['delivery']['code'] = $delivery[$orderInstance->id_carrier]; -} - -if (isset($orderInstance->total_shipping_tax_incl) && (int) $orderInstance->total_shipping_tax_incl > 0) { - $order['delivery']['cost'] = round($orderInstance->total_shipping_tax_incl, 2); -} - -/** - * Add products - * - */ - -$products = $orderInstance->getProducts(); - -foreach ($products as $product) { - $item = array( - //'productId' => $product['product_id'], - 'offer' => array('externalId' => $product['product_id']), - 'productName' => $product['product_name'], - 'quantity' => $product['product_quantity'], - 'initialPrice' => round($product['product_price'], 2), - 'purchasePrice' => round($product['purchase_supplier_price'], 2) - ); - - $order['items'][] = $item; -} - -$api->ordersEdit($order); diff --git a/retailcrm/job/sync.php b/retailcrm/job/sync.php deleted file mode 100755 index 1ce1dd1..0000000 --- a/retailcrm/job/sync.php +++ /dev/null @@ -1,31 +0,0 @@ - - * @license https://opensource.org/licenses/MIT MIT License - * @link http://www.retailcrm.ru/docs/Developers/ApiVersion4 - */ -class CurlException extends \RuntimeException -{ -} diff --git a/retailcrm/lib/InvalidJsonException.php b/retailcrm/lib/InvalidJsonException.php deleted file mode 100755 index 8438051..0000000 --- a/retailcrm/lib/InvalidJsonException.php +++ /dev/null @@ -1,15 +0,0 @@ - - * @license https://opensource.org/licenses/MIT MIT License - * @link http://www.retailcrm.ru/docs/Developers/ApiVersion4 - */ -class InvalidJsonException extends \DomainException -{ -} diff --git a/retailcrm/lib/JobManager.php b/retailcrm/lib/JobManager.php deleted file mode 100755 index 4ec9165..0000000 --- a/retailcrm/lib/JobManager.php +++ /dev/null @@ -1,444 +0,0 @@ - DateInterval::createFromDateString('1 hour') - * ), - * true - * ); - * - * File `jobName.php` must exist in retailcrm/job and must contain everything to run job. - * Throwed errors will be logged in /retailcrm.log - * DateInterval must be positive. Pass `null` instead of DateInterval to remove - * any delay - in other words, jobs without interval will be executed every time. - * - * @param array $jobs - * @param bool $runOnceInContext - * - * @throws \Exception - */ - public static function startJobs($jobs = array(), $runOnceInContext = false) - { - $inBackground = static::canExecInBackground(); - - if ($inBackground) { - $cmdline = sprintf('%s "%s" %b', __FILE__, static::serializeJobs($jobs), $runOnceInContext); - static::execPHP($cmdline, true, $runOnceInContext); - } else { - static::execJobs($jobs, $runOnceInContext); - } - } - - /** - * Run scheduled jobs with request - * - * @param array $jobs - * @param bool $runOnceInContext - * - * @throws \Exception - */ - public static function execJobs($jobs = array(), $runOnceInContext = false) - { - $current = date_create('now'); - $lastRuns = static::getLastRuns(); - - if (!static::lock()) { - return; - } - - foreach ($jobs as $job => $diff) { - try { - if (isset($lastRuns[$job]) && $lastRuns[$job] instanceof DateTime) { - $shouldRunAt = clone $lastRuns[$job]; - } else { - $shouldRunAt = new DateTime(); - } - - if ($diff instanceof DateInterval) { - $shouldRunAt->add($diff); - } - - if (!isset($shouldRunAt) || $shouldRunAt <= $current) { - JobManager::runJob($job, $runOnceInContext); - $lastRuns[$job] = new DateTime(); - } - } catch (\Exception $exception) { - static::handleError($exception->getFile(), $exception->getMessage()); - } catch (\Throwable $throwable) { - static::handleError($throwable->getFile(), $throwable->getMessage()); - } - } - - static::setLastRuns($lastRuns); - static::unlock(); - } - - /** - * Extracts jobs last runs from db - * - * @return array - * @throws \Exception - */ - private static function getLastRuns() - { - $lastRuns = json_decode((string) Configuration::get(self::LAST_RUN_NAME), true); - - if (json_last_error() != JSON_ERROR_NONE) { - $lastRuns = []; - } else { - foreach ($lastRuns as $job => $ran) { - $lastRan = DateTime::createFromFormat(DATE_RFC3339, $ran); - - if ($lastRan instanceof DateTime) { - $lastRuns[$job] = $lastRan; - } else { - $lastRuns[$job] = new DateTime(); - } - } - } - - return (array) $lastRuns; - } - - /** - * Updates jobs last runs in db - * - * @param array $lastRuns - * - * @throws \Exception - */ - private static function setLastRuns($lastRuns = []) - { - $now = new DateTime(); - - if (!is_array($lastRuns)) { - $lastRuns = []; - } - - foreach ($lastRuns as $job => $ran) { - if ($ran instanceof DateTime) { - $lastRuns[$job] = $ran->format(DATE_RFC3339); - } else { - $lastRuns[$job] = $now->format(DATE_RFC3339); - } - } - - Configuration::updateValue(self::LAST_RUN_NAME, (string) json_encode($lastRuns)); - } - - /** - * Runs job - * - * @param string $job - * @param bool $once - */ - public static function runJob($job, $once = false) - { - $jobFile = implode(DIRECTORY_SEPARATOR, array(_PS_ROOT_DIR_, 'modules', 'retailcrm', 'job', $job . '.php')); - - if (!file_exists($jobFile)) { - throw new \InvalidArgumentException('Cannot find job'); - } - - static::execPHP($jobFile, false, $once); - } - - /** - * Runs PHP file - * - * @param $fileCommandLine - * @param bool $fork - * @param bool $once - */ - private static function execPHP($fileCommandLine, $fork = true, $once = false) - { - if ($fork) { - static::execInBackground(sprintf('%s %s', static::getPhpBinary(), $fileCommandLine)); - } else { - static::execHere($fileCommandLine, $once); - } - } - - /** - * Serializes jobs to JSON - * - * @param $jobs - * - * @return string - */ - public static function serializeJobs($jobs) - { - foreach ($jobs as $name => $interval) { - $jobs[$name] = serialize($interval); - } - - return (string) base64_encode(json_encode($jobs)); - } - - /** - * Unserializes jobs - * - * @param $jobsJson - * - * @return array - */ - public static function deserializeJobs($jobsJson) - { - $jobs = json_decode(base64_decode($jobsJson), true); - - if (json_last_error() != JSON_ERROR_NONE) { - throw new \InvalidArgumentException(sprintf('Invalid JSON: %s', json_last_error_msg())); - } - - if (!is_array($jobs) || count($jobs) == 0) { - throw new \InvalidArgumentException('Empty or invalid data'); - } - - foreach ($jobs as $name => $interval) { - if (!is_string($name) || !is_string($interval)) { - throw new \InvalidArgumentException('Invalid job in array'); - } - - $intervalObj = unserialize($interval); - - if (!($intervalObj instanceof DateInterval)) { - throw new \InvalidArgumentException('Invalid job interval in array'); - } - - $jobs[$name] = $intervalObj; - } - - return (array) $jobs; - } - - /** - * Writes error to log and returns 500 - * - * @param $file - * @param $msg - */ - private static function handleError($file, $msg) - { - error_log(sprintf('%s: %s', $file, $msg), 3, _PS_ROOT_DIR_ . '/retailcrm.log'); - http_response_code(500); - } - - /** - * Run process in background without waiting - * - * @param $cmd - * - * @return void - */ - private static function execInBackground($cmd) { - if (substr(php_uname(), 0, 7) == "Windows"){ - pclose(popen("start /B ". $cmd, "r")); - } else { - $outputPos = strpos($cmd, '>'); - - if ($outputPos !== false) { - $cmd = substr($cmd, 0, $outputPos); - } - - $command = $cmd . " > /dev/null &"; - - if (function_exists('exec')) { - exec($command); - } else if (function_exists('shell_exec')) { - shell_exec($command); - } else if (function_exists('passthru')) { - passthru($command); - } - } - } - - /** - * Executes php script in this context, without hanging up request - * - * @param string $phpScript - * @param bool $once - */ - private static function execHere($phpScript, $once = false) - { - ignore_user_abort( true); - set_time_limit(static::getTimeLimit()); - - if (version_compare(phpversion(), '7.0.16', '>=') && - function_exists('fastcgi_finish_request') - ) { - if (!headers_sent()) { - header('Expires: Thu, 19 Nov 1981 08:52:00 GMT'); - header('Cache-Control: no-store, no-cache, must-revalidate'); - } - - fastcgi_finish_request(); - } - - if ($once) { - require_once($phpScript); - } else { - require($phpScript); - } - } - - /** - * Returns true if system support execution in background - * - * @return bool - */ - private static function canExecInBackground() - { - if (substr(php_uname(), 0, 7) == "Windows"){ - return function_exists('pclose') && function_exists('popen'); - } else { - return function_exists('exec') - || function_exists('shell_exec') - || function_exists('passthru'); - } - } - - /** - * Returns path to current PHP binary - * - * @return string - */ - private static function getPhpBinary() - { - if (defined('PHP_BINARY') && !empty(PHP_BINARY)) { - return PHP_BINARY; - } - - if (defined('PHP_BINDIR') && !empty(PHP_BINDIR)) { - $version = phpversion(); - $filePath = implode(DIRECTORY_SEPARATOR, array(PHP_BINDIR, 'php' . $version)); - - while (strlen($version) != 0 && !file_exists($filePath)) { - $dotPos = strrpos($version, '.'); - - if ($dotPos !== false) { - $version = substr($version, 0, strrpos($version, '.')); - } else { - $version = ''; - } - - $filePath = implode(DIRECTORY_SEPARATOR, array(PHP_BINDIR, 'php' . $version)); - } - - if (file_exists($filePath)) { - return $filePath; - } - } - - return 'php'; - } - - /** - * Returns script execution time limit - * - * @return int - */ - private static function getTimeLimit() - { - return 14400; - } - - /** - * Returns true if lock is present and it's not expired - * - * @return bool - */ - private static function isLocked() - { - $inProcess = (bool) Configuration::get(self::IN_PROGRESS_NAME); - $lastRan = date_create_from_format(DATE_RFC3339, (string) Configuration::get(self::LAST_RUN_NAME)); - $lastRanSeconds = $lastRan instanceof DateTime ? $lastRan->format('U') : time(); - - if (($lastRanSeconds + self::getTimeLimit()) < time()) { - static::unlock(); - - return false; - } - - return $inProcess; - } - - /** - * Installs lock - * - * @return bool - */ - private static function lock() - { - if (!static::isLocked()) { - Configuration::updateValue(self::IN_PROGRESS_NAME, true); - - return true; - } - - return false; - } - - /** - * Removes lock - * - * @return bool - */ - private static function unlock() - { - Configuration::updateValue(self::IN_PROGRESS_NAME, false); - - return false; - } -} - -if (PHP_SAPI == 'cli' && $argc == 3) { - try { - $jobs = JobManager::deserializeJobs($argv[1]); - $runOnce = (bool) $argv[2]; - } catch (InvalidArgumentException $exception) { - printf('Error: %s%s', $exception->getMessage(), PHP_EOL); - exit(0); - } - - JobManager::execJobs($jobs, $runOnce); -} \ No newline at end of file diff --git a/retailcrm/lib/RetailcrmAbstractDataBuilder.php b/retailcrm/lib/RetailcrmAbstractDataBuilder.php new file mode 100644 index 0000000..23a1f49 --- /dev/null +++ b/retailcrm/lib/RetailcrmAbstractDataBuilder.php @@ -0,0 +1,109 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +abstract class RetailcrmAbstractDataBuilder +{ + /** + * @var mixed $data Any data type (depends on the builder) + */ + protected $data; + + /** + * RetailcrmAddressBuilder constructor. + */ + public function __construct() + { + $this->reset(); + } + + /** + * Reset builder state + * + * @return $this + */ + public function reset() + { + $this->data = null; + + return $this; + } + + /** + * Return cleared built data casted to array + * + * @return array + */ + public function getDataArray() + { + if (is_array($this->data)) { + return RetailcrmTools::clearArray((array) $this->data); + } + + return array(); + } + + /** + * Returns built data casted to string + * + * @return string + */ + public function getDataString() + { + if (is_string($this->data)) { + return (string) $this->data; + } + + return ''; + } + + /** + * Return builder data without any type-casting + * + * @return mixed + */ + public function getData() + { + return $this->data; + } + + /** + * Build data + * + * @return $this + */ + abstract public function build(); +} diff --git a/retailcrm/lib/RetailcrmAddressBuilder.php b/retailcrm/lib/RetailcrmAddressBuilder.php new file mode 100644 index 0000000..f1facb1 --- /dev/null +++ b/retailcrm/lib/RetailcrmAddressBuilder.php @@ -0,0 +1,299 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +class RetailcrmAddressBuilder extends RetailcrmAbstractDataBuilder +{ + /** + * Mode for regular customer. Default. + */ + const MODE_CUSTOMER = 0; + + /** + * Mode for corporate customer. + */ + const MODE_CORPORATE_CUSTOMER = 1; + + /** + * Mode for order delivery address + */ + const MODE_ORDER_DELIVERY = 2; + + /** + * @var Address|\AddressCore + */ + private $address; + + /** + * @var bool + */ + private $isMain; + + /** + * @var bool + */ + private $withExternalId; + + /** + * @var string + */ + private $externalIdSuffix = ''; + + /** + * @var int + */ + private $mode; + + /** + * @param Address|\AddressCore $address + * + * @return RetailcrmAddressBuilder + */ + public function setAddress($address) + { + $this->address = $address; + return $this; + } + + /** + * @param bool $isMain + * + * @return RetailcrmAddressBuilder + */ + public function setIsMain($isMain) + { + $this->isMain = $isMain; + return $this; + } + + /** + * @param int $mode + * + * @return RetailcrmAddressBuilder + */ + public function setMode($mode) + { + $this->mode = $mode; + return $this; + } + + /** + * @param bool $withExternalId + * + * @return RetailcrmAddressBuilder + */ + public function setWithExternalId($withExternalId) + { + $this->withExternalId = $withExternalId; + return $this; + } + + /** + * @param string $externalIdSuffix + * + * @return RetailcrmAddressBuilder + */ + public function setExternalIdSuffix($externalIdSuffix) + { + $this->externalIdSuffix = $externalIdSuffix; + return $this; + } + + /** + * @param int $addressId + * + * @return RetailcrmAddressBuilder + */ + public function setAddressId($addressId) + { + $this->address = new Address($addressId); + return $this; + } + + /** + * Reset builder state + * + * @return \RetailcrmAbstractDataBuilder|void + */ + public function reset() + { + parent::reset(); + + $this->data = array(); + $this->address = null; + $this->mode = static::MODE_CUSTOMER; + $this->isMain = false; + $this->withExternalId = false; + $this->externalIdSuffix = ''; + } + + /** + * Build address + * + * @return $this|\RetailcrmAbstractDataBuilder + */ + public function build() + { + if (!empty($this->address)) { + switch ($this->mode) { + case static::MODE_CUSTOMER: + $this->buildCustomerAddress(); + $this->buildCustomerPhones(); + break; + case static::MODE_CORPORATE_CUSTOMER: + $this->buildCorporateCustomerAddress(); + break; + case static::MODE_ORDER_DELIVERY: + $this->buildOrderAddress(); + $this->buildOrderPhones(); + break; + default: + throw new \InvalidArgumentException("Incorrect builder mode"); + } + } + + return $this; + } + + /** + * Returns built data. Data for order and for customer should be merged respectively, e.g. + * $order = array_merge($order, $builder->getData()); + * or + * $customer = array_merge($customer, $builder->getData()); + * Data for corporate customers should be used as address array e.g. + * $corporateCustomer["addresses"][] = $builder->getData(); + * + * @return array + */ + public function getDataArray() + { + if (!empty($this->address)) { + switch ($this->mode) { + case static::MODE_CUSTOMER: + return $this->data['customer']; + case static::MODE_CORPORATE_CUSTOMER: + return $this->data['customer']['address']; + case static::MODE_ORDER_DELIVERY: + return $this->data['order']; + } + } + + return array(); + } + + /** + * Parse generic address data + * + * @return array + */ + private function parseAddress() + { + return array( + 'index' => $this->address->postcode, + 'city' => $this->address->city, + 'countryIso' => Country::getIsoById($this->address->id_country), + 'text' => sprintf("%s %s", $this->address->address1, $this->address->address2) + ); + } + + /** + * Extract customer phones from address + */ + private function buildCustomerPhones() + { + if (!empty($this->address->phone_mobile)) { + $this->data['customer']['phones'][] = array('number'=> $this->address->phone_mobile); + } + + if (!empty($this->address->phone)) { + $this->data['customer']['phones'][] = array('number'=> $this->address->phone); + } + } + + /** + * Extract order phone from address + */ + private function buildOrderPhones() + { + if (!empty($this->address->phone_mobile)) { + $this->data['order']['phone'] = $this->address->phone_mobile; + } + + if (!empty($this->address->phone)) { + if (empty($this->data['order']['phone'])) { + $this->data['order']['phone'] = $this->address->phone; + } else { + $this->data['order']['additionalPhone'] = $this->address->phone; + } + } + } + + /** + * Build regular customer address + */ + private function buildCustomerAddress() + { + $this->data['customer']['address'] = $this->parseAddress(); + } + + /** + * Build corporate customer address. Address's `externalId` should be unique in customer. + * Attempt to create address with same `externalId` in customer will result in error. + */ + private function buildCorporateCustomerAddress() + { + $this->data['customer']['address'] = $this->parseAddress(); + $this->data['customer']['address']['isMain'] = $this->isMain; + + if ($this->withExternalId) { + $this->data['customer']['address']['externalId'] = $this->address->id; + + if (!empty($this->externalIdSuffix)) { + $this->data['customer']['address']['externalId'] .= $this->externalIdSuffix; + } + } + } + + /** + * Build order address + */ + private function buildOrderAddress() + { + $this->data['order']['delivery']['address'] = $this->parseAddress(); + $this->data['order']['countryIso'] = Country::getIsoById($this->address->id_country); + unset($this->data['order']['delivery']['address']['countryIso']); + } +} diff --git a/retailcrm/lib/RetailcrmApiClientV4.php b/retailcrm/lib/RetailcrmApiClientV4.php deleted file mode 100755 index c2f2b42..0000000 --- a/retailcrm/lib/RetailcrmApiClientV4.php +++ /dev/null @@ -1,1750 +0,0 @@ - - * @license https://opensource.org/licenses/MIT MIT License - * @link http://www.retailcrm.ru/docs/Developers/ApiVersion4 - */ -class RetailcrmApiClientV4 -{ - 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 - * - * @return mixed - */ - public function __construct($url, $apiKey, $site = null) - { - if ('/' !== $url[strlen($url) - 1]) { - $url .= '/'; - } - - $url = $url . 'api/' . self::VERSION; - - $this->client = new RetailcrmHttpClient($url, array('apiKey' => $apiKey)); - $this->siteCode = $site; - } - - /** - * Returns users list - * - * @param array $filter - * @param null $page - * @param null $limit - * - * @throws \RetailCrm\Exception\InvalidJsonException - * @throws \RetailCrm\Exception\CurlException - * @throws \InvalidArgumentException - * - * @return ApiResponse - */ - 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', - RetailcrmHttpClient::METHOD_GET, - $parameters - ); - } - - /** - * Returns user data - * - * @param integer $id user ID - * - * @throws \RetailCrm\Exception\InvalidJsonException - * @throws \RetailCrm\Exception\CurlException - * @throws \InvalidArgumentException - * - * @return ApiResponse - */ - public function usersGet($id) - { - return $this->client->makeRequest("/users/$id", Client::METHOD_GET); - } - - /** - * Returns filtered orders list - * - * @param array $filter (default: array()) - * @param int $page (default: null) - * @param int $limit (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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', - RetailcrmHttpClient::METHOD_GET, - $parameters - ); - } - - /** - * Create a order - * - * @param array $order order data - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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', - RetailcrmHttpClient::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 \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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', - RetailcrmHttpClient::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 \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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', - RetailcrmHttpClient::METHOD_GET, - $parameters - ); - } - - /** - * Upload array of the orders - * - * @param array $orders array of orders - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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', - RetailcrmHttpClient::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 \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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)) - ); - } - - /** - * Edit a order - * - * @param array $order order data - * @param string $by (default: 'externalId') - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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]), - RetailcrmHttpClient::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 ApiResponse - */ - 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', - RetailcrmHttpClient::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 \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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', - RetailcrmHttpClient::METHOD_GET, - $parameters - ); - } - - /** - * Create a customer - * - * @param array $customer customer data - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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', - RetailcrmHttpClient::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 \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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', - RetailcrmHttpClient::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 \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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', - RetailcrmHttpClient::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 \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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)) - ); - } - - /** - * Edit a customer - * - * @param array $customer customer data - * @param string $by (default: 'externalId') - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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]), - RetailcrmHttpClient::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 ApiResponse - */ - 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', - RetailcrmHttpClient::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 \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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', - RetailcrmHttpClient::METHOD_GET, - $parameters - ); - } - - /** - * Create orders assembly - * - * @param array $pack pack data - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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', - RetailcrmHttpClient::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 \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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', - RetailcrmHttpClient::METHOD_GET, - $parameters - ); - } - - /** - * Get orders assembly by id - * - * @param string $id pack identificator - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - public function ordersPacksGet($id) - { - if (empty($id)) { - throw new \InvalidArgumentException('Parameter `id` must be set'); - } - - return $this->client->makeRequest( - "/orders/packs/$id", - RetailcrmHttpClient::METHOD_GET - ); - } - - /** - * Delete orders assembly by id - * - * @param string $id pack identificator - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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), - RetailcrmHttpClient::METHOD_POST - ); - } - - /** - * Edit orders assembly - * - * @param array $pack pack data - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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']), - RetailcrmHttpClient::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 \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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', - RetailcrmHttpClient::METHOD_GET, - $parameters - ); - } - - /** - * Get store settings - * - * @param string $code get settings code - * - * @return ApiResponse - * @throws \RetailCrm\Exception\InvalidJsonException - * @throws \RetailCrm\Exception\CurlException - * @throws \InvalidArgumentException - * - * @return ApiResponse - */ - public function storeSettingsGet($code) - { - if (empty($code)) { - throw new \InvalidArgumentException('Parameter `code` must be set'); - } - - return $this->client->makeRequest( - "/store/setting/$code", - RetailcrmHttpClient::METHOD_GET - ); - } - - /** - * Edit store configuration - * - * @param array $configuration - * - * @throws \RetailCrm\Exception\InvalidJsonException - * @throws \RetailCrm\Exception\CurlException - * @throws \InvalidArgumentException - * - * @return ApiResponse - */ - 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']), - RetailcrmHttpClient::METHOD_POST, - $configuration - ); - } - - /** - * Upload store inventories - * - * @param array $offers offers data - * @param string $site (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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', - RetailcrmHttpClient::METHOD_POST, - $this->fillSite($site, array('offers' => json_encode($offers))) - ); - } - - /** - * Get products - * - * @param array $filter (default: array()) - * @param int $page (default: null) - * @param int $limit (default: null) - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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', - RetailcrmHttpClient::METHOD_GET, - $parameters - ); - } - - /** - * Get delivery settings - * - * @param string $code - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - public function deliverySettingsGet($code) - { - if (empty($code)) { - throw new \InvalidArgumentException('Parameter `code` must be set'); - } - - return $this->client->makeRequest( - "/delivery/generic/setting/$code", - RetailcrmHttpClient::METHOD_GET - ); - } - - /** - * Edit delivery configuration - * - * @param array $configuration - * - * @throws \RetailCrm\Exception\InvalidJsonException - * @throws \RetailCrm\Exception\CurlException - * @throws \InvalidArgumentException - * - * @return ApiResponse - */ - 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']), - RetailcrmHttpClient::METHOD_POST, - array('configuration' => json_encode($configuration)) - ); - } - - /** - * Delivery tracking update - * - * @param string $code - * @param array $statusUpdate - * - * @throws \RetailCrm\Exception\InvalidJsonException - * @throws \RetailCrm\Exception\CurlException - * @throws \InvalidArgumentException - * - * @return ApiResponse - */ - 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), - RetailcrmHttpClient::METHOD_POST, - $statusUpdate - ); - } - - /** - * Returns available county list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - public function countriesList() - { - return $this->client->makeRequest( - '/reference/countries', - RetailcrmHttpClient::METHOD_GET - ); - } - - /** - * Returns deliveryServices list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - public function deliveryServicesList() - { - return $this->client->makeRequest( - '/reference/delivery-services', - RetailcrmHttpClient::METHOD_GET - ); - } - - /** - * Edit deliveryService - * - * @param array $data delivery service data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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']), - RetailcrmHttpClient::METHOD_POST, - array('deliveryService' => json_encode($data)) - ); - } - - /** - * Returns deliveryTypes list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - public function deliveryTypesList() - { - return $this->client->makeRequest( - '/reference/delivery-types', - RetailcrmHttpClient::METHOD_GET - ); - } - - /** - * Edit deliveryType - * - * @param array $data delivery type data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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']), - RetailcrmHttpClient::METHOD_POST, - array('deliveryType' => json_encode($data)) - ); - } - - /** - * Returns orderMethods list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - public function orderMethodsList() - { - return $this->client->makeRequest( - '/reference/order-methods', - RetailcrmHttpClient::METHOD_GET - ); - } - - /** - * Edit orderMethod - * - * @param array $data order method data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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']), - RetailcrmHttpClient::METHOD_POST, - array('orderMethod' => json_encode($data)) - ); - } - - /** - * Returns orderTypes list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - public function orderTypesList() - { - return $this->client->makeRequest( - '/reference/order-types', - RetailcrmHttpClient::METHOD_GET - ); - } - - /** - * Edit orderType - * - * @param array $data order type data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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']), - RetailcrmHttpClient::METHOD_POST, - array('orderType' => json_encode($data)) - ); - } - - /** - * Returns paymentStatuses list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - public function paymentStatusesList() - { - return $this->client->makeRequest( - '/reference/payment-statuses', - RetailcrmHttpClient::METHOD_GET - ); - } - - /** - * Edit paymentStatus - * - * @param array $data payment status data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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']), - RetailcrmHttpClient::METHOD_POST, - array('paymentStatus' => json_encode($data)) - ); - } - - /** - * Returns paymentTypes list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - public function paymentTypesList() - { - return $this->client->makeRequest( - '/reference/payment-types', - RetailcrmHttpClient::METHOD_GET - ); - } - - /** - * Edit paymentType - * - * @param array $data payment type data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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']), - RetailcrmHttpClient::METHOD_POST, - array('paymentType' => json_encode($data)) - ); - } - - /** - * Returns productStatuses list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - public function productStatusesList() - { - return $this->client->makeRequest( - '/reference/product-statuses', - RetailcrmHttpClient::METHOD_GET - ); - } - - /** - * Edit productStatus - * - * @param array $data product status data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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']), - RetailcrmHttpClient::METHOD_POST, - array('productStatus' => json_encode($data)) - ); - } - - /** - * Returns sites list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - public function sitesList() - { - return $this->client->makeRequest( - '/reference/sites', - RetailcrmHttpClient::METHOD_GET - ); - } - - /** - * Edit site - * - * @param array $data site data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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']), - RetailcrmHttpClient::METHOD_POST, - array('site' => json_encode($data)) - ); - } - - /** - * Returns statusGroups list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - public function statusGroupsList() - { - return $this->client->makeRequest( - '/reference/status-groups', - RetailcrmHttpClient::METHOD_GET - ); - } - - /** - * Returns statuses list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - public function statusesList() - { - return $this->client->makeRequest( - '/reference/statuses', - RetailcrmHttpClient::METHOD_GET - ); - } - - /** - * Edit order status - * - * @param array $data status data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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']), - RetailcrmHttpClient::METHOD_POST, - array('status' => json_encode($data)) - ); - } - - /** - * Returns stores list - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - public function storesList() - { - return $this->client->makeRequest( - '/reference/stores', - RetailcrmHttpClient::METHOD_GET - ); - } - - /** - * Edit store - * - * @param array $data site data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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']), - RetailcrmHttpClient::METHOD_POST, - array('store' => json_encode($data)) - ); - } - - /** - * Get telephony settings - * - * @param string $code - * - * @throws \RetailCrm\Exception\InvalidJsonException - * @throws \RetailCrm\Exception\CurlException - * @throws \InvalidArgumentException - * - * @return ApiResponse - */ - public function telephonySettingsGet($code) - { - if (empty($code)) { - throw new \InvalidArgumentException('Parameter `code` must be set'); - } - - return $this->client->makeRequest( - "/telephony/setting/$code", - RetailcrmHttpClient::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 ApiResponse - */ - 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", - RetailcrmHttpClient::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 ApiResponse - * @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', - RetailcrmHttpClient::METHOD_POST, - array('event' => json_encode($parameters)) - ); - } - - /** - * Upload calls - * - * @param array $calls calls data - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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', - RetailcrmHttpClient::METHOD_POST, - array('calls' => json_encode($calls)) - ); - } - - /** - * Get call manager - * - * @param string $phone phone number - * @param bool $details detailed information - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - 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', - RetailcrmHttpClient::METHOD_GET, - $parameters - ); - } - - /** - * Update CRM basic statistic - * - * @throws \InvalidArgumentException - * @throws \RetailCrm\Exception\CurlException - * @throws \RetailCrm\Exception\InvalidJsonException - * - * @return ApiResponse - */ - public function statisticUpdate() - { - return $this->client->makeRequest( - '/statistic/update', - RetailcrmHttpClient::METHOD_GET - ); - } - - /** - * Edit marketplace configuration - * - * @param array $configuration - * - * @throws \RetailCrm\Exception\InvalidJsonException - * @throws \RetailCrm\Exception\CurlException - * @throws \InvalidArgumentException - * - * @return ApiResponse - */ - public function marketplaceSettingsEdit(array $configuration) - { - if (!count($configuration) || empty($configuration['code'])) { - throw new \InvalidArgumentException( - 'Parameter `configuration` must contains a data & configuration `code` must be set' - ); - } - - $code = $configuration['code']; - - return $this->client->makeRequest( - "/marketplace/external/setting/$code/edit", - RetailcrmHttpClient::METHOD_POST, - array('configuration' => json_encode($configuration)) - ); - } - - /** - * 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/retailcrm/lib/RetailcrmApiErrors.php b/retailcrm/lib/RetailcrmApiErrors.php deleted file mode 100755 index 1f621d0..0000000 --- a/retailcrm/lib/RetailcrmApiErrors.php +++ /dev/null @@ -1,77 +0,0 @@ - + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +class RetailcrmCachedSettingExtractor extends RetailcrmAbstractDataBuilder +{ + /** @var string */ + private $cachedKey; + + /** @var string */ + private $configKey; + + /** @var bool */ + private $fromCache; + + /** + * RetailcrmCachedValueExtractor constructor. + */ + public function __construct() + { + parent::__construct(); + $this->reset(); + } + + /** + * @param string $cachedKey + * + * @return $this + */ + public function setCachedKey($cachedKey) + { + $this->cachedKey = $cachedKey; + return $this; + } + + /** + * @param string $configKey + * + * @return $this + */ + public function setConfigKey($configKey) + { + $this->configKey = $configKey; + return $this; + } + + /** + * @param string $key + * + * @return $this + */ + public function setCachedAndConfigKey($key) + { + $this->setCachedKey($key); + $this->setConfigKey($key); + return $this; + } + + public function build() + { + /** @var Cache $cache */ + $cache = Cache::getInstance(); + $this->fromCache = false; + + if ($cache->exists($this->cachedKey)) { + $this->data = $cache->get($this->cachedKey); + $this->fromCache = true; + } + + if (empty($this->data)) { + $this->data = Configuration::get($this->configKey); + $this->fromCache = false; + } + } + + /** + * Reset inner state + * + * @return $this + */ + public function reset() + { + parent::reset(); + + $this->cachedKey = ''; + $this->configKey = ''; + $this->fromCache = false; + + return $this; + } + + /** + * @return bool + */ + public function isFromCache() + { + return $this->fromCache; + } +} diff --git a/retailcrm/lib/RetailcrmCartUploader.php b/retailcrm/lib/RetailcrmCartUploader.php new file mode 100644 index 0000000..edd12cd --- /dev/null +++ b/retailcrm/lib/RetailcrmCartUploader.php @@ -0,0 +1,390 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +class RetailcrmCartUploader +{ + /** + * @var \RetailcrmProxy|\RetailcrmApiClientV5 + */ + static $api; + + /** + * @var array + */ + static $cartsIds; + + /** + * @var array + */ + static $paymentTypes; + + /** + * @var int + */ + static $syncDelay; + + /** + * @var string + */ + static $syncStatus; + + /** + * @var \DateTime + */ + static $now; + + /** + * Cast provided sync delay to integer + * + * @param $time + */ + public static function setSyncDelay($time) + { + if (is_numeric($time) && $time > 0) { + static::$syncDelay = (int)$time; + } else { + static::$syncDelay = 0; + } + } + + /** + * Initialize inner state + */ + public static function init() + { + static::$api = null; + static::$cartsIds = array(); + static::$paymentTypes = array(); + static::$syncDelay = 0; + static::$syncStatus = ''; + static::$now = new \DateTime(); + } + + /** + * run carts upload + */ + public static function run() + { + if (!static::validateState()) { + return; + } + + static::loadAbandonedCartsIds(); + + foreach (static::$cartsIds as $cartId) { + $cart = new Cart($cartId['id_cart']); + $cartExternalId = RetailcrmTools::getCartOrderExternalId($cart); + $cartLastUpdateDate = null; + + if (static::isGuestCart($cart) || static::isCartTooOld($cart->date_upd) || static::isCartEmpty($cart)) { + continue; + } + + if (!empty($cart->date_upd)) { + $cartLastUpdateDate = \DateTime::createFromFormat('Y-m-d H:i:s', $cart->date_upd); + } + + if (!static::isAbandonedCartShouldBeUpdated( + static::getAbandonedCartLastSync($cart->id), + $cartLastUpdateDate + )) { + continue; + } + + $response = static::$api->ordersGet($cartExternalId); + + if (!($response instanceof RetailcrmApiResponse)) { + //TODO + // Extract address from cart (if exists) and append to customer? + // Or maybe this customer will not order anything, so we don't need it's address... + static::$api->customersCreate(RetailcrmOrderBuilder::buildCrmCustomer(new Customer($cart->id_customer))); + + $order = static::buildCartOrder($cart, $cartExternalId); + + if (empty($order)) { + continue; + } + + if (static::$api->ordersCreate($order) !== false) { + $cart->date_upd = date('Y-m-d H:i:s'); + $cart->save(); + } + + continue; + } + + if (isset($response['order']) && !empty($response['order'])) { + $order = static::buildCartOrder($cart, $response['order']['externalId']); + + if (empty($order)) { + continue; + } + + if (static::$api->ordersEdit($order) !== false) { + static::registerAbandonedCartSync($cart->id); + } + } + } + } + + /** + * Returns true if cart belongs to guest. + * + * @param $cart + * + * @return bool + */ + private static function isGuestCart($cart) + { + /** @var Customer|\CustomerCore $guestCustomer */ + $guestCustomer = new Customer($cart->id_guest); + + if (!empty($cart->id_guest) && $cart->id_customer == $cart->id_guest && $guestCustomer->is_guest) { + return true; + } + + return false; + } + + /** + * Returns true if cart is too old to update. + * + * @param string $cartDateUpd It's $cart->date_upd + * + * @return bool + */ + private static function isCartTooOld($cartDateUpd) + { + try { + $allowedUpdateInterval = new \DateInterval('P1D'); + $cartLastUpdate = \DateTime::createFromFormat('Y-m-d H:i:s', $cartDateUpd); + + if ($cartLastUpdate instanceof \DateTime) { + $cartLastUpdateDiff = $cartLastUpdate->diff(new \DateTime('now')); + + // Workaround for PHP bug: https://bugs.php.net/bug.php?id=49914 + ob_start(); + var_dump($allowedUpdateInterval); + var_dump($cartLastUpdateDiff); + ob_clean(); + ob_end_flush(); + + if ($cartLastUpdateDiff > $allowedUpdateInterval) { + return true; + } + } + } catch (\Exception $exception) { + RetailcrmLogger::writeCaller(__METHOD__, $exception->getMessage()); + RetailcrmLogger::writeNoCaller($exception->getTraceAsString()); + } + + return false; + } + + /** + * Returns true if cart is empty or if cart emptiness cannot be checked because something gone wrong. + * Errors with checking cart emptiness will be correctly written to log. + * + * @param Cart|CartCore $cart + * + * @return bool + */ + private static function isCartEmpty($cart) + { + $shouldBeUploaded = true; + + try { + $currentCartTotal = $cart->getOrderTotal(false, Cart::ONLY_PRODUCTS); + + if ($currentCartTotal == 0) { + $shouldBeUploaded = false; + } + } catch (\Exception $exception) { + RetailcrmLogger::writeCaller( + __METHOD__, + sprintf("Failure while trying to get cart total (cart id=%d)", $cart->id) + ); + RetailcrmLogger::writeCaller(__METHOD__, "Error message and stacktrace will be printed below"); + RetailcrmLogger::writeCaller(__METHOD__, $exception->getMessage()); + RetailcrmLogger::writeNoCaller($exception->getTraceAsString()); + + return true; + } + + try { + // Don't upload empty cartsIds. + if (count($cart->getProducts()) == 0 || !$shouldBeUploaded) { + return true; + } + } catch (\Exception $exception) { + RetailcrmLogger::writeCaller( + __METHOD__, + sprintf("Failure while trying to get cart total (cart id=%d)", $cart->id) + ); + RetailcrmLogger::writeCaller(__METHOD__, "Error message and stacktrace will be printed below"); + RetailcrmLogger::writeCaller(__METHOD__, $exception->getMessage()); + RetailcrmLogger::writeNoCaller($exception->getTraceAsString()); + + return true; + } + + return false; + } + + /** + * Build order for abandoned cart + * + * @param Cart|\CartCore $cart + * @param string $cartExternalId + * + * @return array + */ + private static function buildCartOrder($cart, $cartExternalId) + { + $order = array(); + + try { + $order = RetailcrmOrderBuilder::buildCrmOrderFromCart( + static::$api, + $cart, + $cartExternalId, + static::$paymentTypes[0], + static::$syncStatus + ); + } catch (\Exception $exception) { + RetailcrmLogger::writeCaller( + 'abandonedCarts', + $exception->getMessage() . PHP_EOL . $exception->getTraceAsString() + ); + RetailcrmLogger::writeNoCaller($exception->getTraceAsString()); + } + + return $order; + } + + /** + * Register abandoned cart sync event + * + * @param int $cartId + * + * @return bool + */ + private static function registerAbandonedCartSync($cartId) + { + $sql = 'INSERT INTO `' . _DB_PREFIX_ . 'retailcrm_abandonedcarts` (`id_cart`, `last_uploaded`) + VALUES (\'' . pSQL($cartId) . '\', \'' . pSQL(date('Y-m-d H:i:s')) . '\') + ON DUPLICATE KEY UPDATE `last_uploaded` = \'' . pSQL(date('Y-m-d H:i:s')) . '\';'; + + return Db::getInstance()->execute($sql); + } + + /** + * Get abandoned cart last sync time + * + * @param int $cartId + * + * @return \DateTime|false + */ + private static function getAbandonedCartLastSync($cartId) + { + $sql = 'SELECT `last_uploaded` FROM `' . _DB_PREFIX_ . 'retailcrm_abandonedcarts` + WHERE `id_cart` = \'' . pSQL((int) $cartId) . '\''; + $when = Db::getInstance()->getValue($sql); + + if (empty($when)) { + return null; + } + + return \DateTime::createFromFormat('Y-m-d H:i:s', $when); + } + + /** + * Loads abandoned carts ID's from DB. + */ + private static function loadAbandonedCartsIds() + { + $sql = 'SELECT c.id_cart, c.date_upd + FROM ' . _DB_PREFIX_ . 'cart AS c + WHERE id_customer != 0 + AND TIME_TO_SEC(TIMEDIFF(\'' . pSQL(static::$now->format('Y-m-d H:i:s')) + . '\', date_upd)) >= ' . pSQL(static::$syncDelay) . ' + AND c.id_cart NOT IN(SELECT id_cart from ' . _DB_PREFIX_ . 'orders);'; + static::$cartsIds = Db::getInstance()->executeS($sql); + } + + /** + * Returns true if abandoned cart should be uploaded + * + * @param \DateTime|null $lastUploadDate + * @param \DateTime|null $lastUpdatedDate + * + * @return bool + */ + private static function isAbandonedCartShouldBeUpdated($lastUploadDate, $lastUpdatedDate) + { + // Workaround for PHP bug: https://bugs.php.net/bug.php?id=49914 + ob_start(); + var_dump($lastUploadDate); + var_dump($lastUpdatedDate); + ob_clean(); + ob_end_flush(); + + if (is_null($lastUploadDate) || is_null($lastUpdatedDate)) { + return true; + } + + return $lastUploadDate < $lastUpdatedDate; + } + + /** + * Returns false if inner state is not correct + * + * @return bool + */ + private static function validateState() + { + if (empty(static::$syncStatus) + || (count(static::$paymentTypes) < 1) + || is_null(static::$now) + || !static::$api + ) { + return false; + } + + return true; + } +} diff --git a/retailcrm/lib/RetailcrmCatalog.php b/retailcrm/lib/RetailcrmCatalog.php old mode 100755 new mode 100644 index f1cfd49..77d0835 --- a/retailcrm/lib/RetailcrmCatalog.php +++ b/retailcrm/lib/RetailcrmCatalog.php @@ -1,5 +1,40 @@ + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ class RetailcrmCatalog { public $default_lang; @@ -15,6 +50,8 @@ class RetailcrmCatalog public function getData() { + $version = substr(_PS_VERSION_, 0, 3); + $versionSplit = explode('.', $version); $id_lang = (int) Configuration::get('PS_LANG_DEFAULT'); $currency = new Currency(Configuration::get('PS_CURRENCY_DEFAULT')); $shop_url = (Configuration::get('PS_SSL_ENABLED') ? _PS_BASE_URL_SSL_ : _PS_BASE_URL_); @@ -22,6 +59,8 @@ class RetailcrmCatalog $items = array(); $categories = array(); + $inactiveCategories = array(); + $categoriesIds = array(); if ($currency->iso_code == 'RUB') { $currency->iso_code = 'RUR'; @@ -32,8 +71,17 @@ class RetailcrmCatalog $types = Category::getCategories($id_lang, true, false); foreach ($types as $category) { + if (!self::isCategoryActive(new Category($category['id_category']))) { + if (!in_array($category['id_category'], $inactiveCategories)) { + $inactiveCategories[] = $category['id_category']; + } + + continue; + } + $picture = $link->getCatImageLink($category['link_rewrite'], $category['id_category'], 'category_default'); + $categoriesIds[] = $category['id_category']; $categories[] = array( 'id' => $category['id_category'], 'parentId' => $category['id_parent'], @@ -45,22 +93,32 @@ class RetailcrmCatalog $products = Product::getProducts($id_lang, 0, 0, 'name', 'asc'); foreach ($products AS $product) { + $homeCategory = Configuration::get('PS_HOME_CATEGORY'); $category = $product['id_category_default']; - if ($category == Configuration::get('PS_HOME_CATEGORY')) { - $temp_categories = Product::getProductCategories($product['id_product']); - - foreach ($temp_categories AS $category) { - if ($category != Configuration::get('PS_HOME_CATEGORY')) - break; - } - - if ($category == Configuration::get('PS_HOME_CATEGORY')) { - continue; - } + if (!in_array($category, $categoriesIds)) { + continue; } - $version = substr(_PS_VERSION_, 0, 3); + $currentProductCategories = Product::getProductCategories($product['id_product']); + $categoriesLeft = array_filter( + $currentProductCategories, + function ($val) use ($inactiveCategories, $categoriesIds, $homeCategory) { + if ($val == $homeCategory) { + return false; + } + + if (in_array($val, $inactiveCategories)) { + return false; + } + + return in_array($val, $categoriesIds); + } + ); + + if (empty($categoriesLeft)) { + continue; + } if ($version == "1.3") { $available_for_order = $product['active'] && $product['quantity']; @@ -229,4 +287,32 @@ class RetailcrmCatalog return array($categories, $items); } + + /** + * @param \Category|\CategoryCore $category + * + * @return bool + */ + private static function isCategoryActive($category) + { + if ($category->id == Configuration::get('PS_HOME_CATEGORY')) { + return true; + } + + if (!$category->active) { + return false; + } + + if (!empty($category->id_parent)) { + $parent = new Category($category->id_parent); + + if (!$parent->active) { + return false; + } + + return self::isCategoryActive($parent); + } + + return $category->active; + } } diff --git a/retailcrm/lib/RetailcrmConsultantRcctExtractor.php b/retailcrm/lib/RetailcrmConsultantRcctExtractor.php new file mode 100644 index 0000000..1ae7500 --- /dev/null +++ b/retailcrm/lib/RetailcrmConsultantRcctExtractor.php @@ -0,0 +1,105 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +class RetailcrmConsultantRcctExtractor extends RetailcrmAbstractDataBuilder +{ + /** @var string */ + const EXTRACT_REGEX = '/.*rcct(\s+)?\=(\s+)?[\'\"](?P[\w\d]+)[\'\"]/m'; + + /** @var string */ + private $consultantScript; + + /** + * @param string $consultantScript + * + * @return $this + */ + public function setConsultantScript($consultantScript) + { + $this->consultantScript = $consultantScript; + $this->data = ''; + return $this; + } + + /** + * Reset inner state + * + * @return $this + */ + public function reset() + { + parent::reset(); + + $this->consultantScript = ''; + + return $this; + } + + /** + * Build data + * + * @return $this + */ + public function build() + { + preg_match_all( + static::EXTRACT_REGEX, + $this->consultantScript, + $this->data, + PREG_SET_ORDER, + 0 + ); + + $this->data = reset($this->data); + + return $this; + } + + /** + * Returns string because token should be returned as string. + * + * @return string + */ + public function getDataString() + { + if (!empty($this->data) && is_array($this->data) && isset($this->data['token'])) { + return $this->data['token']; + } + + return ''; + } +} diff --git a/retailcrm/lib/RetailcrmDaemonCollector.php b/retailcrm/lib/RetailcrmDaemonCollector.php deleted file mode 100755 index 42e6d6e..0000000 --- a/retailcrm/lib/RetailcrmDaemonCollector.php +++ /dev/null @@ -1,52 +0,0 @@ - - (function(_,r,e,t,a,i,l){_['retailCRMObject']=a;_[a]=_[a]||function(){(_[a].q=_[a].q||[]).push(arguments)};_[a].l=1*new Date();l=r.getElementsByTagName(e)[0];i=r.createElement(e);i.async=!0;i.src=t;l.parentNode.insertBefore(i,l)})(window,document,'script','https://collector.retailcrm.pro/w.js','_rc'); - {{ code }} - _rc('send', 'pageView'); - -EOT; - - public function __construct($customer, $siteKey) - { - $this->customer = $customer; - $this->siteKey = $siteKey; - } - - /** - * @return string - */ - public function getJs() - { - return $this->js; - } - - /** - * @return $this - */ - public function buildScript() - { - $params = array(); - - if ($this->customer->id) { - $params['customerId'] = $this->customer->id; - } - - $this->js = preg_replace( - '/{{ code }}/', - sprintf( - "\t_rc('create', '%s', %s);\n", - $this->siteKey, - json_encode((object) $params) - ), - $this->template - ); - - return $this; - } -} diff --git a/retailcrm/lib/RetailcrmHistory.php b/retailcrm/lib/RetailcrmHistory.php old mode 100755 new mode 100644 index ecc07b3..abcfa0d --- a/retailcrm/lib/RetailcrmHistory.php +++ b/retailcrm/lib/RetailcrmHistory.php @@ -1,10 +1,45 @@ + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ class RetailcrmHistory { + /** @var \RetailcrmApiClientV5 */ public static $api; public static $default_lang; - public static $apiVersion; /** * Get customers history @@ -21,16 +56,34 @@ class RetailcrmHistory ? array('startDate' => date('Y-m-d H:i:s', strtotime('-1 days', strtotime(date('Y-m-d H:i:s'))))) : array('sinceId' => $lastSync); - $history = self::$api->customersHistory($filter); + $request = new RetailcrmApiPaginatedRequest(); + $historyChanges = array(); + $history = $request + ->setApi(self::$api) + ->setMethod('customersHistory') + ->setParams(array($filter, '{{page}}')) + ->setDataKey('history') + ->setLimit(100) + ->execute() + ->getData(); - if ($history && count($history->history)) { - $historyChanges = $history->history; + if (count($history) > 0) { + $historyChanges = static::filterHistory($history, 'customer'); + } + + if (count($historyChanges)) { $end = end($historyChanges); - $sinceid = $end['id']; + Configuration::updateValue('RETAILCRM_LAST_CUSTOMERS_SYNC', $end['id']); $customersHistory = RetailcrmHistoryHelper::assemblyCustomer($historyChanges); foreach ($customersHistory as $customerHistory) { + $syncAddressFIO = false; + + if (isset($customerHistory['deleted']) && $customerHistory['deleted']) { + continue; + } + if (isset($customerHistory['externalId'])) { $customer = new Customer($customerHistory['externalId']); @@ -50,16 +103,46 @@ class RetailcrmHistory $customer->email = $customerHistory['email']; } + if (empty($customer->passwd)) { + $customer->passwd = md5(time()); + } + + // Only sync subscription status if customer was marked as unsubscribed in retailCRM. + if (isset($customerHistory['subscribed']) && $customerHistory['subscribed'] == false) { + $customer->newsletter = false; + } + if (self::loadInCMS($customer, 'update') === false) { continue; } + if ($syncAddressFIO) { + $customerAddresses = $customer->getAddresses(static::$default_lang); + + foreach ($customerAddresses as $addressArray) { + if (isset($addressArray['alias']) && $addressArray['alias'] == 'default') { + $cmsAddress = new Address($addressArray['id_address']); + $cmsAddress->firstname = $customer->firstname; + $cmsAddress->lastname = $customer->lastname; + + self::loadInCMS($cmsAddress, 'update'); + + break; + } + } + } + } else { + if (!isset($customerHistory['firstName'])) { + continue; + } + $customer = new Customer(); $customer->firstname = $customerHistory['firstName']; $customer->lastname = isset($customerHistory['lastName']) ? $customerHistory['lastName'] : '--'; $customer->id_lang = self::$default_lang; + $customer->newsletter = false; if (isset($customerHistory['birthday'])) { $customer->birthday = $customerHistory['birthday']; @@ -72,7 +155,7 @@ class RetailcrmHistory if (isset($customerHistory['email']) && Validate::isEmail($customerHistory['email'])) { $customer->email = $customerHistory['email']; } else { - $customer->email = md5($customerHistory['firstName']) . '@retailcrm.ru'; + $customer->email = RetailcrmTools::createPlaceholderEmail($customerHistory['firstName']); } $customer->passwd = Tools::substr(str_shuffle(Tools::strtolower(sha1(rand() . time()))), 0, 5); @@ -101,7 +184,13 @@ class RetailcrmHistory $customerAddress->address1 = isset($customerHistory['address']['text']) ? $customerHistory['address']['text'] : '--'; if (isset($customerHistory['phones'])) { - $phone = reset($customerHistory['phones']); + // Can be returned as string, beware! + if (is_array($customerHistory['phones'])) { + $phone = reset($customerHistory['phones']); + } else { + $phone = $customerHistory['phones']; + } + $customerAddress->phone = $phone['number']; } @@ -125,11 +214,6 @@ class RetailcrmHistory self::$api->customersFixExternalIds($customerFix); } - /* - * Update last sync id - */ - Configuration::updateValue('RETAILCRM_LAST_CUSTOMERS_SYNC', $sinceid); - return true; } else { return 'Nothing to sync'; @@ -140,6 +224,8 @@ class RetailcrmHistory * Get orders history * * @return mixed + * @throws \PrestaShopException + * @throws \PrestaShopDatabaseException */ public static function ordersHistory() { @@ -161,16 +247,30 @@ class RetailcrmHistory $filter = array('startDate' => $lastDate); } elseif ($lastSync !== false) { $filter = array('sinceId' => $lastSync); + } else { + $filter = array(); } $customerFix = array(); $orderFix = array(); - $history = self::$api->ordersHistory($filter); + $historyChanges = array(); + $request = new RetailcrmApiPaginatedRequest(); + $history = $request + ->setApi(self::$api) + ->setMethod('ordersHistory') + ->setParams(array($filter, '{{page}}')) + ->setDataKey('history') + ->setLimit(100) + ->execute() + ->getData(); - if ($history && count($history->history) > 0) { - $historyChanges = $history->history; + if (count($history) > 0) { + $historyChanges = static::filterHistory($history, 'order'); + } + + if (count($historyChanges)) { $end = end($historyChanges); - $sinceId = $end['id']; + Configuration::updateValue('RETAILCRM_LAST_ORDERS_SYNC', $end['id']); $statuses = array_flip(array_filter(json_decode(Configuration::get('RETAILCRM_API_STATUS'), true))); $cartStatus = (string)(Configuration::get('RETAILCRM_API_SYNCHRONIZED_CART_STATUS')); @@ -186,10 +286,10 @@ class RetailcrmHistory } if (!array_key_exists('externalId', $order_history)) { - $responce = self::$api->ordersGet($order_history['id'], 'id'); + $response = self::$api->ordersGet($order_history['id'], 'id'); - if ($responce) { - $order = $responce['order']; + if ($response) { + $order = $response['order']; if ($order['status'] == $cartStatus) { continue; @@ -198,34 +298,35 @@ class RetailcrmHistory continue; } - $delivery = $order['delivery']['code']; + $delivery = isset($order['delivery']['code']) ? $order['delivery']['code'] : false; - if (array_key_exists($delivery, $deliveries) && $deliveries[$delivery] != '') { + if ($delivery && array_key_exists($delivery, $deliveries) && $deliveries[$delivery] != '') { $deliveryType = $deliveries[$delivery]; } - if (self::$apiVersion != 5) { - $payment = $order['paymentType']; - } else { - if (isset($order['payments']) && count($order['payments']) == 1) { - $paymentCRM = end($order['payments']); - $payment = $paymentCRM['type']; - } elseif (isset($order['payments']) && count($order['payments']) > 1) { - foreach ($order['payments'] as $paymentCRM) { - if ($paymentCRM['status'] != 'paid') { - $payment = $paymentCRM['type']; - } + if (isset($order['payments']) && count($order['payments']) == 1) { + $paymentCRM = end($order['payments']); + $payment = $paymentCRM['type']; + } elseif (isset($order['payments']) && count($order['payments']) > 1) { + foreach ($order['payments'] as $paymentCRM) { + if (isset($paymentCRM['status']) && $paymentCRM['status'] != 'paid') { + $payment = $paymentCRM['type']; } } } - if (array_key_exists($payment, $payments) && !empty($payments[$payment])) { - if (Module::getInstanceByName($payments[$payment])) { - $paymentType = Module::getModuleName($payments[$payment]); + $crmPaymentType = isset($payment) + ? ((is_array($payment) && isset($payment['type'])) ? $payment['type'] : $payment) + : null; + + if (!is_null($crmPaymentType) && array_key_exists($crmPaymentType, $payments) && !empty($payments[$crmPaymentType])) { + if (Module::getInstanceByName($payments[$crmPaymentType])) { + $paymentType = Module::getModuleName($payments[$crmPaymentType]); } else { - $paymentType = $payments[$payment]; + $paymentType = $payments[$crmPaymentType]; } - $paymentId = $payments[$payment]; + + $paymentId = $payments[$crmPaymentType]; } $state = $order['status']; @@ -237,7 +338,7 @@ class RetailcrmHistory $paymentId = $paymentDefault; } - if (!$paymentType) { + if (!isset($paymentType) || !$paymentType) { if ($paymentDefault) { if (Module::getInstanceByName($paymentDefault)) { $paymentType = Module::getModuleName($paymentDefault); @@ -245,10 +346,12 @@ class RetailcrmHistory $paymentType = $paymentDefault; } } else { - error_log( - 'orderHistory: set default payment(error in order where id = '.$order['id'].')', - 3, - _PS_ROOT_DIR_ . '/retailcrm.log' + RetailcrmLogger::writeCaller( + 'orderHistory', + sprintf( + 'set default payment(error in order where id = %d)', + $order['id'] + ) ); continue; @@ -259,29 +362,88 @@ class RetailcrmHistory if ($deliveryDefault) { $deliveryType = $deliveryDefault; } else { - error_log( - 'orderHistory: set default delivery(error in order where id = '.$order['id'].')', - 3, - _PS_ROOT_DIR_ . '/retailcrm.log' + RetailcrmLogger::writeCaller( + 'orderHistory', + sprintf( + 'set default delivery(error in order where id = %d)', + $order['id'] + ) ); + continue; } } - if (array_key_exists('externalId', $order['customer'])) { + $customerId = null; + $builtFromContact = false; + + if ($order['customer']['type'] == 'customer_corporate' + && RetailcrmTools::isCorporateEnabled() + && !empty($order['contact']) + && array_key_exists('externalId', $order['contact']) + ) { + if (isset($order['contact']['externalId'])) { + $customerId = Customer::customerIdExistsStatic($order['contact']['externalId']); + } + + if (empty($customerId) && !empty($order['contact']['email'])) { + $customerData = Customer::getCustomersByEmail($order['contact']['email']); + $customerData = is_array($customerData) ? reset($customerData) : array(); + + if (array_key_exists('id_customer', $customerData)) { + $customerId = $customerData['id_customer']; + } + } + } elseif (array_key_exists('externalId', $order['customer'])) { $customerId = Customer::customerIdExistsStatic($order['customer']['externalId']); } - if (!array_key_exists('externalId', $order['customer']) - || (isset($customerId) && $customerId == 0) - ) { + if (empty($customerId)) { + $firstName = ''; + $lastName = ''; + $email = ''; + + if ($order['customer']['type'] == 'customer_corporate') { + if (!empty($order['contact'])) { + $contact = $order['contact']; + $firstName = $contact['firstName']; + $lastName = !empty($contact['lastName']) ? $contact['lastName'] : '--'; + $email = + Validate::isEmail(isset($contact['email']) ? $contact['email'] : '') + ? $contact['email'] + : RetailcrmTools::createPlaceholderEmail($contact['firstName']); + $builtFromContact = true; + } elseif (!empty($order['customer']['nickName'])) { + $firstName = $order['customer']['nickName']; + $lastName = '--'; + $email = Validate::isEmail( + isset($order['customer']['email']) ? $order['customer']['email'] : '' + ) + ? $order['customer']['email'] + : RetailcrmTools::createPlaceholderEmail($order['customer']['nickName']); + } + } else { + $firstName = $order['customer']['firstName']; + $lastName = !empty($order['customer']['lastName']) + ? $order['customer']['lastName'] + : '--'; + $email = Validate::isEmail( + isset($order['customer']['email']) ? $order['customer']['email'] : '' + ) + ? $order['customer']['email'] + : RetailcrmTools::createPlaceholderEmail($order['customer']['firstName']); + } + + /** @var Customer|\CustomerCore $customer */ $customer = new Customer(); - $customer->firstname = $order['customer']['firstName']; - $customer->lastname = !empty($order['customer']['lastName']) ? $order['customer']['lastName'] : '--'; - $customer->email = Validate::isEmail($order['customer']['email']) ? - $order['customer']['email'] : - md5($order['customer']['firstName']) . '@retailcrm.ru'; - $customer->passwd = Tools::substr(str_shuffle(Tools::strtolower(sha1(rand() . time()))), 0, 5); + $customer->firstname = $firstName; + $customer->lastname = $lastName; + $customer->email = $email; + $customer->passwd = Tools::substr( + str_shuffle(Tools::strtolower(sha1(rand() . time()))), + 0, + 5 + ); if (self::loadInCMS($customer, 'add') === false) { continue; @@ -290,14 +452,16 @@ class RetailcrmHistory array_push( $customerFix, array( - 'id' => $order['customer']['id'], + 'id' => $builtFromContact ? $order['contact']['id'] : $order['customer']['id'], 'externalId' => $customer->id ) ); } else { + /** @var Customer|\CustomerCore $customer */ $customer = new Customer($customerId); } + /** @var Address|\AddressCore $address */ $address = new Address(); $address->id_customer = $customer->id; $address->id_country = $default_country; @@ -310,7 +474,25 @@ class RetailcrmHistory $address->address1 = !empty($order['delivery']['address']['text']) ? $order['delivery']['address']['text'] : '--'; $address->phone = isset($order['phone']) ? $order['phone'] : ''; - $address->add(); + + if (!empty($order['company']) + && !empty($order['company']['contragent']) + && !empty($order['company']['contragent']['legalName']) + ) { + $address->company = $order['company']['contragent']['legalName']; + + if (!empty($order['company']['contragent']['INN'])) { + $address->vat_number = $order['company']['contragent']['INN']; + } + } + + static::assignAddressIdByFields($customer, $address); + + if (empty($address->id)) { + $address->add(); + } else { + $address->save(); + } $cart = new Cart(); $cart->id_currency = $default_currency; @@ -373,6 +555,11 @@ class RetailcrmHistory $newOrder->conversion_rate = 1.000000; if (isset($orderStatus)) { $newOrder->current_state = (int) $orderStatus; + $newOrderHistoryRecord = new OrderHistory( + null, + static::$default_lang, + Context::getContext()->shop->id + ); } if (!empty($order['delivery']['date'])) { $newOrder->delivery_date = $order['delivery']['date']; @@ -393,6 +580,7 @@ class RetailcrmHistory $product = new Product((int) $item['offer']['externalId'], false, self::$default_lang); $product_id = $item['offer']['externalId']; $product_attribute_id = 0; + if (strpos($item['offer']['externalId'], '#') !== false) { $product_id = explode('#', $item['offer']['externalId']); $product_attribute_id = $product_id[1]; @@ -431,12 +619,29 @@ class RetailcrmHistory 'id_customization' => 0 ); - if (isset($item['discountTotal']) && self::$apiVersion == 5) { + if (isset($item['discountTotal'])) { $newOrder->total_discounts += $item['discountTotal'] * $item['quantity']; } } - $newOrder->add(false, false); + try { + $newOrder->add(false, false); + + if (isset($newOrderHistoryRecord)) { + $newOrderHistoryRecord->id_order = $newOrder->id; + $newOrderHistoryRecord->id_order_state = $newOrder->current_state; + $newOrderHistoryRecord->id_employee = static::getFirstEmployeeId(); + $newOrderHistoryRecord->date_add = date('Y-m-d H:i:s'); + $newOrderHistoryRecord->date_upd = $newOrderHistoryRecord->date_add; + $newOrderHistoryRecord->add(); + } + } catch (\Exception $e) { + RetailcrmLogger::writeCaller( + __METHOD__, + sprintf('Error adding order id=%d: %s', $order['id'], $e->getMessage()) + ); + RetailcrmLogger::writeNoCaller($e->getTraceAsString()); + } if (isset($order['payments']) && !empty($order['payments'])) { foreach ($order['payments'] as $payment) { @@ -465,7 +670,7 @@ class RetailcrmHistory } } - $carrier = new OrderCarrierCore(); + $carrier = new OrderCarrier(); $carrier->id_order = $newOrder->id; $carrier->id_carrier = $deliveryType; $carrier->shipping_cost_tax_excl = $order['delivery']['cost']; @@ -489,8 +694,17 @@ class RetailcrmHistory if (!empty($orderFix)) { self::$api->ordersFixExternalIds($orderFix); } + //TODO + // Also update orders numbers after creating them in PrestaShop. + // Current logic will result in autogenerated order numbers in retailCRM if + // order was placed via retailCRM interface. } else { $order = $order_history; + + if (stripos($order['externalId'], 'pscart_') !== false) { + continue; + } + $orderToUpdate = new Order((int) $order['externalId']); /* @@ -503,7 +717,13 @@ class RetailcrmHistory if (isset($deliveries[$dtype]) && $deliveries[$dtype] != null) { if ($deliveries[$dtype] != $orderToUpdate->id_carrier or $dcost != null) { if ($dtype != null) { - $orderCarrier = new OrderCarrier($orderToUpdate->id_order_carrier); + if (property_exists($orderToUpdate, 'id_order_carrier')) { + $idOrderCarrier = $orderToUpdate->id_order_carrier; + } elseif (method_exists($orderToUpdate, 'getIdOrderCarrier')) { + $idOrderCarrier = $orderToUpdate->getIdOrderCarrier(); + } + + $orderCarrier = new OrderCarrier($idOrderCarrier); $orderCarrier->id_carrier = $deliveries[$dtype]; } @@ -526,19 +746,7 @@ class RetailcrmHistory /** * check payment type */ - if (!empty($order['paymentType']) && self::$apiVersion != 5) { - $ptype = $order['paymentType']; - - if ($payments[$ptype] != null) { - $paymentType = Module::getModuleName($payments[$ptype]); - if ($payments[$ptype] != $orderToUpdate->payment) { - $orderToUpdate->payment = $paymentType != null ? $paymentType : $payments[$ptype]; - $orderPayment = OrderPayment::getByOrderId($orderToUpdate->id); - $orderPayment->payment_method = $payments[$ptype]; - $orderPayment->update(); - } - } - } elseif (!empty($order['payments']) && self::$apiVersion == 5) { + if (!empty($order['payments'])) { foreach ($order['payments'] as $payment) { if (!isset($payment['externalId']) && isset($payment['status']) @@ -567,7 +775,8 @@ class RetailcrmHistory } $orderPayment->id_currency = $default_currency; - $orderPayment->date_add = $payment['paidAt']; + $orderPayment->date_add = + isset($payment['paidAt']) ? $payment['paidAt'] : date('Y-m-d H:i:s'); $orderPayment->save(); } } @@ -584,7 +793,7 @@ class RetailcrmHistory if (!empty($order['status'])) { $stype = $order['status']; - if ($statuses[$stype] != null) { + if (isset($statuses[$stype]) && !empty($statuses[$stype])) { if ($statuses[$stype] != $orderToUpdate->current_state) { $orderHistory = new OrderHistory(); $orderHistory->id_employee = 0; @@ -601,47 +810,35 @@ class RetailcrmHistory } } - /* - * Update last sync timestamp - */ - Configuration::updateValue('RETAILCRM_LAST_ORDERS_SYNC', $sinceId); - return true; } else { return 'Nothing to sync'; } } + /** + * Updates items in order via history + * + * @param array $order + * @param Order|\OrderCore $orderToUpdate + * + * @throws \PrestaShopDatabaseException + * @throws \PrestaShopException + */ private static function updateItems($order, $orderToUpdate) { /* * Clean deleted items */ $id_order_detail = null; + foreach ($order['items'] as $key => $item) { if (isset($item['delete']) && $item['delete'] == true) { - if (strpos($item['offer']['externalId'], '#') !== false) { - $itemId = explode('#', $item['offer']['externalId']); - $product_id = $itemId[0]; - $product_attribute_id = $itemId[1]; - } else { - $product_id = $item['offer']['externalId']; - $product_attribute_id = 0; - } - - if (isset($item['externalIds'])) { - foreach ($item['externalIds'] as $externalId) { - if ($externalId['code'] == 'prestashop') { - $id_order_detail = explode('_', $externalId['value']); - } - } - }else { - $id_order_detail = explode('#', $item['offer']['externalId']); - } - - if (isset($id_order_detail[1])){ - $id_order_detail = $id_order_detail[1]; - } + $parsedExtId = static::parseItemExternalId($item); + $product_id = $parsedExtId['product_id']; + $product_attribute_id = $parsedExtId['product_attribute_id']; + $id_order_detail = !empty($parsedExtId['id_order_detail']) + ? $parsedExtId['id_order_detail'] : 0; self::deleteOrderDetailByProduct($orderToUpdate->id, $product_id, $product_attribute_id, $id_order_detail); @@ -655,47 +852,33 @@ class RetailcrmHistory */ foreach ($orderToUpdate->getProductsDetail() as $orderItem) { foreach ($order['items'] as $key => $item) { - if (strpos($item['offer']['externalId'], '#') !== false) { - $itemId = explode('#', $item['offer']['externalId']); - $product_id = $itemId[0]; - $product_attribute_id = $itemId[1]; - } else { - $product_id = $item['offer']['externalId']; - $product_attribute_id = 0; - } - if ($product_id == $orderItem['product_id'] && - $product_attribute_id == $orderItem['product_attribute_id']) { + $parsedExtId = static::parseItemExternalId($item); + $product_id = $parsedExtId['product_id']; + $product_attribute_id = $parsedExtId['product_attribute_id']; + $isExistingItem = isset($item['create']) ? false : true; + + if ($isExistingItem && + $product_id == $orderItem['product_id'] && + $product_attribute_id == $orderItem['product_attribute_id'] + ) { $product = new Product((int) $product_id, false, self::$default_lang); $tax = new TaxCore($product->id_tax_rules_group); + if ($product_attribute_id != 0) { $prodPrice = Combination::getPrice($product_attribute_id); $prodPrice = $prodPrice > 0 ? $prodPrice : $product->price; } else { $prodPrice = $product->price; } + $prodPrice = $prodPrice + $prodPrice / 100 * $tax->rate; + // discount - if (self::$apiVersion == 5) { - $productPrice = $prodPrice - (isset($item['discountTotal']) ? $item['discountTotal'] : 0); - } else { - $productPrice = $prodPrice - $item['discount']; - if ($item['discountPercent'] > 0) { - $productPrice = $productPrice - ($prodPrice / 100 * $item['discountPercent']); - } - } + $productPrice = $prodPrice - (isset($item['discountTotal']) ? $item['discountTotal'] : 0); $productPrice = round($productPrice, 2); - - if (isset($item['externalIds'])) { - foreach ($item['externalIds'] as $externalId) { - if ($externalId['code'] == 'prestashop') { - $id_order_detail = explode('_', $externalId['value']); - } - } - } else { - $id_order_detail = explode('#', $item['offer']['externalId']); - } - - $orderDetail = new OrderDetail($id_order_detail[1]); + $orderDetailId = !empty($parsedExtId['id_order_detail']) + ? $parsedExtId['id_order_detail'] : $orderItem['id_order_detail']; + $orderDetail = new OrderDetail($orderDetailId); $orderDetail->unit_price_tax_incl = $productPrice; $orderDetail->id_warehouse = 0; @@ -703,7 +886,7 @@ class RetailcrmHistory if (isset($item['quantity']) && $item['quantity'] != $orderItem['product_quantity']) { $orderDetail->product_quantity = $item['quantity']; $orderDetail->product_quantity_in_stock = $item['quantity']; - $orderDetail->id_order_detail = $id_order_detail[1]; + $orderDetail->id_order_detail = $orderDetailId; $orderDetail->id_warehouse = 0; } @@ -712,16 +895,15 @@ class RetailcrmHistory $orderDetail->id_shop = Context::getContext()->shop->id; $product = new Product((int) $product_id, false, self::$default_lang); - $productName = htmlspecialchars(strip_tags($item['offer']['displayName'])); + $productName = static::removeEdgeQuotes(htmlspecialchars(strip_tags( + !empty($item['offer']['displayName']) + ? $item['offer']['displayName'] + : Product::getProductName($product_id, $product_attribute_id) + ))); - $orderDetail->product_name = implode('', array('\'', $productName, '\'')); - $orderDetail->product_price = $item['initialPrice'] ? $item['initialPrice'] : $product->price; - - if (strpos($item['offer']['externalId'], '#') !== false) { - $product_id = explode('#', $item['offer']['externalId']); - $product_attribute_id = $product_id[1]; - $product_id = $product_id[0]; - } + static::setOrderDetailProductName($orderDetail, $productName); + $orderDetail->product_price = isset($item['initialPrice']) + ? $item['initialPrice'] : $product->price; $orderDetail->product_id = (int) $product_id; $orderDetail->product_attribute_id = (int) $product_attribute_id; @@ -773,15 +955,19 @@ class RetailcrmHistory */ if (!empty($order['items'])) { foreach ($order['items'] as $key => $newItem) { - $product_id = $newItem['offer']['externalId']; - $product_attribute_id = 0; - if (strpos($product_id, '#') !== false) { - $product_id = explode('#', $product_id); - $product_attribute_id = $product_id[1]; - $product_id = $product_id[0]; + $isNewItem = isset($newItem['create']) ? $newItem['create'] : false; + + if (!$isNewItem) { + continue; } + + $parsedExtId = static::parseItemExternalId($newItem); + $product_id = $parsedExtId['product_id']; + $product_attribute_id = $parsedExtId['product_attribute_id']; + $product = new Product((int) $product_id, false, self::$default_lang); $tax = new TaxCore($product->id_tax_rules_group); + if ($product_attribute_id != 0) { $productName = htmlspecialchars( strip_tags(Product::getProductName($product_id, $product_attribute_id)) @@ -804,23 +990,16 @@ class RetailcrmHistory $ItemDiscount = true; } - if(isset($newItem['externalIds'])) { - foreach ($newItem['externalIds'] as $externalId) { - if ($externalId['code'] == 'prestashop') { - $id_order_detail = explode('_', $externalId['value']); - } - } - } else { - $id_order_detail = explode('#', $item['offer']['externalId']); - } + $orderDetail = new OrderDetail( + !empty($parsedExtId['id_order_detail']) ? $parsedExtId['id_order_detail'] : null + ); - $orderDetail = new OrderDetail($id_order_detail[1]); + static::setOrderDetailProductName($orderDetail, $productName); $orderDetail->id_order = $orderToUpdate->id; $orderDetail->id_order_invoice = $orderToUpdate->invoice_number; $orderDetail->id_shop = Context::getContext()->shop->id; $orderDetail->product_id = (int) $product_id; $orderDetail->product_attribute_id = (int) $product_attribute_id; - $orderDetail->product_name = implode('', array('\'', $productName, '\'')); $orderDetail->product_quantity = (int) $newItem['quantity']; $orderDetail->product_quantity_in_stock = (int) $newItem['quantity']; $orderDetail->product_price = $productPrice; @@ -831,7 +1010,8 @@ class RetailcrmHistory $orderDetail->unit_price_tax_incl = ($productPrice + $productPrice / 100 * $tax->rate); $orderDetail->original_product_price = $productPrice; $orderDetail->id_warehouse = !empty($orderToUpdate->id_warehouse) ? $orderToUpdate->id_warehouse : 0; - $orderDetail->id_order_detail = $id_order_detail[1]; + $orderDetail->id_order_detail = + !empty($parsedExtId['id_order_detail']) ? $parsedExtId['id_order_detail'] : null; if ($orderDetail->save()) { $upOrderItems = array( @@ -883,7 +1063,7 @@ class RetailcrmHistory || isset($order['discountPercent']) || isset($order['delivery']['cost']) || isset($order['discountTotal']) - || $ItemDiscount + || isset($ItemDiscount) && $ItemDiscount ) { $orderTotalProducts = $infoOrder['summ']; $deliveryCost = $infoOrder['delivery']['cost']; @@ -923,10 +1103,10 @@ class RetailcrmHistory { Db::getInstance()->execute(' DELETE FROM ' . _DB_PREFIX_ . 'order_detail - WHERE id_order = ' . $order_id . ' - AND product_id = ' . $product_id . ' - AND product_attribute_id = ' . $product_attribute_id . ' - AND id_order_detail = ' . $id_order_detail + WHERE id_order = ' . pSQL((int) $order_id) . ' + AND product_id = ' . pSQL((int) $product_id) . ' + AND product_attribute_id = ' . pSQL((int) $product_attribute_id) . ' + AND id_order_detail = ' . pSQL((int) $id_order_detail) ); } @@ -940,8 +1120,8 @@ class RetailcrmHistory /** * load and catch exception * - * @param $object - * @param $action + * @param \ObjectModel|\ObjectModelCore $object + * @param string $action * * @return boolean */ @@ -950,15 +1130,239 @@ class RetailcrmHistory try { $object->$action(); } catch (PrestaShopException $e) { - error_log( - '[' . date('Y-m-d H:i:s') . '] History:loadInCMS ' . $e->getMessage() . "\n", - 3, - _PS_ROOT_DIR_ . '/retailcrm.log' + RetailcrmLogger::writeCaller( + 'loadInCMS', + sprintf( + ' > %s %s', + (string)$action, + $e->getMessage() + ) ); + RetailcrmLogger::writeNoCaller($e->getTraceAsString()); return false; } return true; } + + /** + * Filters out history by these terms: + * - Changes from current API key will be added only if CMS changes are more actual than history. + * - All other changes will be merged as usual. + * It fixes these problems: + * - Changes from current API key are merged when it's not needed. + * - Changes from CRM can overwrite more actual changes from CMS due to ignoring current API key changes. + * + * @param array $historyEntries Raw history from CRM + * @param string $recordType Entity field name, e.g. `customer` or `order`. + * + * @return array + */ + private static function filterHistory($historyEntries, $recordType) + { + $history = array(); + $organizedHistory = array(); + $notOurChanges = array(); + + foreach ($historyEntries as $entry) { + if (!isset($entry[$recordType]['externalId'])) { + if ($entry['source'] == 'api' + && isset($change['apiKey']['current']) + && $entry['apiKey']['current'] == true + && $entry['field'] != 'externalId' + ) { + continue; + } else { + $history[] = $entry; + } + + continue; + } + + $externalId = $entry[$recordType]['externalId']; + $field = $entry['field']; + + if (!isset($organizedHistory[$externalId])) { + $organizedHistory[$externalId] = array(); + } + + if (!isset($notOurChanges[$externalId])) { + $notOurChanges[$externalId] = array(); + } + + if ($entry['source'] == 'api' + && isset($entry['apiKey']['current']) + && $entry['apiKey']['current'] == true + ) { + if (isset($notOurChanges[$externalId][$field]) || $entry['field'] == 'externalId') { + $organizedHistory[$externalId][] = $entry; + } else { + continue; + } + } else { + $organizedHistory[$externalId][] = $entry; + $notOurChanges[$externalId][$field] = true; + } + } + + unset($notOurChanges); + + foreach ($organizedHistory as $historyChunk) { + $history = array_merge($history, $historyChunk); + } + + return $history; + } + + /** + * Assign address ID from customer addresses + * + * @param Customer|CustomerCore $customer + * @param Address|\AddressCore $address + */ + private static function assignAddressIdByFields($customer, &$address) + { + $checkMapping = array( + 'id_customer', + 'id_country', + 'lastname', + 'firstname', + 'alias', + 'postcode', + 'city', + 'address1', + 'phone', + 'company', + 'vat_number' + ); + + // Assigns id to $address if same address was found in customer + foreach ($customer->getAddresses(static::$default_lang) as $customerInnerAddress) { + /** @var Address|\AddressCore $customerAddress */ + $customerAddress = new Address($customerInnerAddress['id_address']); + + foreach ($checkMapping as $field) { + if ($customerAddress->$field != $address->$field) { + continue 2; + } + } + + $address->id = $customerInnerAddress['id_address']; + + break; + } + } + + /** + * Returns array with product id and product attribute id. + * Returns 0 as attribute id if attribute is not present. + * Returns array(0, 0) in case of failure. + * + * @param $item + * + * @return array + */ + private static function parseItemExternalId($item) + { + if (isset($item['externalIds'])) { + foreach ($item['externalIds'] as $externalId) { + if ($externalId['code'] == 'prestashop') { + return static::parseItemExternalIdString($externalId['value']); + } + } + } else { + return static::parseItemExternalIdString($item['offer']['externalId']); + } + + return static::parseItemExternalIdString('0#0_0'); + } + + /** + * Parse item externalId + * + * @param $externalIdString + * + * @return array + */ + private static function parseItemExternalIdString($externalIdString) + { + $parsed = explode('_', $externalIdString); + $data = array( + 'product_id' => 0, + 'product_attribute_id' => 0, + 'id_order_detail' => 0 + ); + + if (count($parsed) > 0) { + $productIdParsed = explode('#', $parsed[0]); + + if (count($productIdParsed) == 2) { + $data['product_id'] = $productIdParsed[0]; + $data['product_attribute_id'] = $productIdParsed[1]; + } elseif (count($productIdParsed) == 1) { + $data['product_id'] = $parsed[0]; + } + + if (count($parsed) == 2) { + $data['id_order_detail'] = $parsed[1]; + } + } + + return $data; + } + + /** + * Returns oldest active employee id. For order history record. + * + * @return false|string|null + */ + private static function getFirstEmployeeId() + { + return Db::getInstance()->getValue(' + SELECT `id_employee` + FROM `' . _DB_PREFIX_ . 'employee` + WHERE `active` = 1 + ORDER BY `id_employee` ASC + '); + } + + /** + * Removes quotes on string start and end + * + * @param $str + * + * @return false|string + */ + private static function removeEdgeQuotes($str) + { + if (strlen($str) >= 2) { + $newStr = $str; + + if ($newStr[0] == '\'' && $newStr[strlen($newStr) - 1] == '\'') { + $newStr = substr($newStr, 1, strlen($newStr) - 2); + } + + return $newStr; + } + + return $str; + } + + /** + * Sets product_name in OrderDetail through validation + * + * @param OrderDetail|\OrderDetailCore $object + * @param string $name + * + * @throws \PrestaShopException + */ + private static function setOrderDetailProductName(&$object, $name) + { + $object->product_name = static::removeEdgeQuotes($name); + + if ($object->validateField('product_name', $object->product_name) !== true) { + $object->product_name = implode('', array('\'', $name, '\'')); + } + } } diff --git a/retailcrm/lib/RetailcrmHistoryHelper.php b/retailcrm/lib/RetailcrmHistoryHelper.php old mode 100755 new mode 100644 index c771999..14cf024 --- a/retailcrm/lib/RetailcrmHistoryHelper.php +++ b/retailcrm/lib/RetailcrmHistoryHelper.php @@ -1,5 +1,40 @@ + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ class RetailcrmHistoryHelper { public static function assemblyOrder($orderHistory) { @@ -12,11 +47,14 @@ class RetailcrmHistoryHelper { $orders = array(); foreach ($orderHistory as $change) { $change['order'] = self::removeEmpty($change['order']); + if (isset($change['order']['items']) && $change['order']['items']) { $items = array(); + foreach($change['order']['items'] as $item) { $items[$item['id']] = $item; } + $change['order']['items'] = $items; } @@ -114,6 +152,8 @@ class RetailcrmHistoryHelper { public static function assemblyCustomer($customerHistory) { + $fields = array(); + if (file_exists(_PS_ROOT_DIR_ . '/modules/retailcrm/objects.xml')) { $objects = simplexml_load_file(_PS_ROOT_DIR_ . '/modules/retailcrm/objects.xml'); @@ -125,9 +165,22 @@ class RetailcrmHistoryHelper { } $customers = array(); + foreach ($customerHistory as $change) { $change['customer'] = self::removeEmpty($change['customer']); + if (isset($change['deleted']) + && $change['deleted'] + && isset($customers[$change['customer']['id']]) + ) { + $customers[$change['customer']['id']]['deleted'] = true; + continue; + } + + if ($change['field'] == 'id') { + $customers[$change['customer']['id']] = $change['customer']; + } + if (isset($customers[$change['customer']['id']])) { $customers[$change['customer']['id']] = array_merge($customers[$change['customer']['id']], $change['customer']); } else { @@ -139,14 +192,95 @@ class RetailcrmHistoryHelper { ) { $customers[$change['customer']['id']][$fields['customer'][$change['field']]] = self::newValue($change['newValue']); } + + // email_marketing_unsubscribed_at old value will be null and new value will be datetime in + // `Y-m-d H:i:s` format if customer was marked as unsubscribed in retailCRM + if (isset($change['customer']['id']) && + $change['field'] == 'email_marketing_unsubscribed_at' + ) { + if ($change['oldValue'] == null && is_string(self::newValue($change['newValue']))) { + $customers[$change['customer']['id']]['subscribed'] = false; + } elseif (is_string($change['oldValue']) && self::newValue($change['newValue']) == null) { + $customers[$change['customer']['id']]['subscribed'] = true; + } + } } return $customers; } + public static function assemblyCorporateCustomer($customerHistory) + { + $fields = array(); + + if (file_exists(_PS_ROOT_DIR_ . '/modules/retailcrm/objects.xml')) { + $objects = simplexml_load_file(_PS_ROOT_DIR_ . '/modules/retailcrm/objects.xml'); + + foreach($objects->fields->field as $object) { + if (in_array($object["group"], array('customerCorporate', 'customerAddress'))) { + $fields[(string)$object["group"]][(string)$object["id"]] = (string)$object; + } + } + } + + $customersCorporate = array(); + foreach ($customerHistory as $change) { + $change['customer'] = self::removeEmpty($change['customer']); + + if (isset($change['deleted']) + && $change['deleted'] + && isset($customersCorporate[$change['customer']['id']]) + ) { + $customersCorporate[$change['customer']['id']]['deleted'] = true; + continue; + } + + if (isset($customersCorporate[$change['customer']['id']])) { + if (isset($customersCorporate[$change['customer']['id']]['deleted']) + && $customersCorporate[$change['customer']['id']]['deleted'] + ) { + continue; + } + + $customersCorporate[$change['customer']['id']] = array_merge($customersCorporate[$change['customer']['id']], $change['customer']); + } else { + $customersCorporate[$change['customer']['id']] = $change['customer']; + } + + if (isset($fields['customerCorporate'][$change['field']]) + && $fields['customerCorporate'][$change['field']] + ) { + $customersCorporate[$change['customer']['id']][$fields['customerCorporate'][$change['field']]] = self::newValue($change['newValue']); + } + + if (isset($fields['customerAddress'][$change['field']]) + && $fields['customerAddress'][$change['field']] + ) { + if (empty($customersCorporate[$change['customer']['id']]['address'])) { + $customersCorporate[$change['customer']['id']]['address'] = array(); + } + + $customersCorporate[$change['customer']['id']]['address'][$fields['customerAddress'][$change['field']]] = self::newValue($change['newValue']); + } + + if ($change['field'] == 'address') { + $customersCorporate[$change['customer']['id']]['address'] = array_merge($change['address'], self::newValue($change['newValue'])); + } + } + + foreach ($customersCorporate as $id => &$customer) { + if (empty($customer['id']) && !empty($id)) { + $customer['id'] = $id; + $customer['deleted'] = true; + } + } + + return $customersCorporate; + } + public static function newValue($value) { - if(isset($value['code'])) { + if (isset($value['code'])) { return $value['code']; } else { return $value; diff --git a/retailcrm/lib/RetailcrmIcml.php b/retailcrm/lib/RetailcrmIcml.php old mode 100755 new mode 100644 index ab7c9ed..132f5c1 --- a/retailcrm/lib/RetailcrmIcml.php +++ b/retailcrm/lib/RetailcrmIcml.php @@ -1,5 +1,40 @@ + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ class RetailcrmIcml { protected $shop; diff --git a/retailcrm/lib/RetailcrmInventories.php b/retailcrm/lib/RetailcrmInventories.php old mode 100755 new mode 100644 index a8c2486..0ee50cc --- a/retailcrm/lib/RetailcrmInventories.php +++ b/retailcrm/lib/RetailcrmInventories.php @@ -1,7 +1,45 @@ + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ class RetailcrmInventories { + /** + * @var \RetailcrmProxy|\RetailcrmApiClientV5 + */ public static $api; /** diff --git a/retailcrm/lib/RetailcrmJobManager.php b/retailcrm/lib/RetailcrmJobManager.php new file mode 100644 index 0000000..813c419 --- /dev/null +++ b/retailcrm/lib/RetailcrmJobManager.php @@ -0,0 +1,472 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) { + date_default_timezone_set(@date_default_timezone_get()); +} + +require_once(dirname(__FILE__) . '/../../../config/config.inc.php'); +require_once(dirname(__FILE__) . '/../../../init.php'); +require_once(dirname(__FILE__) . '/../bootstrap.php'); + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Class RetailcrmJobManager + * + * @author DIGITAL RETAIL TECHNOLOGIES SL + * @license GPL + * @link https://retailcrm.ru + */ +class RetailcrmJobManager +{ + const LAST_RUN_NAME = 'RETAILCRM_LAST_RUN'; + const IN_PROGRESS_NAME = 'RETAILCRM_JOBS_IN_PROGRESS'; + + /** + * Entry point for all jobs. + * Jobs must be passed in this format: + * RetailcrmJobManager::startJobs( + * array( + * 'jobName' => DateInterval::createFromDateString('1 hour') + * ), + * true + * ); + * + * File `jobName.php` must exist in retailcrm/job and must contain everything to run job. + * Throwed errors will be logged in /retailcrm.log + * DateInterval must be positive. Pass `null` instead of DateInterval to remove + * any delay - in other words, jobs without interval will be executed every time. + * + * @param array $jobs Jobs list + * @param bool $runOnceInContext Use require_once instead of require + * + * @throws \Exception + */ + public static function startJobs( + $jobs = array(), + $runOnceInContext = true + ) { + RetailcrmLogger::writeDebug(__METHOD__,'starting JobManager'); + static::execJobs($jobs, $runOnceInContext); + } + + /** + * Run scheduled jobs with request + * + * @param array $jobs + * @param bool $runOnceInContext + * + * @throws \Exception + */ + public static function execJobs($jobs = array(), $runOnceInContext = false) + { + $current = date_create('now'); + $lastRuns = array(); + + try { + $lastRuns = static::getLastRuns(); + } catch (Exception $exception) { + static::handleError( + $exception->getFile(), + $exception->getMessage(), + $exception->getTraceAsString(), + '', + $jobs + ); + + return; + } + + RetailcrmLogger::writeDebug(__METHOD__, 'Trying to acquire lock...'); + + if (!static::lock()) { + RetailcrmLogger::writeDebug(__METHOD__, 'Cannot acquire lock'); + die; + } + + RetailcrmLogger::writeDebug( + __METHOD__, + sprintf('Current time: %s', $current->format(DATE_RFC3339)) + ); + + foreach ($lastRuns as $name => $diff) { + if (!array_key_exists($name, $jobs)) { + unset($lastRuns[$name]); + } + } + + foreach ($jobs as $job => $diff) { + try { + if (isset($lastRuns[$job]) && $lastRuns[$job] instanceof DateTime) { + $shouldRunAt = clone $lastRuns[$job]; + + if ($diff instanceof DateInterval) { + $shouldRunAt->add($diff); + } + } else { + $shouldRunAt = \DateTime::createFromFormat('Y-m-d H:i:s', '1970-01-01 00:00:00'); + } + + RetailcrmLogger::writeDebug(__METHOD__, sprintf( + 'Checking %s, interval %s, shouldRunAt: %s: %s', + $job, + is_null($diff) ? 'NULL' : $diff->format('%R%Y-%m-%d %H:%i:%s:%F'), + isset($shouldRunAt) && $shouldRunAt instanceof \DateTime + ? $shouldRunAt->format(DATE_RFC3339) + : 'undefined', + (isset($shouldRunAt) && $shouldRunAt <= $current) ? 'true' : 'false' + )); + + if (isset($shouldRunAt) && $shouldRunAt <= $current) { + RetailcrmLogger::writeDebug(__METHOD__, sprintf('Executing job %s', $job)); + RetailcrmJobManager::runJob($job, $runOnceInContext); + $lastRuns[$job] = new \DateTime('now'); + } + } catch (\Exception $exception) { + static::handleError( + $exception->getFile(), + $exception->getMessage(), + $exception->getTraceAsString(), + $job + ); + } catch (\Throwable $throwable) { + static::handleError( + $throwable->getFile(), + $throwable->getMessage(), + $throwable->getTraceAsString(), + $job + ); + } + } + + try { + static::setLastRuns($lastRuns); + } catch (Exception $exception) { + static::handleError( + $exception->getFile(), + $exception->getMessage(), + $exception->getTraceAsString(), + '', + $jobs + ); + } + + static::unlock(); + } + + /** + * Extracts jobs last runs from db + * + * @return array + * @throws \Exception + */ + private static function getLastRuns() + { + $lastRuns = json_decode((string)Configuration::get(self::LAST_RUN_NAME), true); + + if (json_last_error() != JSON_ERROR_NONE) { + $lastRuns = array(); + } else { + foreach ($lastRuns as $job => $ran) { + $lastRan = DateTime::createFromFormat(DATE_RFC3339, $ran); + + if ($lastRan instanceof DateTime) { + $lastRuns[$job] = $lastRan; + } else { + $lastRuns[$job] = new DateTime(); + } + } + } + + return (array)$lastRuns; + } + + /** + * Updates jobs last runs in db + * + * @param array $lastRuns + * + * @throws \Exception + */ + private static function setLastRuns($lastRuns = array()) + { + $now = new DateTime(); + + if (!is_array($lastRuns)) { + $lastRuns = array(); + } + + foreach ($lastRuns as $job => $ran) { + if ($ran instanceof DateTime) { + $lastRuns[$job] = $ran->format(DATE_RFC3339); + } else { + $lastRuns[$job] = $now->format(DATE_RFC3339); + } + + RetailcrmLogger::writeDebug( + __METHOD__, + sprintf('Saving last run for %s as %s', $job, $lastRuns[$job]) + ); + } + + Configuration::updateValue(self::LAST_RUN_NAME, (string)json_encode($lastRuns)); + } + + /** + * Runs job + * + * @param string $job + * @param bool $once + * + * @throws \RetailcrmJobManagerException + */ + public static function runJob($job, $once = false) + { + $jobFile = implode( + DIRECTORY_SEPARATOR, + array(_PS_ROOT_DIR_, 'modules', 'retailcrm', 'lib', 'events', self::escapeJobName($job) . '.php') + ); + + if (!file_exists($jobFile)) { + throw new \RetailcrmJobManagerException('Cannot find job', $job); + } + + static::execPHP($jobFile, $once); + } + + /** + * Runs PHP file + * + * @param string $fileCommandLine + * @param bool $once + * + * @throws \RetailcrmJobManagerException + */ + private static function execPHP($fileCommandLine, $once = false) + { + $error = null; + + try { + static::execHere($fileCommandLine, $once); + } catch (\Exception $exception) { + throw new RetailcrmJobManagerException($exception->getMessage(), $fileCommandLine); + } catch (\Throwable $exception) { + throw new RetailcrmJobManagerException($exception->getMessage(), $fileCommandLine); + } + } + + /** + * Serializes jobs to JSON + * + * @param $jobs + * + * @return string + */ + public static function serializeJobs($jobs) + { + foreach ($jobs as $name => $interval) { + $jobs[$name] = serialize($interval); + } + + return (string)base64_encode(json_encode($jobs)); + } + + /** + * Writes error to log and returns 500 + * + * @param string $file + * @param string $msg + * @param string $trace + * @param string $currentJob + * @param array $jobs + */ + private static function handleError($file, $msg, $trace, $currentJob = '', $jobs = array()) + { + $data = array(); + + if (!empty($currentJob)) { + $data[] = 'current job: ' . $currentJob; + } + + if (count($jobs) > 0) { + $data[] = 'jobs list: ' . self::serializeJobs($jobs); + } + + RetailcrmLogger::writeNoCaller(sprintf('%s: %s (%s)', $file, $msg, implode(', ', $data))); + RetailcrmLogger::writeNoCaller($trace); + RetailcrmTools::http_response_code(500); + } + + /** + * Executes php script in this context, without hanging up request + * + * @param string $phpScript + * @param bool $once + */ + private static function execHere($phpScript, $once = false) + { + ignore_user_abort(true); + set_time_limit(static::getTimeLimit()); + + if (version_compare(phpversion(), '7.0.16', '>=') && + function_exists('fastcgi_finish_request') + ) { + if (!headers_sent()) { + header('Expires: Thu, 19 Nov 1981 08:52:00 GMT'); + header('Cache-Control: no-store, no-cache, must-revalidate'); + } + + fastcgi_finish_request(); + } + + if ($once) { + require_once($phpScript); + } else { + require($phpScript); + } + } + + /** + * Returns script execution time limit + * + * @return int + */ + private static function getTimeLimit() + { + return 14400; + } + + /** + * Removes disallowed symbols from job name. Only latin characters, numbers and underscore allowed. + * + * @param string $job + * + * @return string + */ + private static function escapeJobName($job) + { + return (string) preg_replace('/[^[a-zA-Z0-9_]]*/m', '', $job); + } + + /** + * Returns when JobManager was executed + * + * @throws \Exception + */ + private static function getLastRun() + { + $lastRuns = array_values(static::getLastRuns()); + + if (empty($lastRuns)) { + return \DateTime::createFromFormat('Y-m-d H:i:s', '1970-01-01 00:00:00'); + } + + usort( + $lastRuns, + function ($first, $second) { + if ($first < $second) { + return 1; + } else if ($first > $second) { + return -1; + } else { + return 0; + } + } + ); + + return $lastRuns[count($lastRuns) - 1]; + } + + /** + * Returns true if lock is present and it's not expired + * + * @return bool + * @throws \Exception + */ + private static function isLocked() + { + $inProcess = (bool)Configuration::get(self::IN_PROGRESS_NAME); + $lastRan = static::getLastRun(); + $lastRanSeconds = $lastRan->format('U'); + + if (($lastRanSeconds + self::getTimeLimit()) < time()) { + RetailcrmLogger::writeDebug(__METHOD__, 'Removing lock because time limit exceeded.'); + static::unlock(); + + return false; + } + + return $inProcess; + } + + /** + * Installs lock + * + * @return bool + * @throws \Exception + */ + private static function lock() + { + if (!static::isLocked()) { + RetailcrmLogger::writeDebug(__METHOD__, 'Acquiring lock...'); + Configuration::updateValue(self::IN_PROGRESS_NAME, true); + RetailcrmLogger::writeDebug(__METHOD__, 'Lock acquired.'); + + return true; + } + + return false; + } + + /** + * Removes lock + * + * @return bool + */ + private static function unlock() + { + RetailcrmLogger::writeDebug(__METHOD__, 'Removing lock...'); + Configuration::updateValue(self::IN_PROGRESS_NAME, false); + RetailcrmLogger::writeDebug(__METHOD__, 'Lock removed.'); + + return false; + } +} diff --git a/retailcrm/lib/RetailcrmLogger.php b/retailcrm/lib/RetailcrmLogger.php new file mode 100644 index 0000000..e2c4285 --- /dev/null +++ b/retailcrm/lib/RetailcrmLogger.php @@ -0,0 +1,118 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Class RetailcrmLogger + * @author DIGITAL RETAIL TECHNOLOGIES SL + * @license GPL + * @link https://retailcrm.ru + */ +class RetailcrmLogger +{ + /** + * Write entry to log + * + * @param string $caller + * @param string $message + */ + public static function writeCaller($caller, $message) + { + error_log( + sprintf( + '[%s] @ [%s] %s' . PHP_EOL, + date(DATE_RFC3339), + $caller, + $message + ), + 3, + static::getErrorLog() + ); + } + + /** + * Write entry to log without caller name + * + * @param string $message + */ + public static function writeNoCaller($message) + { + error_log( + sprintf( + '[%s] %s' . PHP_EOL, + date(DATE_RFC3339), + $message + ), + 3, + static::getErrorLog() + ); + } + + /** + * Write debug log record + * + * @param string $caller + * @param mixed $message + */ + public static function writeDebug($caller, $message) + { + if (RetailcrmTools::isDebug()) { + static::writeNoCaller(sprintf( + '(DEBUG) <%s> %s', + $caller, + print_r($message, true) + )); + } + } + + /** + * Returns error log path + * + * @return string + */ + protected static function getErrorLog() + { + if (!defined('_PS_ROOT_DIR_')) { + return ''; + } + + return _PS_ROOT_DIR_ . '/retailcrm.log'; + } +} \ No newline at end of file diff --git a/retailcrm/lib/RetailcrmOrderBuilder.php b/retailcrm/lib/RetailcrmOrderBuilder.php new file mode 100644 index 0000000..450d119 --- /dev/null +++ b/retailcrm/lib/RetailcrmOrderBuilder.php @@ -0,0 +1,1213 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +class RetailcrmOrderBuilder +{ + /** + * @var \RetailcrmApiClientV5 $api + */ + protected $api; + + /** + * @var int $default_lang + */ + protected $default_lang; + + /** + * @var Order|OrderCore|null $cmsOrder + */ + protected $cmsOrder; + + /** + * @var Cart|CartCore|null $cmsCart + */ + protected $cmsCart; + + /** + * @var Customer|CustomerCore|null $cmsCustomer + */ + protected $cmsCustomer; + + /** + * @var Address|\AddressCore + */ + protected $invoiceAddress; + + /** + * @var Address|\AddressCore + */ + protected $deliveryAddress; + + /** + * @var array|null $createdCustomer + */ + protected $createdCustomer; + + /** + * @var array|null $corporateCompanyExtractCache + */ + protected $corporateCompanyExtractCache; + + /** + * @var string $apiSite + */ + protected $apiSite; + + /** + * @return RetailcrmOrderBuilder + */ + public function defaultLangFromConfiguration() + { + $this->default_lang = (int)Configuration::get('PS_LANG_DEFAULT'); + return $this; + } + + /** + * @param mixed $default_lang + * + * @return RetailcrmOrderBuilder + */ + public function setDefaultLang($default_lang) + { + $this->default_lang = $default_lang; + return $this; + } + + /** + * @param mixed $api + * + * @return RetailcrmOrderBuilder + */ + public function setApi($api) + { + $this->api = $api; + return $this; + } + + /** + * @param Order|OrderCore $cmsOrder + * + * @return RetailcrmOrderBuilder + */ + public function setCmsOrder($cmsOrder) + { + $this->cmsOrder = $cmsOrder; + + if ($cmsOrder instanceof Order) { + if (is_null($this->cmsCustomer)) { + $this->cmsCustomer = $cmsOrder->getCustomer(); + } + + if (is_null($this->invoiceAddress)) { + $this->invoiceAddress = new Address($cmsOrder->id_address_invoice); + } + + if (is_null($this->deliveryAddress)) { + $this->deliveryAddress = new Address($cmsOrder->id_address_delivery); + } + } + + return $this; + } + + /** + * @param Cart|CartCore $cmsCart + * + * @return RetailcrmOrderBuilder + */ + public function setCmsCart($cmsCart) + { + $this->cmsCart = $cmsCart; + + if ($cmsCart instanceof Cart) { + if (is_null($this->cmsCustomer) && !empty($cmsCart->id_customer)) { + $this->cmsCustomer = new Customer($cmsCart->id_customer); + } + + if (is_null($this->invoiceAddress) && !empty($cmsCart->id_address_invoice)) { + $this->invoiceAddress = new Address($cmsCart->id_address_invoice); + } + + if (is_null($this->deliveryAddress) && !empty($cmsCart->id_address_delivery)) { + $this->deliveryAddress = new Address($cmsCart->id_address_delivery); + } + } + + return $this; + } + + /** + * @param mixed $cmsCustomer + * + * @return RetailcrmOrderBuilder + */ + public function setCmsCustomer($cmsCustomer) + { + $this->cmsCustomer = $cmsCustomer; + return $this; + } + + /** + * getApiSite + * + * @return string|null + */ + protected function getApiSite() + { + if (empty($this->apiSite)) { + $this->apiSite = $this->api->getSingleSiteForKey(); + } + + return (empty($this->apiSite) || is_bool($this->apiSite)) ? null : $this->apiSite; + } + + /** + * Returns order with prepared customer data. Customer is created if it's not exist, shouldn't be called to just + * build order. + * + * @param bool $dataFromCart + * + * @return array + */ + public function buildOrderWithPreparedCustomer($dataFromCart = false) + { + if (RetailcrmTools::isCorporateEnabled() && RetailcrmTools::isOrderCorporate($this->cmsOrder)) { + return $this->buildCorporateOrder($dataFromCart); + } + + return $this->buildRegularOrder($dataFromCart); + } + + /** + * Creates customer if it's not present + * + * @return array|bool + */ + private function createCustomerIfNotExist() + { + $this->validateCmsCustomer(); + + $customer = $this->findRegularCustomer(); + + if (empty($customer)) { + $crmCustomer = static::buildCrmCustomer($this->cmsCustomer, $this->buildRegularAddress()); + $createResponse = $this->api->customersCreate($crmCustomer); + + if (!$createResponse || !$createResponse->isSuccessful()) { + $this->createdCustomer = array(); + + return false; + } + + $this->createdCustomer = $this->findRegularCustomer(); + $customer = $this->createdCustomer; + } else { + $crmCustomer = RetailcrmTools::mergeCustomerAddress($customer, $this->buildRegularAddress()); + $response = $this->api->customersEdit($crmCustomer); + + if ($response instanceof RetailcrmApiResponse && $response->isSuccessful()) { + $customer = $crmCustomer; + } + } + + return isset($customer['id']) ? $customer : false; + } + + private function buildRegularOrder($dataFromCart = false) + { + $this->createCustomerIfNotExist(); + $order = static::buildCrmOrder( + $this->cmsOrder, + $this->cmsCustomer, + $this->cmsCart, + false, + false, + $dataFromCart + ); + + return RetailcrmTools::clearArray($order); + } + + /** + * Build regular customer address + * + * @return array + */ + private function buildRegularAddress() + { + $addressBuilder = new RetailcrmAddressBuilder(); + + return $addressBuilder + ->setAddress($this->invoiceAddress) + ->build() + ->getDataArray(); + } + + /** + * Builds address array for corporate customer, returns empty array in case of failure. + * + * @param bool $isMain + * + * @return array + */ + private function buildCorporateAddress($isMain = true) + { + if (empty($this->invoiceAddress) || empty($this->invoiceAddress->id)) { + return array(); + } + + $addressBuilder = new RetailcrmAddressBuilder(); + + return $addressBuilder + ->setMode(RetailcrmAddressBuilder::MODE_CORPORATE_CUSTOMER) + ->setAddress($this->invoiceAddress) + ->setIsMain($isMain) + ->setWithExternalId(true) + ->build() + ->getDataArray(); + } + + /** + * Builds company for corporate customer + * + * @param int $addressId + * + * @return array + */ + private function buildCorporateCompany($addressId = 0) + { + $companyName = ''; + $vat = ''; + + if (!empty($this->invoiceAddress)) { + if (empty($this->invoiceAddress->company)) { + $companyName = 'Main Company'; + } else { + $companyName = $this->invoiceAddress->company; + } + + if (!empty($this->invoiceAddress->vat_number)) { + $vat = $this->invoiceAddress->vat_number; + } + } + + $company = array( + 'isMain' => true, + 'name' => $companyName + ); + + if (!empty($addressId)) { + $company['address'] = array( + 'id' => $addressId + ); + } + + if (!empty($vat)) { + $company['contragent']['INN'] = $vat; + } + + return RetailcrmTools::clearArray($company); + } + + /** + * Creates new corporate customer from data, returns false in case of error + * + * @return array|bool + */ + private function createCorporateIfNotExist() + { + $corporateWasFound = true; + $this->validateCmsCustomerInDb(); + + $customer = $this->createCustomerIfNotExist(); + + if (!$customer) { + RetailcrmLogger::writeCaller(__METHOD__, 'Cannot proceed because customer is empty!'); + + return false; + } + + $crmCorporate = $this->findCorporateCustomerByContactAndCompany( + $customer['id'], + $this->invoiceAddress->company + ); + + if (empty($crmCorporate)) { + $crmCorporate = $this->findCorporateCustomerByCompany($this->invoiceAddress->company); + } + + if (empty($crmCorporate)) { + $crmCorporate = $this->createCorporateCustomer($customer['externalId']); + $corporateWasFound = false; + } elseif (isset($crmCorporate['id'])) { + $this->appendAdditionalAddressToCorporate($crmCorporate['id']); + } + + if ($corporateWasFound) { + $contactList = $this->api->customersCorporateContacts( + $crmCorporate['id'], + array('contactIds' => array($customer['id'])), + null, + null, + 'id', + $this->getApiSite() + ); + + if (!$contactList->offsetExists('contacts')) { + return $crmCorporate; + } + + if (count($contactList['contacts']) == 0) { + $contactData = array( + 'isMain' => false, + 'customer' => array( + 'id' => $customer['id'], + 'site' => $this->getApiSite() + ) + ); + + $crmCorporateCompany = $this->extractCorporateCompanyCached( + $crmCorporate['id'], + $this->invoiceAddress->company + ); + + if (!empty($crmCorporateCompany) && isset($crmCorporateCompany['id'])) { + $contactData['companies'] = array(array( + 'company' => array('id' => $crmCorporateCompany['id']) + )); + } + + $this->api->customersCorporateContactsCreate( + $crmCorporate['id'], + $contactData, + 'id', + $this->getApiSite() + ); + } + } + + return $crmCorporate; + } + + /** + * createCorporateCustomer + * + * @param string $contactPersonExternalId + * + * @return bool|array|\RetailcrmApiResponse + */ + private function createCorporateCustomer($contactPersonExternalId) + { + $customerCorporate = static::buildCrmCustomerCorporate( + $this->cmsCustomer, + $this->invoiceAddress->company, + $contactPersonExternalId, + false, + false, + $this->getApiSite() + ); + $crmCorporate = $this->api->customersCorporateCreate($customerCorporate, $this->getApiSite()); + + if (!$crmCorporate || !$crmCorporate->isSuccessful()) { + return false; + } + + $address = $this->buildCorporateAddress(); + $createResponse = $this->api->customersCorporateAddressesCreate( + $crmCorporate['id'], + $address, + 'id', + $this->getApiSite() + ); + + if ($createResponse && $createResponse->isSuccessful()) { + $company = $this->buildCorporateCompany($createResponse['id']); + $this->api->customersCorporateCompaniesCreate( + $crmCorporate['id'], + $company, + 'id', + $this->getApiSite() + ); + } + + $crmCorporate = $this->api->customersCorporateGet($crmCorporate['id'], 'id', $this->getApiSite()); + + if ($crmCorporate + && $crmCorporate->isSuccessful() + && $crmCorporate->offsetExists('customerCorporate') + ) { + return $crmCorporate['customerCorporate']; + } + + return $crmCorporate; + } + + /** + * Append new address to corporate customer if new address is not present in corporate customer. + * + * @param string|int $corporateId + */ + private function appendAdditionalAddressToCorporate($corporateId) + { + $request = new RetailcrmApiPaginatedRequest(); + $address = $this->buildCorporateAddress(false); + $addresses = $request + ->setApi($this->api) + ->setMethod('customersCorporateAddresses') + ->setParams(array( + $corporateId, + array(), + '{{page}}', + '{{limit}}', + 'id', + $this->getApiSite() + )) + ->setDataKey('addresses') + ->execute() + ->getData(); + + foreach ($addresses as $addressInCrm) { + if (!empty($addressInCrm['externalId']) && $addressInCrm['externalId'] == $this->invoiceAddress->id) { + $this->api->customersCorporateAddressesEdit( + $corporateId, + $addressInCrm['externalId'], + $address, + 'id', + 'externalId', + $this->getApiSite() + ); + + return; + } + } + + $this->api->customersCorporateAddressesCreate( + $corporateId, + $address, + 'id', + $this->getApiSite() + ); + } + + /** + * Find self::cmsCustomer in retailCRM by id or by email + * + * @return array|mixed + */ + private function findRegularCustomer() + { + $this->validateCmsCustomer(); + + if (empty($this->cmsCustomer->id) || $this->cmsCustomer->is_guest) { + if (!empty($this->cmsCustomer->email)) { + $customers = $this->api->customersList(array('email' => $this->cmsCustomer->email)); + + if ($customers + && $customers->isSuccessful() + && $customers->offsetExists('customers') + && !empty($customers['customers']) + ) { + $customers = $customers['customers']; + + return reset($customers); + } + } + } else { + $customer = $this->api->customersGet($this->cmsCustomer->id); + + if ($customer && $customer->isSuccessful() && $customer->offsetExists('customer')) { + return $customer['customer']; + } + } + + return array(); + } + + /** + * Finds all corporate customers with specified contact id and filters them by provided main company name + * + * @param $contactId + * @param $companyName + * + * @return array + */ + private function findCorporateCustomerByContactAndCompany($contactId, $companyName) + { + $crmCorporate = $this->api->customersCorporateList(array( + 'contactIds' => array($contactId), + 'companyName' => $companyName + )); + + if ($crmCorporate instanceof RetailcrmApiResponse + && $crmCorporate->isSuccessful() + && $crmCorporate->offsetExists('customersCorporate') + && count($crmCorporate['customersCorporate']) > 0 + ) { + $crmCorporate = $crmCorporate['customersCorporate']; + + return reset($crmCorporate); + } + + return array(); + } + + /** + * Find corporate customer by company name + * + * @param $companyName + * + * @return array + */ + private function findCorporateCustomerByCompany($companyName) + { + $crmCorporate = $this->api->customersCorporateList(array( + 'companyName' => $companyName + )); + + if ($crmCorporate instanceof RetailcrmApiResponse + && $crmCorporate->isSuccessful() + && $crmCorporate->offsetExists('customersCorporate') + && count($crmCorporate['customersCorporate']) > 0 + ) { + $crmCorporate = $crmCorporate['customersCorporate']; + + return reset($crmCorporate); + } + + return array(); + } + + /** + * Get corporate companies, extract company data by provided identifiers + * + * @param int|string $corporateCrmId + * @param string $companyName + * @param string $by + * + * @return array + */ + private function extractCorporateCompany($corporateCrmId, $companyName, $by = 'id') + { + $companiesResponse = $this->api->customersCorporateCompanies( + $corporateCrmId, + array(), + null, + null, + $by + ); + + if ($companiesResponse instanceof RetailcrmApiResponse + && $companiesResponse->isSuccessful() + && $companiesResponse->offsetExists('companies') + && count($companiesResponse['companies']) > 0 + ) { + $company = array_reduce( + $companiesResponse['companies'], + function ($carry, $item) use ($companyName) { + if (is_array($item) && isset($item['name']) && $item['name'] == $companyName) { + $carry = $item; + } + + return $carry; + } + ); + + if (is_array($company)) { + return $company; + } + } + + return array(); + } + + /** + * extractCorporateCompany with cache + * + * @param int|string $corporateCrmId + * @param string $companyName + * @param string $by + * + * @return array + */ + private function extractCorporateCompanyCached($corporateCrmId, $companyName, $by = 'id') + { + $cachedItemId = sprintf('%s:%s', (string) $corporateCrmId, $companyName); + + if (!is_array($this->corporateCompanyExtractCache)) { + $this->corporateCompanyExtractCache = array(); + } + + if (!isset($this->corporateCompanyExtractCache[$cachedItemId])) { + $this->corporateCompanyExtractCache[$cachedItemId] = $this->extractCorporateCompany( + $corporateCrmId, + $companyName, + $by + ); + } + + return $this->corporateCompanyExtractCache[$cachedItemId]; + } + + /** + * Throws exception if cmsCustomer is not set + * + * @throws \InvalidArgumentException + */ + private function validateCmsCustomer() + { + if (is_null($this->cmsCustomer)) { + throw new \InvalidArgumentException("RetailcrmOrderBuilder::cmsCustomer must be set"); + } + } + + /** + * Throws exception if cmsCustomer is not set or it's not present in DB yet + * + * @throws \InvalidArgumentException + */ + private function validateCmsCustomerInDb() + { + $this->validateCmsCustomer(); + + if (empty($this->cmsCustomer->id)) { + throw new \InvalidArgumentException("RetailcrmOrderBuilder::cmsCustomer must be stored in DB"); + } + } + + private function buildCorporateOrder($dataFromCart = false) + { + $customer = $this->createCorporateIfNotExist(); + $contactPersonId = ''; + $contactPersonExternalId = ''; + + if (empty($customer)) { + return array(); + } + + if (empty($this->cmsCustomer->id)) { + $contacts = $this->api->customersList(array('email' => $this->cmsCustomer->email)); + + if ($contacts + && $contacts->isSuccessful() + && $contacts->offsetExists('customers') + && !empty($contacts['customers']) + ) { + $contacts = $contacts['customers']; + $contactPerson = reset($contacts); + + if (isset($contactPerson['id'])) { + $contactPersonId = $contactPerson['id']; + } + } + } else { + $contacts = $this->api->customersCorporateContacts( + $customer['id'], + array('contactExternalIds' => array($this->cmsCustomer->id)), + null, + null, + 'id', + $this->getApiSite() + ); + + if ($contacts + && $contacts->isSuccessful() + && $contacts->offsetExists('contacts') + && count($contacts['contacts']) == 1 + ) { + $contactPersonExternalId = $this->cmsCustomer->id; + } + } + + return static::buildCrmOrder( + $this->cmsOrder, + $this->cmsCustomer, + $this->cmsCart, + false, + false, + $dataFromCart, + $contactPersonId, + $contactPersonExternalId, + $customer['id'], + $this->getApiSite() + ); + } + + /** + * Build array with order data for retailCRM from PrestaShop order data + * + * @param Order|\OrderCore $order PrestaShop Order + * @param Customer|\CustomerCore|null $customer PrestaShop Customer + * @param Cart|\CartCore|null $orderCart Cart for provided order. Optional + * @param bool $isStatusExport Use status for export + * @param bool $preferCustomerAddress Use customer address even if delivery address is + * provided + * @param bool $dataFromCart Prefer data from cart + * @param string $contactPersonId Contact person id to append + * @param string $contactPersonExternalId Contact person externalId to append. + * @param string $corporateCustomerId Corporate customer id (will be used instead of regular + * customer) + * @param string $site Site code (for customer only) + * + * @return array retailCRM order data + * @todo Refactor into OrderBuilder (current order builder should be divided into several independent builders). + */ + public static function buildCrmOrder( + $order, + $customer = null, + $orderCart = null, + $isStatusExport = false, + $preferCustomerAddress = false, + $dataFromCart = false, + $contactPersonId = '', + $contactPersonExternalId = '', + $corporateCustomerId = '', + $site = '' + ) { + $statusExport = Configuration::get(RetailCRM::STATUS_EXPORT); + $delivery = json_decode(Configuration::get(RetailCRM::DELIVERY), true); + $payment = json_decode(Configuration::get(RetailCRM::PAYMENT), true); + $status = json_decode(Configuration::get(RetailCRM::STATUS), true); + + if (Module::getInstanceByName('advancedcheckout') === false) { + $paymentType = $order->module; + } else { + $paymentType = $order->payment; + } + + if ($order->current_state == 0) { + $order_status = $statusExport; + + if (!$isStatusExport) { + $order_status = + array_key_exists($order->current_state, $status) + ? $status[$order->current_state] : 'new'; + } + } else { + $order_status = array_key_exists($order->current_state, $status) + ? $status[$order->current_state] + : $statusExport; + } + + $cart = $orderCart; + + if (is_null($cart)) { + $cart = new Cart($order->getCartIdStatic($order->id)); + } + + if (is_null($customer)) { + $customer = new Customer($order->id_customer); + } + + $crmOrder = array_filter(array( + 'externalId' => $order->id, + 'number' => $order->id, + 'createdAt' => RetailcrmTools::verifyDate($order->date_add, 'Y-m-d H:i:s') + ? $order->date_add : date('Y-m-d H:i:s'), + 'status' => $order_status, + 'firstName' => $customer->firstname, + 'lastName' => $customer->lastname, + 'email' => $customer->email, + )); + + $addressCollection = $cart->getAddressCollection(); + $addressDelivery = new Address($order->id_address_delivery); + $addressInvoice = new Address($order->id_address_invoice); + + if (is_null($addressDelivery->id) || $preferCustomerAddress === true) { + $addressDelivery = array_filter( + $addressCollection, + function ($v) use ($customer) { + return $v->id_customer == $customer->id; + } + ); + + if (is_array($addressDelivery) && count($addressDelivery) == 1) { + $addressDelivery = reset($addressDelivery); + } + } + + $addressBuilder = new RetailcrmAddressBuilder(); + $addressBuilder + ->setMode(RetailcrmAddressBuilder::MODE_ORDER_DELIVERY) + ->setAddress($addressDelivery) + ->build(); + $crmOrder = array_merge($crmOrder, $addressBuilder->getDataArray()); + + if ($addressInvoice instanceof Address && !empty($addressInvoice->company)) { + $crmOrder['contragent']['legalName'] = $addressInvoice->company; + + if (!empty($addressInvoice->vat_number)) { + $crmOrder['contragent']['INN'] = $addressInvoice->vat_number; + } + } + + if (isset($payment[$paymentType]) && !empty($payment[$paymentType])) { + $order_payment = array( + 'externalId' => $order->id . '#' . $order->reference, + 'amount' => round($order->total_paid, 2), + 'type' => $payment[$paymentType] + ); + + $crmOrder['discountManualAmount'] = round($order->total_discounts, 2); + } + + if (isset($order_payment)) { + $crmOrder['payments'][] = $order_payment; + } else { + $crmOrder['payments'] = array(); + } + + $idCarrier = $dataFromCart ? $cart->id_carrier : $order->id_carrier; + + if (empty($idCarrier)) { + $idCarrier = $order->id_carrier; + $totalShipping = $order->total_shipping; + $totalShippingWithoutTax = $order->total_shipping_tax_excl; + } else { + $totalShipping = $dataFromCart ? $cart->getCarrierCost($idCarrier) : $order->total_shipping; + + if (!empty($totalShipping) && $totalShipping != 0) { + $totalShippingWithoutTax = $dataFromCart + ? $totalShipping - $cart->getCarrierCost($idCarrier, false) + : $order->total_shipping_tax_excl; + } else { + $totalShippingWithoutTax = $order->total_shipping_tax_excl; + } + } + + // TODO Shouldn't cause any errors while creating order even if correspondent carrier is not set. + if (array_key_exists($idCarrier, $delivery) && !empty($delivery[$idCarrier])) { + $crmOrder['delivery']['code'] = $delivery[$idCarrier]; + } + + if (isset($totalShipping) && ((int)$totalShipping) > 0) { + $crmOrder['delivery']['cost'] = round($totalShipping, 2); + } + + if (isset($totalShippingWithoutTax) && $totalShippingWithoutTax > 0) { + $crmOrder['delivery']['netCost'] = round($totalShippingWithoutTax, 2); + } + + $comment = $order->getFirstMessage(); + + if ($comment !== false) { + $crmOrder['customerComment'] = $comment; + } + + if ($dataFromCart) { + $productStore = $cart; + $converter = function ($product) { + $map = array( + 'product_attribute_id' => 'id_product_attribute', + 'product_quantity' => 'cart_quantity', + 'product_id' => 'id_product', + 'id_order_detail' => 'id_product', + 'product_name' => 'name', + 'product_price' => 'price', + 'purchase_supplier_price' => 'price', + 'product_price_wt' => 'price_wt' + ); + + foreach ($map as $target => $value) { + if (isset($product[$value])) { + $product[$target] = $product[$value]; + } + } + + return $product; + }; + } else { + $productStore = $order; + $converter = function ($product) { + return $product; + }; + } + + foreach ($productStore->getProducts() as $productData) { + $product = $converter($productData); + + if (isset($product['product_attribute_id']) && $product['product_attribute_id'] > 0) { + $productId = $product['product_id'] . '#' . $product['product_attribute_id']; + } else { + $productId = $product['product_id']; + } + + if (isset($product['attributes']) && $product['attributes']) { + $arProp = array(); + $count = 0; + $arAttr = explode(",", $product['attributes']); + + foreach ($arAttr as $valAttr) { + $arItem = explode(":", $valAttr); + + if ($arItem[0] && $arItem[1]) { + $arProp[$count]['name'] = trim($arItem[0]); + $arProp[$count]['value'] = trim($arItem[1]); + } + + $count++; + } + } + + $item = array( + "externalIds" => array( + array( + 'code' => 'prestashop', + 'value' => $productId . "_" . $product['id_order_detail'], + ), + ), + 'offer' => array('externalId' => $productId), + 'productName' => $product['product_name'], + 'quantity' => $product['product_quantity'], + 'initialPrice' => round($product['product_price'], 2), + /*'initialPrice' => !empty($item['rate']) + ? $item['price'] + ($item['price'] * $item['rate'] / 100) + : $item['price'],*/ + 'purchasePrice' => round($product['purchase_supplier_price'], 2) + ); + + if (true == Configuration::get('PS_TAX') && isset($product['product_price_wt'])) { + $item['initialPrice'] = round($product['product_price_wt'], 2); + } + + if (isset($arProp)) { + $item['properties'] = $arProp; + } + + $crmOrder['items'][] = $item; + } + + if ($order->id_customer) { + if (empty($corporateCustomerId)) { + $crmOrder['customer']['externalId'] = $order->id_customer; + } else { + $crmOrder['customer']['id'] = $corporateCustomerId; + } + + if (!empty($contactPersonExternalId)) { + $crmOrder['contact']['externalId'] = $contactPersonExternalId; + $crmOrder['contact']['site'] = $site; + } elseif (!empty($contactPersonId)) { + $crmOrder['contact']['id'] = $contactPersonId; + $crmOrder['contact']['site'] = $site; + } + + if (!empty($site)) { + $crmOrder['customer']['site'] = $site; + } + + if (RetailcrmTools::isCorporateEnabled() && RetailcrmTools::isOrderCorporate($order)) { + $crmOrder['contragent']['contragentType'] = 'legal-entity'; + } else { + $crmOrder['contragent']['contragentType'] = 'individual'; + } + } + + return RetailcrmTools::clearArray($crmOrder); + } + + /** + * Build array with order data for retailCRM from PrestaShop cart data + * + * @param \RetailcrmProxy|\RetailcrmApiClientV5 $api + * @param Cart $cart Cart with data + * @param string $externalId External ID for order + * @param string $paymentType Payment type (buildCrmOrder requires it) + * @param string $status Status for order + * + * @return array + * @throws \Exception + */ + public static function buildCrmOrderFromCart($api, $cart = null, $externalId = '', $paymentType = '', $status = '') + { + if (empty($cart) || empty($paymentType) || empty($status)) { + return array(); + } + + try { + $order = new Order(); + $order->id_cart = $cart->id; + $order->id_customer = $cart->id_customer; + $order->id_address_delivery = $cart->id_address_delivery; + $order->id_address_invoice = $cart->id_address_invoice; + $order->id_currency = $cart->id_currency; + $order->id_carrier = $cart->id_carrier; + $order->total_discounts = $cart->getOrderTotal(true, Cart::ONLY_DISCOUNTS);; + $order->module = $paymentType; + $order->payment = $paymentType; + + if (!empty($cart->id_carrier)) { + $order->total_shipping = $cart->getPackageShippingCost(); + $order->total_shipping_tax_excl = $cart->getPackageShippingCost(null, false); + } + + $orderBuilder = new RetailcrmOrderBuilder(); + $orderData = $orderBuilder + ->defaultLangFromConfiguration() + ->setApi($api) + ->setCmsOrder($order) + ->setCmsCart($cart) + ->setCmsCustomer(new Customer($cart->id_customer)) + ->buildOrderWithPreparedCustomer(true); + $orderData['externalId'] = $externalId; + $orderData['status'] = $status; + + unset($orderData['payments']); + + return RetailcrmTools::clearArray($orderData); + } catch (\InvalidArgumentException $exception) { + RetailcrmLogger::writeCaller( + 'buildCrmOrderFromCart', + $exception->getMessage() + ); + + return array(); + } + } + + /** + * Builds retailCRM customer data from PrestaShop customer data + * + * @param Customer $object + * @param array $address + * + * @return array + */ + public static function buildCrmCustomer(Customer $object, $address = array()) + { + return array_filter(array_merge( + array( + 'externalId' => !empty($object->id) ? $object->id : null, + 'firstName' => $object->firstname, + 'lastName' => $object->lastname, + 'email' => $object->email, + 'subscribed' => $object->newsletter, + 'createdAt' => RetailcrmTools::verifyDate($object->date_add, 'Y-m-d H:i:s') + ? $object->date_add : date('Y-m-d H:i:s'), + 'birthday' => RetailcrmTools::verifyDate($object->birthday, 'Y-m-d') + ? $object->birthday : '', + 'sex' => $object->id_gender == "0" ? "" : ($object->id_gender == "1" ? "male" : "female") + ), + $address + ), function ($value) { + return !($value === '' || $value === null || (is_array($value) ? count($value) == 0 : false)); + }); + } + + public static function buildCrmCustomerCorporate( + Customer $object, + $nickName = '', + $contactExternalId = '', + $appendAddress = false, + $appendCompany = false, + $site = '' + ) { + $customerAddresses = array(); + $addresses = $object->getAddresses((int)Configuration::get('PS_LANG_DEFAULT')); + $customer = array( + 'addresses' => array(), + 'companies' => array() + ); + $company = array( + 'isMain' => true, + 'externalId' => null, + 'active' => true, + 'name' => '' + ); + + // TODO: $company['contragent']['INN'] may not work, should check that later... + foreach ($addresses as $address) { + $addressBuilder = new RetailcrmAddressBuilder(); + + if ($address instanceof Address && !empty($address->company)) { + $customerAddresses[] = $addressBuilder + ->setMode(RetailcrmAddressBuilder::MODE_CORPORATE_CUSTOMER) + ->setAddress($address) + ->setWithExternalId(true) + ->build() + ->getDataArray(); + $customer['nickName'] = empty($nickName) ? $address->company : $nickName; + $company['name'] = $address->company; + $company['contragent']['INN'] = $address->vat_number; + $company['externalId'] = 'company_'.$address->id; + } + + if (is_array($address) && !empty($address['company'])) { + $customerAddresses[] = $addressBuilder + ->setMode(RetailcrmAddressBuilder::MODE_CORPORATE_CUSTOMER) + ->setAddressId($address['id_address']) + ->setWithExternalId(true) + ->build() + ->getDataArray(); + $customer['nickName'] = empty($nickName) ? $address->company : $nickName; + $company['name'] = $address['company']; + $company['contragent']['INN'] = $address['vat_number']; + $company['externalId'] = 'company_'.$address['id_address']; + } + } + + if ($appendCompany && !is_null($company['externalId'])) { + $customer['companies'][] = $company; + } + + if (!empty($contactExternalId) && !empty($site)) { + $customer['customerContacts'] = array(array( + 'isMain' => true, + 'customer' => array( + 'externalId' => $contactExternalId, + 'site' => $site + ) + )); + + if (!empty($customer['companies']) + && isset($customer['companies'][0]) + && isset($customer['companies'][0]['externalId']) + ) { + $customer['customerContacts'][0]['companies'] = array(array( + 'company' => array('externalId' => $customer['companies'][0]['externalId']) + )); + } + } + + if ($appendAddress) { + $customer['addresses'] = $customerAddresses; + } + + return RetailcrmTools::clearArray($customer); + } +} diff --git a/retailcrm/lib/RetailcrmPrestashopLoader.php b/retailcrm/lib/RetailcrmPrestashopLoader.php new file mode 100644 index 0000000..9022ec4 --- /dev/null +++ b/retailcrm/lib/RetailcrmPrestashopLoader.php @@ -0,0 +1,42 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +$_SERVER['HTTPS'] = 1; + +require_once(dirname(__FILE__) . '/../../../config/config.inc.php'); +require_once(dirname(__FILE__) . '/../../../init.php'); +require_once(dirname(__FILE__) . '/../bootstrap.php'); \ No newline at end of file diff --git a/retailcrm/lib/RetailcrmProxy.php b/retailcrm/lib/RetailcrmProxy.php deleted file mode 100755 index 17a5ecd..0000000 --- a/retailcrm/lib/RetailcrmProxy.php +++ /dev/null @@ -1,55 +0,0 @@ -api = new RetailcrmApiClientV5($url, $key); - break; - case '4': - $this->api = new RetailcrmApiClientV4($url, $key); - break; - case '3': - $this->api = new RetailcrmApiClientV3($url, $key); - break; - } - - $this->log = $log; - } - - public function __call($method, $arguments) - { - $date = date('Y-m-d H:i:s'); - try { - $response = call_user_func_array(array($this->api, $method), $arguments); - - if (!$response->isSuccessful()) { - error_log("[$date] @ [$method] " . $response->getErrorMsg() . "\n", 3, $this->log); - if (isset($response['errors'])) { - RetailcrmApiErrors::set($response['errors'], $response->getStatusCode()); - $error = implode("\n", $response['errors']); - error_log($error . "\n", 3, $this->log); - } - $response = false; - } - - return $response; - } catch (CurlException $e) { - error_log("[$date] @ [$method] " . $e->getMessage() . "\n", 3, $this->log); - return false; - } catch (InvalidJsonException $e) { - error_log("[$date] @ [$method] " . $e->getMessage() . "\n", 3, $this->log); - return false; - } - } - -} diff --git a/retailcrm/lib/RetailcrmReferences.php b/retailcrm/lib/RetailcrmReferences.php old mode 100755 new mode 100644 index c5a3957..d47bd52 --- a/retailcrm/lib/RetailcrmReferences.php +++ b/retailcrm/lib/RetailcrmReferences.php @@ -1,5 +1,40 @@ + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ class RetailcrmReferences { public $default_lang; @@ -33,6 +68,7 @@ class RetailcrmReferences 'type' => 'select', 'label' => $carrier['name'], 'name' => 'RETAILCRM_API_DELIVERY[' . $carrier['id_carrier'] . ']', + 'subname' => $carrier['id_carrier'], 'required' => false, 'options' => array( 'query' => $apiDeliveryTypes, @@ -60,6 +96,7 @@ class RetailcrmReferences 'type' => 'select', 'label' => $state['name'], 'name' => "RETAILCRM_API_STATUS[$key]", + 'subname' => $key, 'required' => false, 'options' => array( 'query' => $apiStatuses, @@ -86,6 +123,7 @@ class RetailcrmReferences 'type' => 'select', 'label' => $payment['name'], 'name' => 'RETAILCRM_API_PAYMENT[' . $payment['code'] . ']', + 'subname' => $payment['code'], 'required' => false, 'options' => array( 'query' => $apiPaymentTypes, @@ -165,27 +203,31 @@ class RetailcrmReferences public function getSystemPaymentModules($active = true) { - $shop_id = Context::getContext()->shop->id; + $shop_id = (int) Context::getContext()->shop->id; - /* Get all modules then select only payment ones */ - $modules = Module::getModulesOnDisk(true); + /** + * Get all modules then select only payment ones + */ + $modules = RetailCRM::getCachedCmsModulesList(); foreach ($modules as $module) { - if ($module->tab == 'payments_gateways') { + if (!empty($module->parent_class) && $module->parent_class == 'PaymentModule') { if ($module->id) { + $module_id = (int) $module->id; + if (!get_class($module) == 'SimpleXMLElement') $module->country = array(); - $countries = DB::getInstance()->executeS('SELECT id_country FROM ' . _DB_PREFIX_ . 'module_country WHERE id_module = ' . (int) $module->id . ' AND `id_shop`=' . (int) $shop_id); + $countries = DB::getInstance()->executeS('SELECT id_country FROM ' . _DB_PREFIX_ . 'module_country WHERE id_module = ' . pSQL($module_id) . ' AND `id_shop`=' . pSQL($shop_id)); foreach ($countries as $country) $module->country[] = $country['id_country']; if (!get_class($module) == 'SimpleXMLElement') $module->currency = array(); - $currencies = DB::getInstance()->executeS('SELECT id_currency FROM ' . _DB_PREFIX_ . 'module_currency WHERE id_module = ' . (int) $module->id . ' AND `id_shop`=' . (int) $shop_id); + $currencies = DB::getInstance()->executeS('SELECT id_currency FROM ' . _DB_PREFIX_ . 'module_currency WHERE id_module = ' . pSQL($module_id) . ' AND `id_shop`=' . pSQL($shop_id)); foreach ($currencies as $currency) $module->currency[] = $currency['id_currency']; if (!get_class($module) == 'SimpleXMLElement') $module->group = array(); - $groups = DB::getInstance()->executeS('SELECT id_group FROM ' . _DB_PREFIX_ . 'module_group WHERE id_module = ' . (int) $module->id . ' AND `id_shop`=' . (int) $shop_id); + $groups = DB::getInstance()->executeS('SELECT id_group FROM ' . _DB_PREFIX_ . 'module_group WHERE id_module = ' . pSQL($module_id) . ' AND `id_shop`=' . pSQL($shop_id)); foreach ($groups as $group) $module->group[] = $group['id_group']; } else { diff --git a/retailcrm/lib/RetailcrmService.php b/retailcrm/lib/RetailcrmService.php deleted file mode 100755 index 86df68d..0000000 --- a/retailcrm/lib/RetailcrmService.php +++ /dev/null @@ -1,41 +0,0 @@ - + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +class RetailcrmTools +{ + /** + * @var int + */ + static $currentStatusCode; + + /** + * Returns true if corporate customers are enabled in settings + * + * @return bool + */ + public static function isCorporateEnabled() + { + return (bool)Configuration::get(RetailCRM::ENABLE_CORPORATE_CLIENTS); + } + + /** + * Returns true if customer is corporate + * + * @param Customer $customer + * + * @return bool + */ + public static function isCustomerCorporate(Customer $customer) + { + $addresses = $customer->getAddresses((int)Configuration::get('PS_LANG_DEFAULT')); + + foreach ($addresses as $address) { + if (($address instanceof Address && !empty($address->company)) + || (is_array($address) && !empty($address['company'])) + ) { + return true; + } + } + + return false; + } + + /** + * Returns true if order is corporate + * + * @param Order $order + * + * @return bool + */ + public static function isOrderCorporate(Order $order) + { + if (empty($order->id_address_invoice)) { + return false; + } + + $address = new Address($order->id_address_invoice); + + return $address instanceof Address && !empty($address->company); + } + + /** + * Returns 'true' if provided date string is valid + * + * @param $date + * @param string $format + * + * @return bool + */ + public static function verifyDate($date, $format = "Y-m-d") + { + return $date !== "0000-00-00" && (bool)date_create_from_format($format, $date); + } + + /** + * Split a string to id + * + * @param string $ids string with id + * + * @return array|string + */ + public static function partitionId($ids) + { + $ids = explode(',', $ids); + + $ranges = array(); + + foreach ($ids as $idx => $uid) { + if (strpos($uid, '-')) { + $range = explode('-', $uid); + $ranges = array_merge($ranges, range($range[0], $range[1])); + unset($ids[$idx]); + } + } + + $ids = implode(',', array_merge($ids, $ranges)); + $ids = explode(',', $ids); + + return $ids; + } + + /** + * Converts CMS address to CRM address + * + * @param $address + * @param array $customer + * @param array $order + * @deprecated Replaced with RetailcrmAddressBuilder + * + * @return array + */ + public static function addressParse($address, &$customer = array(), &$order = array()) + { + if (!isset($customer)) { + $customer = array(); + } + + if (!isset($order)) { + $order = array(); + } + + if ($address instanceof Address) { + $postcode = $address->postcode; + $city = $address->city; + $addres_line = sprintf("%s %s", $address->address1, $address->address2); + $countryIso = Country::getIsoById($address->id_country); + $vat = $address->vat_number; + } + + if (!empty($postcode)) { + $customer['address']['index'] = $postcode; + $order['delivery']['address']['index'] = $postcode; + } + + if (!empty($city)) { + $customer['address']['city'] = $city; + $order['delivery']['address']['city'] = $city; + } + + if (!empty($addres_line)) { + $customer['address']['text'] = $addres_line; + $order['delivery']['address']['text'] = $addres_line; + } + + if (!empty($countryIso)) { + $order['countryIso'] = $countryIso; + $customer['address']['countryIso'] = $countryIso; + } + + $phones = static::getPhone($address, $customer, $order); + $order = array_merge($order, $phones['order']); + $customer = array_merge($customer, $phones['customer']); + + return array( + 'order' => RetailcrmTools::clearArray($order), + 'customer' => RetailcrmTools::clearArray($customer), + 'vat' => isset($vat) && !empty($vat) ? $vat : '' + ); + } + + public static function getPhone($address, &$customer = array(), &$order = array()) + { + if (!isset($customer)) { + $customer = array(); + } + + if (!isset($order)) { + $order = array(); + } + + if (!empty($address->phone_mobile)) { + $order['phone'] = $address->phone_mobile; + $customer['phones'][] = array('number'=> $address->phone_mobile); + } + + if (!empty($address->phone)) { + $order['additionalPhone'] = $address->phone; + $customer['phones'][] = array('number'=> $address->phone); + } + + if (!isset($order['phone']) && !empty($order['additionalPhone'])) { + $order['phone'] = $order['additionalPhone']; + unset($order['additionalPhone']); + } + + $phonesArray = array('customer' => $customer, 'order' => $order); + + return $phonesArray; + } + + /** + * Validate crm address + * + * @param $address + * + * @return bool + */ + public static function validateCrmAddress($address) + { + if (preg_match("/https:\/\/(.*).retailcrm.(pro|ru|es)/", $address) === 1) { + return true; + } + + return false; + } + + public static function getDate($file) + { + if (file_exists($file)) { + $result = file_get_contents($file); + } else { + $result = date('Y-m-d H:i:s', strtotime('-1 days', strtotime(date('Y-m-d H:i:s')))); + } + + return $result; + } + + public static function explodeFIO($string) + { + $result = array(); + $parse = (!$string) ? false : explode(" ", $string, 3); + + switch (count($parse)) { + case 1: + $result['firstName'] = $parse[0]; + break; + case 2: + $result['firstName'] = $parse[1]; + $result['lastName'] = $parse[0]; + break; + case 3: + $result['firstName'] = $parse[1]; + $result['lastName'] = $parse[0]; + $result['patronymic'] = $parse[2]; + break; + default: + return false; + } + + return $result; + } + + /** + * Returns externalId for order + * + * @param Cart $cart + * + * @return string + */ + public static function getCartOrderExternalId(Cart $cart) + { + return sprintf('pscart_%d', $cart->id); + } + + /** + * Unset empty fields + * + * @param array $arr input array + * @param callable|null $filterFunc + * + * @return array + * @todo Don't filter out false & all methods MUST NOT use false as blank value. + */ + public static function clearArray(array $arr, $filterFunc = null) + { + if (!is_array($arr)) { + return $arr; + } + + $result = array(); + + foreach ($arr as $index => $node) { + $result[$index] = (is_array($node)) + ? self::clearArray($node) + : $node; + + if ($result[$index] == '' + || $result[$index] === null + || (is_array($result[$index]) && count($result[$index]) < 1) + ) { + unset($result[$index]); + } + } + + if (is_callable($filterFunc)) { + return array_filter($result, $filterFunc); + } + + return array_filter($result); + } + + /** + * Returns true if PrestaShop in debug mode or _RCRM_MODE_DEV_ const defined to true. + * Add define('_RCRM_MODE_DEV_', true); to enable extended logging (dev mode) ONLY for retailCRM module. + * In developer mode module will log every JobManager run and every request and response from retailCRM API. + * + * @return bool + */ + public static function isDebug() + { + return (defined('_PS_MODE_DEV_') && _PS_MODE_DEV_ == true) + || (defined('_RCRM_MODE_DEV_') && _RCRM_MODE_DEV_ == true); + } + + /** + * Generates placeholder email + * + * @param string $name + * + * @return string + */ + public static function createPlaceholderEmail($name) + { + return substr(md5($name), 0, 15) . '@example.com'; + } + + /** + * Returns API client proxy if connection is configured. + * Returns null if connection is not configured. + * + * @return \RetailcrmProxy|\RetailcrmApiClientV5|null + */ + public static function getApiClient() + { + $apiUrl = Configuration::get(RetailCRM::API_URL); + $apiKey = Configuration::get(RetailCRM::API_KEY); + + if (!empty($apiUrl) && !empty($apiKey)) { + return new RetailcrmProxy($apiUrl, $apiKey, RetailCRM::getErrorLog()); + } + + return null; + } + + /** + * Merge new address to customer, preserves old phone numbers. + * + * @param array $customer + * @param array $address + * + * @return array + */ + public static function mergeCustomerAddress($customer, $address) + { + $customerPhones = isset($customer['phones']) ? $customer['phones'] : array(); + $addressPhones = isset($address['phones']) ? $address['phones'] : array(); + $squashedCustomerPhones = array_filter(array_map(function ($val) { + return isset($val['number']) ? $val['number'] : null; + }, $customerPhones)); + + foreach ($addressPhones as $newPhone) { + if (empty($newPhone['number'])) { + continue; + } + + if (!in_array($newPhone['number'], $squashedCustomerPhones)) { + $customerPhones[] = $newPhone; + } + } + + return array_merge($customer, $address, array('phones' => $customerPhones)); + } + + /** + * http_response_code polyfill + * + * @param null $code + * + * @return int|null + */ + public static function http_response_code($code = null) + { + if (function_exists('http_response_code')) { + $code = http_response_code($code); + } else { + if ($code !== NULL) { + switch ($code) { + case 100: $text = 'Continue'; break; + case 101: $text = 'Switching Protocols'; break; + case 200: $text = 'OK'; break; + case 201: $text = 'Created'; break; + case 202: $text = 'Accepted'; break; + case 203: $text = 'Non-Authoritative Information'; break; + case 204: $text = 'No Content'; break; + case 205: $text = 'Reset Content'; break; + case 206: $text = 'Partial Content'; break; + case 300: $text = 'Multiple Choices'; break; + case 301: $text = 'Moved Permanently'; break; + case 302: $text = 'Moved Temporarily'; break; + case 303: $text = 'See Other'; break; + case 304: $text = 'Not Modified'; break; + case 305: $text = 'Use Proxy'; break; + case 400: $text = 'Bad Request'; break; + case 401: $text = 'Unauthorized'; break; + case 402: $text = 'Payment Required'; break; + case 403: $text = 'Forbidden'; break; + case 404: $text = 'Not Found'; break; + case 405: $text = 'Method Not Allowed'; break; + case 406: $text = 'Not Acceptable'; break; + case 407: $text = 'Proxy Authentication Required'; break; + case 408: $text = 'Request Time-out'; break; + case 409: $text = 'Conflict'; break; + case 410: $text = 'Gone'; break; + case 411: $text = 'Length Required'; break; + case 412: $text = 'Precondition Failed'; break; + case 413: $text = 'Request Entity Too Large'; break; + case 414: $text = 'Request-URI Too Large'; break; + case 415: $text = 'Unsupported Media Type'; break; + case 500: $text = 'Internal Server Error'; break; + case 501: $text = 'Not Implemented'; break; + case 502: $text = 'Bad Gateway'; break; + case 503: $text = 'Service Unavailable'; break; + case 504: $text = 'Gateway Time-out'; break; + case 505: $text = 'HTTP Version not supported'; break; + default: + exit('Unknown http status code "' . htmlentities($code) . '"'); + break; + } + + $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0'); + + header($protocol . ' ' . $code . ' ' . $text); + } else { + $code = !empty(static::$currentStatusCode) ? static::$currentStatusCode : 200; + } + } + + return $code; + } +} diff --git a/retailcrm/lib/api/CurlException.php b/retailcrm/lib/api/CurlException.php new file mode 100644 index 0000000..f254426 --- /dev/null +++ b/retailcrm/lib/api/CurlException.php @@ -0,0 +1,40 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +class CurlException extends \RuntimeException +{ +} diff --git a/retailcrm/lib/api/InvalidJsonException.php b/retailcrm/lib/api/InvalidJsonException.php new file mode 100644 index 0000000..6422239 --- /dev/null +++ b/retailcrm/lib/api/InvalidJsonException.php @@ -0,0 +1,40 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +class InvalidJsonException extends \DomainException +{ +} diff --git a/retailcrm/lib/RetailcrmApiClientV5.php b/retailcrm/lib/api/RetailcrmApiClientV5.php old mode 100755 new mode 100644 similarity index 70% rename from retailcrm/lib/RetailcrmApiClientV5.php rename to retailcrm/lib/api/RetailcrmApiClientV5.php index 4d842f6..a7906d6 --- a/retailcrm/lib/RetailcrmApiClientV5.php +++ b/retailcrm/lib/api/RetailcrmApiClientV5.php @@ -1,26 +1,57 @@ - * @license https://opensource.org/licenses/MIT MIT License - * @link http://www.retailcrm.ru/docs/Developers/ApiVersion5 + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to http://www.prestashop.com for more information. + * + * @author DIGITAL RETAIL TECHNOLOGIES SL + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. */ class RetailcrmApiClientV5 { const VERSION = 'v5'; protected $client; + protected $unversionedClient; /** * Site code */ protected $siteCode; + /** + * API Key + */ + protected $apiKey; + /** * Client creating * @@ -34,6 +65,8 @@ class RetailcrmApiClientV5 */ public function __construct($url, $apiKey, $site = null) { + $unversionedUrl = ('/' !== $url[strlen($url) - 1] ? $url . '/' : $url) . 'api'; + if ('/' !== $url[strlen($url) - 1]) { $url .= '/'; } @@ -41,9 +74,61 @@ class RetailcrmApiClientV5 $url = $url . 'api/' . self::VERSION; $this->client = new RetailcrmHttpClient($url, array('apiKey' => $apiKey)); + $this->apiKey = $apiKey; + $this->unversionedClient = new RetailcrmHttpClient($unversionedUrl, array('apiKey' => $apiKey)); $this->siteCode = $site; } + /** + * getSingleSiteForKey + * + * @return string|bool + */ + public function getSingleSiteForKey() + { + $response = $this->credentials(); + + if ($response instanceof RetailcrmApiResponse + && isset($response['sitesAvailable']) + && is_array($response['sitesAvailable']) + && !empty($response['sitesAvailable']) + ) { + return $response['sitesAvailable'][0]; + } + + return false; + } + + /** + * /api/credentials response + * + * @return RetailcrmApiResponse|bool + */ + public function credentials() + { + $response = $this->unversionedClient->makeRequest( + '/credentials', + RetailcrmHttpClient::METHOD_GET + ); + + if ($response instanceof RetailcrmApiResponse) { + return $response; + } + + return false; + } + + /** + * @return RetailcrmApiResponse + * @throws CurlException + * @throws InvalidArgumentException + * @throws InvalidJsonException + */ + public function apiVersions() + { + return $this->unversionedClient->makeRequest('/api-versions', RetailcrmHttpClient::METHOD_GET); + } + /** * Returns users list * @@ -55,7 +140,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \InvalidArgumentException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function usersList(array $filter = array(), $page = null, $limit = null) { @@ -87,7 +172,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \InvalidArgumentException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function usersGet($id) { @@ -100,7 +185,7 @@ class RetailcrmApiClientV5 * @param integer $id user ID * @param string $status user status * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function usersStatus($id, $status) { @@ -126,7 +211,7 @@ class RetailcrmApiClientV5 * @param null $limit * @param null $page * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function segmentsList(array $filter = array(), $limit = null, $page = null) { @@ -156,7 +241,7 @@ class RetailcrmApiClientV5 * @param null $limit * @param null $page * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function customFieldsList(array $filter = array(), $limit = null, $page = null) { @@ -185,7 +270,7 @@ class RetailcrmApiClientV5 * @param $entity * @param $customField * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function customFieldsCreate($entity, $customField) { @@ -218,7 +303,7 @@ class RetailcrmApiClientV5 * @param $entity * @param $customField * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function customFieldsEdit($entity, $customField) { @@ -247,7 +332,7 @@ class RetailcrmApiClientV5 * @param $entity * @param $code * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function customFieldsGet($entity, $code) { @@ -276,11 +361,11 @@ class RetailcrmApiClientV5 * @param null $limit * @param null $page * - * @return ApiResponse + * @return RetailcrmApiResponse */ - public function customDictionariesList(array $filter = [], $limit = null, $page = null) + public function customDictionariesList(array $filter = array(), $limit = null, $page = null) { - $parameters = []; + $parameters = array(); if (count($filter)) { $parameters['filter'] = $filter; @@ -304,7 +389,7 @@ class RetailcrmApiClientV5 * * @param $customDictionary * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function customDictionariesCreate($customDictionary) { @@ -329,7 +414,7 @@ class RetailcrmApiClientV5 * * @param $customDictionary * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function customDictionariesEdit($customDictionary) { @@ -354,7 +439,7 @@ class RetailcrmApiClientV5 * * @param $code * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function customDictionariesGet($code) { @@ -381,7 +466,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function ordersList(array $filter = array(), $page = null, $limit = null) { @@ -414,7 +499,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function ordersCreate(array $order, $site = null) { @@ -440,7 +525,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function ordersFixExternalIds(array $ids) { @@ -468,7 +553,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function ordersStatuses(array $ids = array(), array $externalIds = array()) { @@ -498,7 +583,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function ordersUpload(array $orders, $site = null) { @@ -526,7 +611,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function ordersGet($id, $by = 'externalId', $site = null) { @@ -550,7 +635,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function ordersEdit(array $order, $by = 'externalId', $site = null) { @@ -584,7 +669,7 @@ class RetailcrmApiClientV5 * @param null $page * @param null $limit * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function ordersHistory(array $filter = array(), $page = null, $limit = null) { @@ -614,7 +699,7 @@ class RetailcrmApiClientV5 * @param array $order * @param array $resultOrder * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function ordersCombine($order, $resultOrder, $technique = 'ours') { @@ -652,7 +737,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function ordersPaymentCreate(array $payment) { @@ -676,7 +761,7 @@ class RetailcrmApiClientV5 * @param string $by by key * @param null $site site code * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function ordersPaymentEdit(array $payment, $by = 'externalId', $site = null) { @@ -709,7 +794,7 @@ class RetailcrmApiClientV5 * * @param string $id payment id * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function ordersPaymentDelete($id) { @@ -736,7 +821,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function customersList(array $filter = array(), $page = null, $limit = null) { @@ -769,7 +854,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function customersCreate(array $customer, $site = null) { @@ -795,7 +880,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function customersFixExternalIds(array $ids) { @@ -822,7 +907,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function customersUpload(array $customers, $site = null) { @@ -850,7 +935,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function customersGet($id, $by = 'externalId', $site = null) { @@ -874,7 +959,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function customersEdit(array $customer, $by = 'externalId', $site = null) { @@ -908,7 +993,7 @@ class RetailcrmApiClientV5 * @param null $page * @param null $limit * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function customersHistory(array $filter = array(), $page = null, $limit = null) { @@ -937,7 +1022,7 @@ class RetailcrmApiClientV5 * @param array $customers * @param array $resultCustomer * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function customersCombine(array $customers, $resultCustomer) { @@ -969,7 +1054,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function customersNotesList(array $filter = array(), $page = null, $limit = null) { @@ -1000,7 +1085,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function customersNotesCreate($note, $site = null) { @@ -1025,7 +1110,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function customersNotesDelete($id) { @@ -1040,6 +1125,604 @@ class RetailcrmApiClientV5 ); } + /** + * Returns filtered corporate customers list + * + * @param array $filter (default: array()) + * @param int $page (default: null) + * @param int $limit (default: null) + * + * @throws \InvalidArgumentException + * @throws \RetailCrm\Exception\CurlException + * @throws \RetailCrm\Exception\InvalidJsonException + * + * @return RetailcrmApiResponse + */ + public function customersCorporateList(array $filter = array(), $page = null, $limit = null) + { + $parameters = array(); + + if (isset($filter['contactIds'])) { + $parameters['contactIds'] = $filter['contactIds']; + unset($filter['contactIds']); + } + + if (count($filter)) { + $parameters['filter'] = $filter; + } + if (null !== $page) { + $parameters['page'] = (int) $page; + } + if (null !== $limit) { + $parameters['limit'] = (int) $limit; + } + /* @noinspection PhpUndefinedMethodInspection */ + return $this->client->makeRequest( + '/customers-corporate', + "GET", + $parameters + ); + } + /** + * Create a corporate customer + * + * @param array $customerCorporate corporate customer data + * @param string $site (default: null) + * + * @throws \InvalidArgumentException + * @throws \RetailCrm\Exception\CurlException + * @throws \RetailCrm\Exception\InvalidJsonException + * + * @return RetailcrmApiResponse + */ + public function customersCorporateCreate(array $customerCorporate, $site = null) + { + if (! count($customerCorporate)) { + throw new \InvalidArgumentException( + 'Parameter `customerCorporate` must contains a data' + ); + } + /* @noinspection PhpUndefinedMethodInspection */ + return $this->client->makeRequest( + '/customers-corporate/create', + "POST", + $this->fillSite($site, array('customerCorporate' => json_encode($customerCorporate))) + ); + } + /** + * Save corporate customer IDs' (id and externalId) association in the CRM + * + * @param array $ids ids mapping + * + * @throws \InvalidArgumentException + * @throws \RetailCrm\Exception\CurlException + * @throws \RetailCrm\Exception\InvalidJsonException + * + * @return RetailcrmApiResponse + */ + public function customersCorporateFixExternalIds(array $ids) + { + if (! count($ids)) { + throw new \InvalidArgumentException( + 'Method parameter must contains at least one IDs pair' + ); + } + /* @noinspection PhpUndefinedMethodInspection */ + return $this->client->makeRequest( + '/customers-corporate/fix-external-ids', + "POST", + array('customersCorporate' => json_encode($ids)) + ); + } + /** + * Get corporate customers history + * @param array $filter + * @param null $page + * @param null $limit + * + * @return RetailcrmApiResponse + */ + public function customersCorporateHistory(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; + } + + /* @noinspection PhpUndefinedMethodInspection */ + return $this->client->makeRequest( + '/customers-corporate/history', + "GET", + $parameters + ); + } + /** + * Returns filtered corporate customers notes list + * + * @param array $filter (default: array()) + * @param int $page (default: null) + * @param int $limit (default: null) + * + * @throws \InvalidArgumentException + * @throws \RetailCrm\Exception\CurlException + * @throws \RetailCrm\Exception\InvalidJsonException + * + * @return RetailcrmApiResponse + */ + public function customersCorporateNotesList(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; + } + /* @noinspection PhpUndefinedMethodInspection */ + return $this->client->makeRequest( + '/customers-corporate/notes', + "GET", + $parameters + ); + } + /** + * Create corporate customer note + * + * @param array $note (default: array()) + * @param string $site (default: null) + * + * @throws \InvalidArgumentException + * @throws \RetailCrm\Exception\CurlException + * @throws \RetailCrm\Exception\InvalidJsonException + * + * @return RetailcrmApiResponse + */ + public function customersCorporateNotesCreate($note, $site = null) + { + if (empty($note['customer']['id']) && empty($note['customer']['externalId'])) { + throw new \InvalidArgumentException( + 'Customer identifier must be set' + ); + } + /* @noinspection PhpUndefinedMethodInspection */ + return $this->client->makeRequest( + '/customers-corporate/notes/create', + "POST", + $this->fillSite($site, array('note' => json_encode($note))) + ); + } + /** + * Delete corporate customer note + * + * @param integer $id + * + * @throws \InvalidArgumentException + * @throws \RetailCrm\Exception\CurlException + * @throws \RetailCrm\Exception\InvalidJsonException + * + * @return RetailcrmApiResponse + */ + public function customersCorporateNotesDelete($id) + { + if (empty($id)) { + throw new \InvalidArgumentException( + 'Note id must be set' + ); + } + /* @noinspection PhpUndefinedMethodInspection */ + return $this->client->makeRequest( + "/customers-corporate/notes/$id/delete", + "POST" + ); + } + /** + * Upload array of the corporate customers + * + * @param array $customersCorporate array of corporate customers + * @param string $site (default: null) + * + * @return RetailcrmApiResponse + * @throws \RetailCrm\Exception\CurlException + * @throws \RetailCrm\Exception\InvalidJsonException + * + * @throws \InvalidArgumentException + */ + public function customersCorporateUpload(array $customersCorporate, $site = null) + { + if (!count($customersCorporate)) { + throw new \InvalidArgumentException( + 'Parameter `customersCorporate` must contains array of the corporate customers' + ); + } + /* @noinspection PhpUndefinedMethodInspection */ + return $this->client->makeRequest( + '/customers-corporate/upload', + "POST", + $this->fillSite($site, array('customersCorporate' => json_encode($customersCorporate))) + ); + } + /** + * Get corporate customer by id or externalId + * + * @param string $id corporate customer identifier + * @param string $by (default: 'externalId') + * @param string $site (default: null) + * + * @throws \InvalidArgumentException + * @throws \RetailCrm\Exception\CurlException + * @throws \RetailCrm\Exception\InvalidJsonException + * + * @return RetailcrmApiResponse + */ + public function customersCorporateGet($id, $by = 'externalId', $site = null) + { + $this->checkIdParameter($by); + /* @noinspection PhpUndefinedMethodInspection */ + return $this->client->makeRequest( + "/customers-corporate/$id", + "GET", + $this->fillSite($site, array('by' => $by)) + ); + } + /** + * Get corporate customer addresses by id or externalId + * + * @param string $id corporate customer identifier + * @param array $filter (default: array()) + * @param int $page (default: null) + * @param int $limit (default: null) + * @param string $by (default: 'externalId') + * @param string $site (default: null) + * + * @throws \InvalidArgumentException + * @throws \RetailCrm\Exception\CurlException + * @throws \RetailCrm\Exception\InvalidJsonException + * + * @return RetailcrmApiResponse + */ + public function customersCorporateAddresses( + $id, + array $filter = array(), + $page = null, + $limit = null, + $by = 'externalId', + $site = null + ) { + $this->checkIdParameter($by); + $parameters = array('by' => $by); + if (count($filter)) { + $parameters['filter'] = $filter; + } + if (null !== $page) { + $parameters['page'] = (int) $page; + } + if (null !== $limit) { + $parameters['limit'] = (int) $limit; + } + /* @noinspection PhpUndefinedMethodInspection */ + return $this->client->makeRequest( + "/customers-corporate/$id/addresses", + "GET", + $this->fillSite($site, $parameters) + ); + } + + /** + * Create corporate customer address + * + * @param string $id corporate customer identifier + * @param array $address (default: array()) + * @param string $by (default: 'externalId') + * @param string $site (default: null) + * + * @throws \InvalidArgumentException + * @throws \RetailCrm\Exception\CurlException + * @throws \RetailCrm\Exception\InvalidJsonException + * + * @return RetailcrmApiResponse + */ + public function customersCorporateAddressesCreate($id, array $address = array(), $by = 'externalId', $site = null) + { + /* @noinspection PhpUndefinedMethodInspection */ + return $this->client->makeRequest( + "/customers-corporate/$id/addresses/create", + "POST", + $this->fillSite($site, array('address' => json_encode($address), 'by' => $by)) + ); + } + + /** + * Edit corporate customer address + * + * @param string $customerId corporate customer identifier + * @param string $addressId corporate customer identifier + * @param array $address (default: array()) + * @param string $customerBy (default: 'externalId') + * @param string $addressBy (default: 'externalId') + * @param string $site (default: null) + * + * @throws \InvalidArgumentException + * @throws \RetailCrm\Exception\CurlException + * @throws \RetailCrm\Exception\InvalidJsonException + * + * @return RetailcrmApiResponse + */ + public function customersCorporateAddressesEdit( + $customerId, + $addressId, + array $address = array(), + $customerBy = 'externalId', + $addressBy = 'externalId', + $site = null + ) { + $addressFiltered = array_filter($address); + if ((count(array_keys($addressFiltered)) <= 1) + && (!isset($addressFiltered['text']) + || (isset($addressFiltered['text']) && empty($addressFiltered['text'])) + ) + ) { + throw new \InvalidArgumentException( + 'Parameter `address` must contain address text or all other address field' + ); + } + /* @noinspection PhpUndefinedMethodInspection */ + return $this->client->makeRequest( + "/customers-corporate/$customerId/addresses/$addressId/edit", + "POST", + $this->fillSite($site, array( + 'address' => json_encode($address), + 'by' => $customerBy, + 'entityBy' => $addressBy + )) + ); + } + /** + * Get corporate customer companies by id or externalId + * + * @param string $id corporate customer identifier + * @param array $filter (default: array()) + * @param int $page (default: null) + * @param int $limit (default: null) + * @param string $by (default: 'externalId') + * @param string $site (default: null) + * + * @throws \InvalidArgumentException + * @throws \RetailCrm\Exception\CurlException + * @throws \RetailCrm\Exception\InvalidJsonException + * + * @return RetailcrmApiResponse + */ + public function customersCorporateCompanies( + $id, + array $filter = array(), + $page = null, + $limit = null, + $by = 'externalId', + $site = null + ) { + $this->checkIdParameter($by); + $parameters = array('by' => $by); + if (count($filter)) { + $parameters['filter'] = $filter; + } + if (null !== $page) { + $parameters['page'] = (int) $page; + } + if (null !== $limit) { + $parameters['limit'] = (int) $limit; + } + /* @noinspection PhpUndefinedMethodInspection */ + return $this->client->makeRequest( + "/customers-corporate/$id/companies", + "GET", + $this->fillSite($site, $parameters) + ); + } + /** + * Create corporate customer company + * + * @param string $id corporate customer identifier + * @param array $company (default: array()) + * @param string $by (default: 'externalId') + * @param string $site (default: null) + * + * @throws \InvalidArgumentException + * @throws \RetailCrm\Exception\CurlException + * @throws \RetailCrm\Exception\InvalidJsonException + * + * @return RetailcrmApiResponse + */ + public function customersCorporateCompaniesCreate($id, array $company = array(), $by = 'externalId', $site = null) + { + /* @noinspection PhpUndefinedMethodInspection */ + return $this->client->makeRequest( + "/customers-corporate/$id/companies/create", + "POST", + $this->fillSite($site, array('company' => json_encode($company), 'by' => $by)) + ); + } + /** + * Edit corporate customer company + * + * @param string $customerId corporate customer identifier + * @param string $companyId corporate customer identifier + * @param array $company (default: array()) + * @param string $customerBy (default: 'externalId') + * @param string $companyBy (default: 'externalId') + * @param string $site (default: null) + * + * @return RetailcrmApiResponse + * @throws \InvalidArgumentException + * @throws \RetailCrm\Exception\CurlException + * @throws \RetailCrm\Exception\InvalidJsonException + * + */ + public function customersCorporateCompaniesEdit( + $customerId, + $companyId, + array $company = array(), + $customerBy = 'externalId', + $companyBy = 'externalId', + $site = null + ) { + /* @noinspection PhpUndefinedMethodInspection */ + return $this->client->makeRequest( + "/customers-corporate/$customerId/companies/$companyId/edit", + "POST", + $this->fillSite($site, array( + 'company' => json_encode($company), + 'by' => $customerBy, + 'entityBy' => $companyBy + )) + ); + } + /** + * Get corporate customer contacts by id or externalId + * + * @param string $id corporate customer identifier + * @param array $filter (default: array()) + * @param int $page (default: null) + * @param int $limit (default: null) + * @param string $by (default: 'externalId') + * @param string $site (default: null) + * + * @throws \InvalidArgumentException + * @throws \RetailCrm\Exception\CurlException + * @throws \RetailCrm\Exception\InvalidJsonException + * + * @return RetailcrmApiResponse + */ + public function customersCorporateContacts( + $id, + array $filter = array(), + $page = null, + $limit = null, + $by = 'externalId', + $site = null + ) { + $this->checkIdParameter($by); + $parameters = array('by' => $by); + if (count($filter)) { + $parameters['filter'] = $filter; + } + if (null !== $page) { + $parameters['page'] = (int) $page; + } + if (null !== $limit) { + $parameters['limit'] = (int) $limit; + } + /* @noinspection PhpUndefinedMethodInspection */ + return $this->client->makeRequest( + "/customers-corporate/$id/contacts", + "GET", + $this->fillSite($site, $parameters) + ); + } + /** + * Create corporate customer contact + * + * @param string $id corporate customer identifier + * @param array $contact (default: array()) + * @param string $by (default: 'externalId') + * @param string $site (default: null) + * + * @return RetailcrmApiResponse + * @throws \RetailCrm\Exception\CurlException + * @throws \RetailCrm\Exception\InvalidJsonException + * + * @throws \InvalidArgumentException + */ + public function customersCorporateContactsCreate($id, array $contact = array(), $by = 'externalId', $site = null) + { + /* @noinspection PhpUndefinedMethodInspection */ + return $this->client->makeRequest( + "/customers-corporate/$id/contacts/create", + "POST", + $this->fillSite($site, array('contact' => json_encode($contact), 'by' => $by)) + ); + } + /** + * Edit corporate customer contact + * + * @param string $customerId corporate customer identifier + * @param string $contactId corporate customer identifier + * @param array $contact (default: array()) + * @param string $customerBy (default: 'externalId') + * @param string $contactBy (default: 'externalId') + * @param string $site (default: null) + * + * @return RetailcrmApiResponse + * @throws \InvalidArgumentException + * @throws \RetailCrm\Exception\CurlException + * @throws \RetailCrm\Exception\InvalidJsonException + * + */ + public function customersCorporateContactsEdit( + $customerId, + $contactId, + array $contact = array(), + $customerBy = 'externalId', + $contactBy = 'externalId', + $site = null + ) { + /* @noinspection PhpUndefinedMethodInspection */ + return $this->client->makeRequest( + "/customers-corporate/$customerId/contacts/$contactId/edit", + "POST", + $this->fillSite($site, array( + 'contact' => json_encode($contact), + 'by' => $customerBy, + 'entityBy' => $contactBy + )) + ); + } + /** + * Edit a corporate customer + * + * @param array $customerCorporate corporate customer data + * @param string $by (default: 'externalId') + * @param string $site (default: null) + * + * @throws \InvalidArgumentException + * @throws \RetailCrm\Exception\CurlException + * @throws \RetailCrm\Exception\InvalidJsonException + * + * @return RetailcrmApiResponse + */ + public function customersCorporateEdit(array $customerCorporate, $by = 'externalId', $site = null) + { + if (!count($customerCorporate)) { + throw new \InvalidArgumentException( + 'Parameter `customerCorporate` must contains a data' + ); + } + $this->checkIdParameter($by); + if (!array_key_exists($by, $customerCorporate)) { + throw new \InvalidArgumentException( + sprintf('Corporate customer array must contain the "%s" parameter.', $by) + ); + } + /* @noinspection PhpUndefinedMethodInspection */ + return $this->client->makeRequest( + sprintf('/customers-corporate/%s/edit', $customerCorporate[$by]), + "POST", + $this->fillSite( + $site, + array('customerCorporate' => json_encode($customerCorporate), 'by' => $by) + ) + ); + } + /** * Get orders assembly list * @@ -1051,7 +1734,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function ordersPacksList(array $filter = array(), $page = null, $limit = null) { @@ -1084,7 +1767,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function ordersPacksCreate(array $pack, $site = null) { @@ -1112,7 +1795,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function ordersPacksHistory(array $filter = array(), $page = null, $limit = null) { @@ -1144,7 +1827,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function ordersPacksGet($id) { @@ -1167,7 +1850,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function ordersPacksDelete($id) { @@ -1191,7 +1874,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function ordersPacksEdit(array $pack, $site = null) { @@ -1215,7 +1898,7 @@ class RetailcrmApiClientV5 * @param null $limit * @param null $page * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function tasksList(array $filter = array(), $limit = null, $page = null) { @@ -1244,7 +1927,7 @@ class RetailcrmApiClientV5 * @param array $task * @param null $site * - * @return ApiResponse + * @return RetailcrmApiResponse * */ public function tasksCreate($task, $site = null) @@ -1271,7 +1954,7 @@ class RetailcrmApiClientV5 * @param array $task * @param null $site * - * @return ApiResponse + * @return RetailcrmApiResponse * */ public function tasksEdit($task, $site = null) @@ -1297,7 +1980,7 @@ class RetailcrmApiClientV5 * * @param $id * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function tasksGet($id) { @@ -1324,7 +2007,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function storeProductsGroups(array $filter = array(), $page = null, $limit = null) { @@ -1348,7 +2031,7 @@ class RetailcrmApiClientV5 } /** - * Get purchace prices & stock balance + * Get purchase prices & stock balance * * @param array $filter (default: array()) * @param int $page (default: null) @@ -1358,7 +2041,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function storeInventories(array $filter = array(), $page = null, $limit = null) { @@ -1386,12 +2069,12 @@ class RetailcrmApiClientV5 * * @param string $code get settings code * - * @return ApiResponse + * @return RetailcrmApiResponse * @throws \RetailCrm\Exception\InvalidJsonException * @throws \RetailCrm\Exception\CurlException * @throws \InvalidArgumentException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function storeSettingsGet($code) { @@ -1414,7 +2097,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \InvalidArgumentException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function storeSettingsEdit(array $configuration) { @@ -1441,7 +2124,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function storeInventoriesUpload(array $offers, $site = null) { @@ -1469,7 +2152,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function storeProducts(array $filter = array(), $page = null, $limit = null) { @@ -1501,7 +2184,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function deliverySettingsGet($code) { @@ -1524,7 +2207,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \InvalidArgumentException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function deliverySettingsEdit(array $configuration) { @@ -1551,7 +2234,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \InvalidArgumentException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function deliveryTracking($code, array $statusUpdate) { @@ -1579,7 +2262,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function countriesList() { @@ -1596,7 +2279,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function deliveryServicesList() { @@ -1615,7 +2298,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function deliveryServicesEdit(array $data) { @@ -1639,7 +2322,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function deliveryTypesList() { @@ -1658,7 +2341,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function deliveryTypesEdit(array $data) { @@ -1682,7 +2365,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function orderMethodsList() { @@ -1701,7 +2384,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function orderMethodsEdit(array $data) { @@ -1725,7 +2408,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function orderTypesList() { @@ -1744,7 +2427,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function orderTypesEdit(array $data) { @@ -1768,7 +2451,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function paymentStatusesList() { @@ -1787,7 +2470,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function paymentStatusesEdit(array $data) { @@ -1811,7 +2494,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function paymentTypesList() { @@ -1830,7 +2513,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function paymentTypesEdit(array $data) { @@ -1854,7 +2537,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function productStatusesList() { @@ -1873,7 +2556,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function productStatusesEdit(array $data) { @@ -1897,7 +2580,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function sitesList() { @@ -1916,7 +2599,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function sitesEdit(array $data) { @@ -1940,7 +2623,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function statusGroupsList() { @@ -1957,7 +2640,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function statusesList() { @@ -1976,7 +2659,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function statusesEdit(array $data) { @@ -2000,7 +2683,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function storesList() { @@ -2019,7 +2702,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function storesEdit(array $data) { @@ -2051,7 +2734,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \InvalidArgumentException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function telephonySettingsGet($code) { @@ -2083,7 +2766,7 @@ class RetailcrmApiClientV5 * @param bool $hangupEventSupported * @param bool $changeUserStatusUrl * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function telephonySettingsEdit( $code, @@ -2180,7 +2863,7 @@ class RetailcrmApiClientV5 * @param string $externalPhone * @param array $webAnalyticsData * - * @return ApiResponse + * @return RetailcrmApiResponse * @internal param string $code additional phone code * @internal param string $status call status * @@ -2230,7 +2913,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function telephonyCallsUpload(array $calls) { @@ -2257,7 +2940,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function telephonyCallManager($phone, $details) { @@ -2282,7 +2965,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \RetailCrm\Exception\InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function statisticUpdate() { @@ -2301,7 +2984,7 @@ class RetailcrmApiClientV5 * @throws \RetailCrm\Exception\CurlException * @throws \InvalidArgumentException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function integrationModulesEdit(array $configuration) { diff --git a/retailcrm/lib/api/RetailcrmApiErrors.php b/retailcrm/lib/api/RetailcrmApiErrors.php new file mode 100644 index 0000000..fc8500e --- /dev/null +++ b/retailcrm/lib/api/RetailcrmApiErrors.php @@ -0,0 +1,108 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +class RetailcrmApiErrors +{ + /** @var array */ + private static $errors; + + /** @var integer */ + private static $statusCode; + + /** + * ApiErrors constructor. Isn't accessible. + */ + private function __construct() + { + return false; + } + + /** + * Returns status code + * + * @return int + */ + public static function getStatusCode() + { + return isset(static::$statusCode) ? static::$statusCode : 0; + } + + /** + * Returns static::$errors array, or regenerates it. + * + * @return array + */ + public static function getErrors() + { + static::checkArray(); + return static::$errors; + } + + /** + * Sets static::$errors array, or regenerates it. + * Returns true if errors is assigned. + * Returns false if incorrect data was passed to it. + * + * @param array $errors + * @param integer $statusCode + * + * @return bool + */ + public static function set($errors, $statusCode) + { + static::checkArray(); + + if (is_array($errors) && is_integer($statusCode)) { + static::$errors = $errors; + static::$statusCode = $statusCode; + + return true; + } + + return false; + } + + /** + * Regenerates static::$errors array + */ + private static function checkArray() + { + if (!is_array(static::$errors)) { + static::$errors = array(); + } + } +} diff --git a/retailcrm/lib/api/RetailcrmApiPaginatedRequest.php b/retailcrm/lib/api/RetailcrmApiPaginatedRequest.php new file mode 100644 index 0000000..777717c --- /dev/null +++ b/retailcrm/lib/api/RetailcrmApiPaginatedRequest.php @@ -0,0 +1,218 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +class RetailcrmApiPaginatedRequest +{ + /** + * @var \RetailcrmProxy|\RetailcrmApiClientV5 + */ + private $api; + + /** + * @var string + */ + private $method; + + /** + * @var array + */ + private $params; + + /** + * @var string + */ + private $dataKey; + + /** + * @var int + */ + private $limit; + + /** + * @var array + */ + private $data; + + /** + * RetailcrmApiPaginatedRequest constructor. + */ + public function __construct() + { + $this->reset(); + } + + /** + * Sets retailCRM api client to request + * + * @param \RetailcrmApiClientV5|\RetailcrmProxy $api + * + * @return RetailcrmApiPaginatedRequest + */ + public function setApi($api) + { + $this->api = $api; + return $this; + } + + /** + * Sets API client method to request + * + * @param string $method + * + * @return RetailcrmApiPaginatedRequest + */ + public function setMethod($method) + { + $this->method = $method; + return $this; + } + + /** + * Sets method params for API client (leave `{{page}}` instead of page and `{{limit}}` instead of limit) + * + * @param array $params + * + * @return RetailcrmApiPaginatedRequest + */ + public function setParams($params) + { + $this->params = $params; + return $this; + } + + /** + * Sets dataKey (key with data in response) + * + * @param string $dataKey + * + * @return RetailcrmApiPaginatedRequest + */ + public function setDataKey($dataKey) + { + $this->dataKey = $dataKey; + return $this; + } + + /** + * Sets record limit per request + * + * @param int $limit + * + * @return RetailcrmApiPaginatedRequest + */ + public function setLimit($limit) + { + $this->limit = $limit; + return $this; + } + + /** + * Executes request + * + * @return $this + */ + public function execute() + { + $this->data = array(); + $response = true; + $page = 1; + + do { + $response = call_user_func_array( + array($this->api, $this->method), + $this->buildParams($this->params, $page) + ); + + if ($response instanceof RetailcrmApiResponse && $response->offsetExists($this->dataKey)) { + $this->data = array_merge($response[$this->dataKey]); + $page = $response['pagination']['currentPage'] + 1; + } + + time_nanosleep(0, 300000000); + } while ($response && (isset($response['pagination']) + && $response['pagination']['currentPage'] < $response['pagination']['totalPageCount'])); + + return $this; + } + + /** + * Returns data + * + * @return array + */ + public function getData() + { + return $this->data; + } + + /** + * Reset paginated request + * + * @return $this + */ + public function reset() + { + $this->method = ''; + $this->limit = 100; + $this->data = array(); + + return $this; + } + + /** + * buildParams + * + * @param array $placeholderParams + * @param int $currentPage + * + * @return mixed + */ + private function buildParams($placeholderParams, $currentPage) + { + foreach ($placeholderParams as $key => $param) { + if ($param == '{{page}}') { + $placeholderParams[$key] = $currentPage; + } + + if ($param == '{{limit}}') { + $placeholderParams[$key] = $this->limit; + } + } + + return $placeholderParams; + } +} diff --git a/retailcrm/lib/RetailcrmApiResponse.php b/retailcrm/lib/api/RetailcrmApiResponse.php old mode 100755 new mode 100644 similarity index 64% rename from retailcrm/lib/RetailcrmApiResponse.php rename to retailcrm/lib/api/RetailcrmApiResponse.php index 67a7517..a84452c --- a/retailcrm/lib/RetailcrmApiResponse.php +++ b/retailcrm/lib/api/RetailcrmApiResponse.php @@ -1,14 +1,39 @@ - * @license https://opensource.org/licenses/MIT MIT License - * @link http://www.retailcrm.ru/docs/Developers/ApiVersion4 + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to http://www.prestashop.com for more information. + * + * @author DIGITAL RETAIL TECHNOLOGIES SL + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. */ class RetailcrmApiResponse implements \ArrayAccess { @@ -18,6 +43,9 @@ class RetailcrmApiResponse implements \ArrayAccess // response assoc array protected $response; + // raw response + protected $rawResponse; + /** * ApiResponse constructor. * @@ -29,6 +57,7 @@ class RetailcrmApiResponse implements \ArrayAccess public function __construct($statusCode, $responseBody = null) { $this->statusCode = (int) $statusCode; + $this->rawResponse = $responseBody; if (!empty($responseBody)) { $response = json_decode($responseBody, true); @@ -44,6 +73,14 @@ class RetailcrmApiResponse implements \ArrayAccess } } + /** + * @return mixed|null + */ + public function getRawResponse() + { + return $this->rawResponse; + } + /** * Return HTTP response status code * diff --git a/retailcrm/lib/RetailcrmHttpClient.php b/retailcrm/lib/api/RetailcrmHttpClient.php old mode 100755 new mode 100644 similarity index 57% rename from retailcrm/lib/RetailcrmHttpClient.php rename to retailcrm/lib/api/RetailcrmHttpClient.php index 6ec0e5c..3357b2c --- a/retailcrm/lib/RetailcrmHttpClient.php +++ b/retailcrm/lib/api/RetailcrmHttpClient.php @@ -1,14 +1,39 @@ - * @license https://opensource.org/licenses/MIT MIT License - * @link http://www.retailcrm.ru/docs/Developers/ApiVersion4 + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to http://www.prestashop.com for more information. + * + * @author DIGITAL RETAIL TECHNOLOGIES SL + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. */ class RetailcrmHttpClient { @@ -38,6 +63,16 @@ class RetailcrmHttpClient $this->defaultParameters = $defaultParameters; } + /** + * Returns current url + * + * @return string + */ + public function getUrl() + { + return $this->url; + } + /** * Make HTTP request * @@ -51,7 +86,7 @@ class RetailcrmHttpClient * @throws CurlException * @throws InvalidJsonException * - * @return ApiResponse + * @return RetailcrmApiResponse */ public function makeRequest( $path, @@ -104,6 +139,16 @@ class RetailcrmHttpClient throw new CurlException($error, $errno); } + RetailcrmLogger::writeDebug( + sprintf( + '%s `%s`, status: %d', + $method, + $url, + (int) $statusCode + ), + self::METHOD_POST == $method ? ' POST fields: `' . print_r($parameters, true) . '`' : '' + ); + return new RetailcrmApiResponse($statusCode, $responseBody); } } diff --git a/retailcrm/lib/api/RetailcrmProxy.php b/retailcrm/lib/api/RetailcrmProxy.php new file mode 100644 index 0000000..63deaa1 --- /dev/null +++ b/retailcrm/lib/api/RetailcrmProxy.php @@ -0,0 +1,111 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +class RetailcrmProxy +{ + private $api; + private $log; + + public function __construct($url, $key, $log) + { + $this->api = new RetailcrmApiClientV5($url, $key); + $this->log = $log; + } + + /** + * Reduces error array into string + * + * @param $errors + * + * @return false|string + */ + private static function reduceErrors($errors) + { + $reduced = ''; + + if (is_array($errors)) { + foreach ($errors as $key => $error) { + $reduced .= sprintf('%s => %s\n', $key, $error); + } + } + + return $reduced; + } + + public function __call($method, $arguments) + { + $date = date('Y-m-d H:i:s'); + try { + RetailcrmLogger::writeDebug($method, print_r($arguments, true)); + $response = call_user_func_array(array($this->api, $method), $arguments); + + if (!($response instanceof RetailcrmApiResponse)) { + RetailcrmLogger::writeDebug($method, $response); + return $response; + } + + if (!$response->isSuccessful()) { + RetailcrmLogger::writeCaller($method, $response->getErrorMsg()); + + if (isset($response['errors'])) { + RetailcrmApiErrors::set($response['errors'], $response->getStatusCode()); + $error = static::reduceErrors($response['errors']); + RetailcrmLogger::writeNoCaller($error); + } + + $response = false; + } else { + // Don't print long lists in debug logs (errors while calling this will be easy to detect anyway) + if (in_array($method, array('statusesList', 'paymentTypesList', 'deliveryTypesList'))) { + RetailcrmLogger::writeDebug($method, '[request was successful, but response is omitted]'); + } else { + RetailcrmLogger::writeDebug($method, $response->getRawResponse()); + } + } + + return $response; + } catch (CurlException $e) { + RetailcrmLogger::writeCaller(get_class($this->api).'::'.$method, $e->getMessage()); + RetailcrmLogger::writeNoCaller($e->getTraceAsString()); + return false; + } catch (InvalidJsonException $e) { + RetailcrmLogger::writeCaller(get_class($this->api).'::'.$method, $e->getMessage()); + RetailcrmLogger::writeNoCaller($e->getTraceAsString()); + return false; + } + } +} diff --git a/retailcrm/lib/api/index.php b/retailcrm/lib/api/index.php new file mode 100644 index 0000000..6a1c957 --- /dev/null +++ b/retailcrm/lib/api/index.php @@ -0,0 +1,8 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +require_once(dirname(__FILE__) . '/../RetailcrmPrestashopLoader.php'); + +class RetailcrmAbandonedCartsEvent implements RetailcrmEventInterface +{ + /** + * @inheritDoc + */ + public function execute() + { + $syncCartsActive = Configuration::get(RetailCRM::SYNC_CARTS_ACTIVE); + if (empty($syncCartsActive)) { + return; + } + + $api = RetailcrmTools::getApiClient(); + + if (empty($api)) { + RetailcrmLogger::writeCaller('abandonedCarts', 'set api key & url first'); + return; + } + + RetailcrmCartUploader::init(); + RetailcrmCartUploader::$api = $api; + RetailcrmCartUploader::$paymentTypes = array_keys(json_decode(Configuration::get(RetailCRM::PAYMENT), true)); + RetailcrmCartUploader::$syncStatus = Configuration::get(RetailCRM::SYNC_CARTS_STATUS); + RetailcrmCartUploader::setSyncDelay(Configuration::get(RetailCRM::SYNC_CARTS_DELAY)); + RetailcrmCartUploader::run(); + } +} + +$event = new RetailcrmAbandonedCartsEvent(); +$event->execute(); diff --git a/retailcrm/lib/events/RetailcrmEventInterface.php b/retailcrm/lib/events/RetailcrmEventInterface.php new file mode 100644 index 0000000..2cc82c0 --- /dev/null +++ b/retailcrm/lib/events/RetailcrmEventInterface.php @@ -0,0 +1,47 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +interface RetailcrmEventInterface +{ + /** + * Execute event + * + * @return bool + */ + public function execute(); +} diff --git a/retailcrm/lib/events/RetailcrmExportEvent.php b/retailcrm/lib/events/RetailcrmExportEvent.php new file mode 100644 index 0000000..afc47fd --- /dev/null +++ b/retailcrm/lib/events/RetailcrmExportEvent.php @@ -0,0 +1,108 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +require_once(dirname(__FILE__) . '/../RetailcrmPrestashopLoader.php'); + +class RetailcrmExportEvent implements RetailcrmEventInterface +{ + /** + * @inheritDoc + */ + public function execute() + { + $api = RetailcrmTools::getApiClient(); + + if (empty($api)) { + RetailcrmLogger::writeCaller('orderHistory', 'set api key & url first'); + exit(); + } + + $orders = array(); + $orderRecords = Order::getOrdersWithInformations(); + $orderBuilder = new RetailcrmOrderBuilder(); + $orderBuilder->defaultLangFromConfiguration()->setApi($api); + + foreach ($orderRecords as $record) { + $order = new Order($record['id_order']); + + $orderCart = new Cart($order->id_cart); + $orderCustomer = new Customer($order->id_customer); + + if (!empty($orderCustomer->id)) { + $orderBuilder->setCmsCustomer($orderCustomer); + } else { + //TODO + // Caused crash before because of empty RetailcrmOrderBuilder::cmsCustomer. + // Current version *shouldn't* do this, but I suggest more tests for guest customers. + $orderBuilder->setCmsCustomer(null); + } + + if (!empty($orderCart->id)) { + $orderBuilder->setCmsCart($orderCart); + } else { + $orderBuilder->setCmsCart(null); + } + + $orderBuilder->setCmsOrder($order); + + try { + $orders[] = $orderBuilder->buildOrderWithPreparedCustomer(); + } catch (\InvalidArgumentException $exception) { + RetailcrmLogger::writeCaller('export', $exception->getMessage()); + RetailcrmLogger::writeNoCaller($e->getTraceAsString()); + + if (PHP_SAPI == 'cli') { + echo $exception->getMessage() . PHP_EOL; + } + } + + time_nanosleep(0, 500000000); + } + + unset($orderRecords); + + $orders = array_chunk($orders, 50); + + foreach ($orders as $chunk) { + $api->ordersUpload($chunk); + } + } +} + +$event = new RetailcrmExportEvent(); +$event->execute(); diff --git a/retailcrm/lib/events/RetailcrmIcmlEvent.php b/retailcrm/lib/events/RetailcrmIcmlEvent.php new file mode 100644 index 0000000..9e3ee87 --- /dev/null +++ b/retailcrm/lib/events/RetailcrmIcmlEvent.php @@ -0,0 +1,57 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +require_once(dirname(__FILE__) . '/../RetailcrmPrestashopLoader.php'); + +class RetailcrmIcmlEvent implements RetailcrmEventInterface +{ + /** + * @inheritDoc + */ + public function execute() + { + $job = new RetailcrmCatalog(); + $data = $job->getData(); + + $icml = new RetailcrmIcml(Configuration::get('PS_SHOP_NAME'), _PS_ROOT_DIR_ . '/retailcrm.xml'); + $icml->generate($data[0], $data[1]); + } +} + +$event = new RetailcrmIcmlEvent(); +$event->execute(); diff --git a/retailcrm/lib/events/RetailcrmInventoriesEvent.php b/retailcrm/lib/events/RetailcrmInventoriesEvent.php new file mode 100644 index 0000000..df4d818 --- /dev/null +++ b/retailcrm/lib/events/RetailcrmInventoriesEvent.php @@ -0,0 +1,70 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +require_once(dirname(__FILE__) . '/../RetailcrmPrestashopLoader.php'); + +class RetailcrmInventoriesEvent implements RetailcrmEventInterface +{ + /** + * @inheritDoc + */ + public function execute() + { + $apiUrl = Configuration::get(RetailCRM::API_URL); + $apiKey = Configuration::get(RetailCRM::API_KEY); + + if (!empty($apiUrl) && !empty($apiKey)) { + RetailcrmInventories::$api = new RetailcrmProxy($apiUrl, $apiKey, _PS_ROOT_DIR_ . '/retailcrm.log'); + } else { + RetailcrmLogger::writeCaller('inventories', 'set api key & url first'); + exit(); + } + + RetailcrmInventories::loadStocks(); + } +} + +if (Configuration::get(RetailCRM::ENABLE_BALANCES_RECEIVING)) { + $event = new RetailcrmInventoriesEvent(); + $event->execute(); +} else { + RetailcrmLogger::writeDebug( + 'RetailcrmInventoriesEvent', + 'Balances receiving is not enabled, skipping...' + ); +} diff --git a/retailcrm/lib/events/RetailcrmMissingEvent.php b/retailcrm/lib/events/RetailcrmMissingEvent.php new file mode 100644 index 0000000..03b7305 --- /dev/null +++ b/retailcrm/lib/events/RetailcrmMissingEvent.php @@ -0,0 +1,202 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +require_once(dirname(__FILE__) . '/../RetailcrmPrestashopLoader.php'); + +class RetailcrmMissingEvent implements RetailcrmEventInterface +{ + /** + * @inheritDoc + */ + public function execute() + { + $shortopts = 'o:'; + $options = getopt($shortopts); + + if (!isset($options['o'])) { + echo ('Parameter -o is missing'); + exit(); + } + + $apiUrl = Configuration::get(RetailCRM::API_URL); + $apiKey = Configuration::get(RetailCRM::API_KEY); + + if (!empty($apiUrl) && !empty($apiKey)) { + $api = new RetailcrmProxy($apiUrl, $apiKey, _PS_ROOT_DIR_ . '/retailcrm.log'); + } else { + echo('Set api key & url first'); + exit(); + } + + $delivery = json_decode(Configuration::get(RetailCRM::DELIVERY), true); + $payment = json_decode(Configuration::get(RetailCRM::PAYMENT), true); + $status = json_decode(Configuration::get(RetailCRM::STATUS), true); + + $orderInstance = new Order($options['o']); + + $order = array( + 'externalId' => $orderInstance->id, + 'createdAt' => $orderInstance->date_add, + ); + + /** + * Add order customer info + * + */ + + if (!empty($orderInstance->id_customer)) { + $orderCustomer = new Customer($orderInstance->id_customer); + $customer = array( + 'externalId' => $orderCustomer->id, + 'firstName' => $orderCustomer->firstname, + 'lastname' => $orderCustomer->lastname, + 'email' => $orderCustomer->email, + 'createdAt' => $orderCustomer->date_add + ); + + $response = $api->customersEdit($customer); + + if ($response) { + $order['customer']['externalId'] = $orderCustomer->id; + $order['firstName'] = $orderCustomer->firstname; + $order['lastName'] = $orderCustomer->lastname; + $order['email'] = $orderCustomer->email; + } else { + exit(); + } + } + + + /** + * Add order status + * + */ + + if ($orderInstance->current_state == 0) { + $order['status'] = 'completed'; + } else { + $order['status'] = array_key_exists($orderInstance->current_state, $status) + ? $status[$orderInstance->current_state] + : 'completed' + ; + } + + /** + * Add order address data + * + */ + + $cart = new Cart($orderInstance->getCartIdStatic($orderInstance->id)); + $addressCollection = $cart->getAddressCollection(); + $address = array_shift($addressCollection); + + if ($address instanceof Address) { + $phone = is_null($address->phone) + ? is_null($address->phone_mobile) ? '' : $address->phone_mobile + : $address->phone + ; + + $postcode = $address->postcode; + $city = $address->city; + $addres_line = sprintf("%s %s", $address->address1, $address->address2); + } + + if (!empty($postcode)) { + $order['delivery']['address']['index'] = $postcode; + } + + if (!empty($city)) { + $order['delivery']['address']['city'] = $city; + } + + if (!empty($addres_line)) { + $order['delivery']['address']['text'] = $addres_line; + } + + if (!empty($phone)) { + $order['phone'] = $phone; + } + + /** + * Add payment & shippment data + */ + + if (Module::getInstanceByName('advancedcheckout') === false) { + $paymentType = $orderInstance->module; + } else { + $paymentType = $orderInstance->payment; + } + + if (array_key_exists($paymentType, $payment) && !empty($payment[$paymentType])) { + $order['paymentType'] = $payment[$paymentType]; + } + + if (array_key_exists($orderInstance->id_carrier, $delivery) && !empty($delivery[$orderInstance->id_carrier])) { + $order['delivery']['code'] = $delivery[$orderInstance->id_carrier]; + } + + if (isset($orderInstance->total_shipping_tax_incl) && (int) $orderInstance->total_shipping_tax_incl > 0) { + $order['delivery']['cost'] = round($orderInstance->total_shipping_tax_incl, 2); + } + + /** + * Add products + * + */ + + $products = $orderInstance->getProducts(); + + foreach ($products as $product) { + $item = array( + //'productId' => $product['product_id'], + 'offer' => array('externalId' => $product['product_id']), + 'productName' => $product['product_name'], + 'quantity' => $product['product_quantity'], + 'initialPrice' => round($product['product_price'], 2), + 'purchasePrice' => round($product['purchase_supplier_price'], 2) + ); + + $order['items'][] = $item; + } + + $api->ordersEdit($order); + } +} + +$event = new RetailcrmMissingEvent(); +$event->execute(); diff --git a/retailcrm/lib/events/RetailcrmSyncEvent.php b/retailcrm/lib/events/RetailcrmSyncEvent.php new file mode 100644 index 0000000..d2ca66f --- /dev/null +++ b/retailcrm/lib/events/RetailcrmSyncEvent.php @@ -0,0 +1,73 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +require_once(dirname(__FILE__) . '/../RetailcrmPrestashopLoader.php'); + +class RetailcrmSyncEvent implements RetailcrmEventInterface +{ + /** + * @inheritDoc + */ + public function execute() + { + $apiUrl = Configuration::get(RetailCRM::API_URL); + $apiKey = Configuration::get(RetailCRM::API_KEY); + RetailcrmHistory::$default_lang = (int) Configuration::get('PS_LANG_DEFAULT'); + + if (!empty($apiUrl) && !empty($apiKey)) { + RetailcrmHistory::$api = new RetailcrmProxy($apiUrl, $apiKey, _PS_ROOT_DIR_ . '/retailcrm.log'); + } else { + RetailcrmLogger::writeCaller('orderHistory', 'set api key & url first'); + exit(); + } + + RetailcrmHistory::customersHistory(); + RetailcrmHistory::ordersHistory(); + } +} + +if (Configuration::get(RetailCRM::ENABLE_HISTORY_UPLOADS)) { + $event = new RetailcrmSyncEvent(); + $event->execute(); +} else { + RetailcrmLogger::writeDebug( + 'RetailcrmSyncEvent', + 'History uploads is not enabled, skipping...' + ); +} + diff --git a/retailcrm/lib/events/index.php b/retailcrm/lib/events/index.php new file mode 100644 index 0000000..6a1c957 --- /dev/null +++ b/retailcrm/lib/events/index.php @@ -0,0 +1,8 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +class RetailcrmJobManagerException extends Exception +{ + /** + * @var string $job + */ + private $job; + + /** + * @var array $jobs + */ + private $jobs; + + /** + * RetailcrmJobManagerException constructor. + * + * @param string $message + * @param string $job + * @param array $jobs + * @param int $code + * @param \Exception|null $previous + * + */ + public function __construct($message = "", $job = "", $jobs = array(), $code = 0, \Exception $previous = null) + { + parent::__construct($message, $code, $previous); + + $this->job = $job; + $this->jobs = $jobs; + } + + /** + * @return string + */ + public function getJob() + { + return $this->job; + } + + /** + * @return array + */ + public function getJobs() + { + return $this->jobs; + } +} diff --git a/retailcrm/lib/exceptions/index.php b/retailcrm/lib/exceptions/index.php new file mode 100644 index 0000000..6a1c957 --- /dev/null +++ b/retailcrm/lib/exceptions/index.php @@ -0,0 +1,8 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +abstract class RetailcrmAbstractTemplate +{ + /** @var Module|\RetailCRM */ + protected $module; + protected $smarty; + protected $assets; + + /** @var string */ + protected $template; + /** @var array */ + protected $data; + + /** @var array */ + private $errors; + + /** @var array */ + private $warnings; + + /** @var array */ + private $informations; + + /** @var array */ + private $confirmations; + + /** @var Context $context */ + protected $context; + + /** + * RetailcrmAbstractTemplate constructor. + * + * @param Module $module + * @param $smarty + * @param $assets + */ + public function __construct(Module $module, $smarty, $assets) + { + $this->module = $module; + $this->smarty = $smarty; + $this->assets = $assets; + $this->errors = array(); + $this->warnings = array(); + $this->informations = array(); + $this->confirmations = array(); + } + + /** + * Returns ISO code of current employee language or default language. + * + * @return string + */ + protected function getCurrentLanguageISO() + { + $langId = 0; + + global $cookie; + + if (!empty($this->context) && !empty($this->context->employee)) { + $langId = (int) $this->context->employee->id_lang; + } elseif ($cookie instanceof Cookie) { + $langId = (int) $cookie->id_lang; + } else { + $langId = (int) Configuration::get('PS_LANG_DEFAULT'); + } + + return (string) Language::getIsoById($langId); + } + + /** + * @param $file + * + * @return mixed + * @throws RuntimeException + */ + public function render($file) + { + $this->buildParams(); + $this->setTemplate(); + + if (null === $this->template) { + throw new \RuntimeException("Template not be blank"); + } + + $this->smarty->assign(\array_merge($this->data, array( + 'moduleErrors' => $this->errors, + 'moduleWarnings' => $this->warnings, + 'moduleConfirmations' => $this->confirmations, + 'moduleInfos' => $this->informations, + ))); + + return $this->module->display($file, "views/templates/admin/$this->template"); + } + + /** + * @param $messages + * + * @return self + */ + public function setErrors($messages) + { + if (!empty($messages)) { + $this->errors = $messages; + } + + return $this; + } + + /** + * @param $messages + * + * @return self + */ + public function setWarnings($messages) + { + if (!empty($messages)) { + $this->warnings = $messages; + } + + return $this; + } + + /** + * @param $messages + * + * @return self + */ + public function setInformations($messages) + { + if (!empty($messages)) { + $this->informations = $messages; + } + + return $this; + } + + /** + * @param $messages + * + * @return self + */ + public function setConfirmations($messages) + { + if (!empty($messages)) { + $this->confirmations = $messages; + } + + return $this; + } + + /** + * @param $context + * + * @return self + */ + public function setContext($context) + { + if (!empty($context)) { + $this->context = $context; + } + + return $this; + } + + abstract protected function buildParams(); + abstract protected function setTemplate(); +} diff --git a/retailcrm/lib/templates/RetailcrmBaseTemplate.php b/retailcrm/lib/templates/RetailcrmBaseTemplate.php new file mode 100644 index 0000000..dd96eb4 --- /dev/null +++ b/retailcrm/lib/templates/RetailcrmBaseTemplate.php @@ -0,0 +1,77 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +class RetailcrmBaseTemplate extends RetailcrmAbstractTemplate +{ + protected function buildParams() + { + switch ($this->getCurrentLanguageISO()) { + case 'ru': + $promoVideoUrl = 'VEatkEGJfGw'; + $registerUrl = 'https://retailcrm.ru/signup?utm_source=prestashop&utm_medium=modul&utm_campaign=button-in-modul'; + $supportEmail = 'support@retailcrm.ru'; + break; + case 'es': + $promoVideoUrl = 'LdJFoqOkLj8'; + $registerUrl = 'https://retailcrm.es/signup?utm_source=prestashop&utm_medium=modul&utm_campaign=button-in-modul'; + $supportEmail = 'support@retailcrm.es'; + break; + default: + $promoVideoUrl = 'wLjtULfZvOw'; + $registerUrl = 'https://retailcrm.pro/signup?utm_source=prestashop&utm_medium=modul&utm_campaign=button-in-modul'; + $supportEmail = 'support@retailcrm.pro'; + break; + } + + $this->data = array( + 'assets' => $this->assets, + 'apiUrl' => RetailCRM::API_URL, + 'apiKey' => RetailCRM::API_KEY, + 'promoVideoUrl' => $promoVideoUrl, + 'registerUrl' => $registerUrl, + 'supportEmail' => $supportEmail + ); + } + + /** + * Set template data + */ + protected function setTemplate() + { + $this->template = "index.tpl"; + } +} diff --git a/retailcrm/lib/templates/RetailcrmSettingsTemplate.php b/retailcrm/lib/templates/RetailcrmSettingsTemplate.php new file mode 100644 index 0000000..fdc5893 --- /dev/null +++ b/retailcrm/lib/templates/RetailcrmSettingsTemplate.php @@ -0,0 +1,102 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +class RetailcrmSettingsTemplate extends RetailcrmAbstractTemplate +{ + protected $settings; + protected $settingsNames; + + /** + * RetailcrmSettingsTemplate constructor. + * + * @param \Module $module + * @param $smarty + * @param $assets + * @param $settings + * @param $settingsNames + */ + public function __construct(Module $module, $smarty, $assets, $settings, $settingsNames) + { + parent::__construct($module, $smarty, $assets); + + $this->settings = $settings; + $this->settingsNames = $settingsNames; + } + + /** + * Build params for template + * + * @return mixed + */ + protected function getParams() + { + $params = array(); + + if ($this->module->api) { + $params['statusesDefaultExport'] = $this->module->reference->getStatuseDefaultExport(); + $params['deliveryTypes'] = $this->module->reference->getDeliveryTypes(); + $params['orderStatuses'] = $this->module->reference->getStatuses(); + $params['paymentTypes'] = $this->module->reference->getPaymentTypes(); + $params['methodsForDefault'] = $this->module->reference->getPaymentAndDeliveryForDefault( + array($this->module->translate('Delivery method'), $this->module->translate('Payment type')) + ); + } + + return $params; + } + + protected function buildParams() + { + $this->data = array_merge( + array( + 'assets' => $this->assets, + 'cartsDelays' => $this->module->getSynchronizedCartsTimeSelect(), + ), + $this->getParams(), + $this->settingsNames, + $this->settings + ); + } + + /** + * Set template data + */ + protected function setTemplate() + { + $this->template = "settings.tpl"; + } +} diff --git a/retailcrm/lib/templates/RetailcrmTemplateFactory.php b/retailcrm/lib/templates/RetailcrmTemplateFactory.php new file mode 100644 index 0000000..3cba055 --- /dev/null +++ b/retailcrm/lib/templates/RetailcrmTemplateFactory.php @@ -0,0 +1,70 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +class RetailcrmTemplateFactory +{ + private $assets; + private $smarty; + + /** + * RetailcrmTemplateFactory constructor. + * + * @param $smarty + * @param $assets + */ + public function __construct($smarty, $assets) + { + $this->smarty = $smarty; + $this->assets = $assets; + } + + /** + * @param Module $module + * + * @return RetailcrmAbstractTemplate + */ + public function createTemplate(Module $module) + { + $settings = RetailCRM::getSettings(); + + if (empty($settings['url']) && empty($settings['apiKey'])) { + return new RetailcrmBaseTemplate($module, $this->smarty, $this->assets); + } else { + return new RetailcrmSettingsTemplate($module, $this->smarty, $this->assets, $settings, RetailCRM::getSettingsNames()); + } + } +} diff --git a/retailcrm/lib/templates/index.php b/retailcrm/lib/templates/index.php new file mode 100644 index 0000000..6a1c957 --- /dev/null +++ b/retailcrm/lib/templates/index.php @@ -0,0 +1,8 @@ +personalDiscount discountCardNumber + id + externalId + nickName + vip + bad + customFields + personalDiscount + discountCardNumber + manager + address + mainCustomerContact + companyInn + company + index country region diff --git a/retailcrm/public/css/.gitignore b/retailcrm/public/css/.gitignore deleted file mode 100644 index 6be0402..0000000 --- a/retailcrm/public/css/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.min.css \ No newline at end of file diff --git a/retailcrm/public/css/retailcrm-upload.css b/retailcrm/public/css/retailcrm-upload.css deleted file mode 100755 index d9f17a6..0000000 --- a/retailcrm/public/css/retailcrm-upload.css +++ /dev/null @@ -1,29 +0,0 @@ -#retailcrm-loading-fade { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - background: #000; - position: fixed; - left: 0; - right: 0; - bottom: 0; - top: 0; - z-index: 9999; - opacity: .5; - filter: alpha(opacity=50); -} - -#retailcrm-loader { - width: 50px; - height: 50px; - border: 10px solid white; - animation: retailcrm-loader 2s linear infinite; - border-top: 10px solid #0c0c0c; - border-radius: 50%; -} - -@keyframes retailcrm-loader { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } -} \ No newline at end of file diff --git a/retailcrm/public/js/.gitignore b/retailcrm/public/js/.gitignore deleted file mode 100644 index 0baf809..0000000 --- a/retailcrm/public/js/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.min.js \ No newline at end of file diff --git a/retailcrm/public/js/exec-jobs.js b/retailcrm/public/js/exec-jobs.js deleted file mode 100755 index cd2fc8d..0000000 --- a/retailcrm/public/js/exec-jobs.js +++ /dev/null @@ -1,6 +0,0 @@ -(function () { - var xhr = new XMLHttpRequest(); - xhr.open('GET', '/modules/retailcrm/job/jobs.php', true); - xhr.timeout = 0; - xhr.send(null); -})(); \ No newline at end of file diff --git a/retailcrm/public/js/retailcrm-upload.js b/retailcrm/public/js/retailcrm-upload.js deleted file mode 100755 index 895fd34..0000000 --- a/retailcrm/public/js/retailcrm-upload.js +++ /dev/null @@ -1,66 +0,0 @@ -$(function(){ - function RetailcrmUploadForm() { - this.form = $('input[value="retailcrm_upload_form"]').parent().get(0); - - if (typeof this.form === 'undefined') { - return false; - } - - this.input = $(this.form).find('input[name="RETAILCRM_UPLOAD_ORDERS_ID"]').get(0); - this.submitButton = $(this.form).find('button[type="submit"]').get(0); - this.messageContainer = document.querySelector('#content > div.bootstrap + div.bootstrap'); - this.submitAction = this.submitAction.bind(this); - this.partitionId = this.partitionId.bind(this); - this.setLoading = this.setLoading.bind(this); - - $(this.submitButton).click(this.submitAction); - } - - RetailcrmUploadForm.prototype.submitAction = function (event) { - event.preventDefault(); - let ids = this.partitionId($(this.input).val().toString().replace(/\s+/g, '')); - - if (ids.length === 0) { - return false; - } - - this.setLoading(true); - $(this.form).submit(); - } - - RetailcrmUploadForm.prototype.setLoading = function (loading) { - let indicator = $('div#retailcrm-loading-fade'); - - if (indicator.length === 0) { - $('body').append(` -
-
-
- `.trim()); - } - - indicator.css('visibility', (loading ? 'visible' : 'hidden')); - } - - RetailcrmUploadForm.prototype.partitionId = function (idList) { - if (idList === '') { - return []; - } - - let itemsList = idList.split(','); - let ids = itemsList.filter(item => item.toString().indexOf('-') === -1); - let ranges = itemsList.filter(item => item.toString().indexOf('-') !== -1); - let resultRanges = []; - - ranges.forEach(item => { - let rangeData = item.split('-'); - - resultRanges = [...resultRanges, ...[...Array(+rangeData[1] + 1) - .keys()].slice(+rangeData[0], +rangeData[1] + 1)]; - }); - - return [...ids, ...resultRanges].map(item => +item).sort((a, b) => a - b); - } - - new RetailcrmUploadForm(); -}); \ No newline at end of file diff --git a/retailcrm/retailcrm.php b/retailcrm/retailcrm.php old mode 100755 new mode 100644 index 12f260e..9877329 --- a/retailcrm/retailcrm.php +++ b/retailcrm/retailcrm.php @@ -1,11 +1,39 @@ + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. */ if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) { @@ -20,18 +48,67 @@ require_once(dirname(__FILE__) . '/bootstrap.php'); class RetailCRM extends Module { + const API_URL = 'RETAILCRM_ADDRESS'; + const API_KEY = 'RETAILCRM_API_TOKEN'; + const DELIVERY = 'RETAILCRM_API_DELIVERY'; + const STATUS = 'RETAILCRM_API_STATUS'; + const PAYMENT = 'RETAILCRM_API_PAYMENT'; + const DELIVERY_DEFAULT = 'RETAILCRM_API_DELIVERY_DEFAULT'; + const PAYMENT_DEFAULT = 'RETAILCRM_API_PAYMENT_DEFAULT'; + const STATUS_EXPORT = 'RETAILCRM_STATUS_EXPORT'; + const CLIENT_ID = 'RETAILCRM_CLIENT_ID'; + const COLLECTOR_ACTIVE = 'RETAILCRM_DAEMON_COLLECTOR_ACTIVE'; + const COLLECTOR_KEY = 'RETAILCRM_DAEMON_COLLECTOR_KEY'; + const SYNC_CARTS_ACTIVE = 'RETAILCRM_API_SYNCHRONIZE_CARTS'; + const SYNC_CARTS_STATUS = 'RETAILCRM_API_SYNCHRONIZED_CART_STATUS'; + const SYNC_CARTS_DELAY = 'RETAILCRM_API_SYNCHRONIZED_CART_DELAY'; + const UPLOAD_ORDERS = 'RETAILCRM_UPLOAD_ORDERS_ID'; + const MODULE_LIST_CACHE_CHECKSUM = 'RETAILCRM_MODULE_LIST_CACHE_CHECKSUM'; + const ENABLE_CORPORATE_CLIENTS = 'RETAILCRM_ENABLE_CORPORATE_CLIENTS'; + const ENABLE_HISTORY_UPLOADS = 'RETAILCRM_ENABLE_HISTORY_UPLOADS'; + const ENABLE_BALANCES_RECEIVING = 'RETAILCRM_ENABLE_BALANCES_RECEIVING'; + + const LATEST_API_VERSION = '5'; + const CONSULTANT_SCRIPT = 'RETAILCRM_CONSULTANT_SCRIPT'; + const CONSULTANT_RCCT = 'RETAILCRM_CONSULTANT_RCCT'; + + /** + * @var array $templateErrors + */ + private $templateErrors; + + /** + * @var array $templateWarnings + */ + private $templateWarnings; + + /** + * @var array $templateConfirms + */ + private $templateConfirms; + + /** + * @var array $templateInfos + */ + private $templateInfos; + + /** @var bool|\RetailcrmApiClientV5 */ public $api = false; public $default_lang; public $default_currency; public $default_country; public $apiUrl; public $apiKey; - public $apiVersion; public $psVersion; public $log; public $confirmUninstall; + + /** + * @var \RetailcrmReferences + */ public $reference; public $assetsBase; + private static $moduleListCache; private $use_new_hooks = true; @@ -39,27 +116,26 @@ class RetailCRM extends Module { $this->name = 'retailcrm'; $this->tab = 'export'; - $this->version = '2.5.1'; - $this->author = 'Retail Driver LCC'; - $this->displayName = $this->l('RetailCRM'); - $this->description = $this->l('Integration module for RetailCRM'); + $this->version = '3.0.2'; + $this->author = 'DIGITAL RETAIL TECHNOLOGIES SL'; + $this->displayName = $this->l('retailCRM'); + $this->description = $this->l('Integration module for retailCRM'); $this->confirmUninstall = $this->l('Are you sure you want to uninstall?'); - $this->default_lang = (int)Configuration::get('PS_LANG_DEFAULT'); - $this->default_currency = (int)Configuration::get('PS_CURRENCY_DEFAULT'); - $this->default_country = (int)Configuration::get('PS_COUNTRY_DEFAULT'); - $this->apiUrl = Configuration::get('RETAILCRM_ADDRESS'); - $this->apiKey = Configuration::get('RETAILCRM_API_TOKEN'); - $this->apiVersion = Configuration::get('RETAILCRM_API_VERSION'); - $this->ps_versions_compliancy = array('min' => '1.5', 'max' => _PS_VERSION_); + $this->default_lang = (int) Configuration::get('PS_LANG_DEFAULT'); + $this->default_currency = (int) Configuration::get('PS_CURRENCY_DEFAULT'); + $this->default_country = (int) Configuration::get('PS_COUNTRY_DEFAULT'); + $this->apiUrl = Configuration::get(static::API_URL); + $this->apiKey = Configuration::get(static::API_KEY); + $this->ps_versions_compliancy = array('min' => '1.6.1.0', 'max' => _PS_VERSION_); $this->psVersion = Tools::substr(_PS_VERSION_, 0, 3); - $this->log = _PS_ROOT_DIR_ . '/retailcrm.log'; - $this->module_key = '149c765c6cddcf35e1f13ea6c71e9fa5'; + $this->log = static::getErrorLog(); + $this->module_key = 'dff3095326546f5fe8995d9e86288491'; $this->assetsBase = Tools::getShopDomainSsl(true, true) . __PS_BASE_URI__ . 'modules/' . $this->name . - '/public'; + '/views'; if ($this->psVersion == '1.6') { $this->bootstrap = true; @@ -67,7 +143,7 @@ class RetailCRM extends Module } if ($this->apiUrl && $this->apiKey) { - $this->api = new RetailcrmProxy($this->apiUrl, $this->apiKey, $this->log, $this->apiVersion); + $this->api = new RetailcrmProxy($this->apiUrl, $this->apiKey, $this->log); $this->reference = new RetailcrmReferences($this->api); } @@ -83,139 +159,115 @@ class RetailCRM extends Module $this->registerHook('actionPaymentConfirmation') && $this->registerHook('actionCustomerAccountAdd') && $this->registerHook('actionOrderEdited') && + $this->registerHook('actionCarrierUpdate') && $this->registerHook('header') && ($this->use_new_hooks ? $this->registerHook('actionCustomerAccountUpdate') : true) && - ($this->use_new_hooks ? $this->registerHook('actionValidateCustomerAddressForm') : true) + ($this->use_new_hooks ? $this->registerHook('actionValidateCustomerAddressForm') : true) && + $this->installDB() ); } public function hookHeader() { - $this->context->controller->addJS($this->assetsBase . '/js/exec-jobs.js'); - - if (Configuration::get('RETAILCRM_DAEMON_COLLECTOR_ACTIVE') - && Configuration::get('RETAILCRM_DAEMON_COLLECTOR_KEY') - ) { - $collector = new RetailcrmDaemonCollector( - $this->context->customer, - Configuration::get('RETAILCRM_DAEMON_COLLECTOR_KEY') - ); - - return $collector->buildScript()->getJs(); + if (!empty($this->context) && !empty($this->context->controller)) { + $this->context->controller->addJS($this->assetsBase . '/js/retailcrm-compat.min.js'); + $this->context->controller->addJS($this->assetsBase . '/js/retailcrm-jobs.min.js'); + $this->context->controller->addJS($this->assetsBase . '/js/retailcrm-collector.min.js'); + $this->context->controller->addJS($this->assetsBase . '/js/retailcrm-consultant.min.js'); } } public function uninstall() { - $api = new RetailcrmProxy( - Configuration::get('RETAILCRM_ADDRESS'), - Configuration::get('RETAILCRM_API_TOKEN'), - _PS_ROOT_DIR_ . '/retailcrm.log', - Configuration::get('RETAILCRM_API_VERSION') - ); + $apiUrl = Configuration::get(static::API_URL); + $apiKey = Configuration::get(static::API_KEY); - $clientId = Configuration::get('RETAILCRM_CLIENT_ID'); - $this->integrationModule($api, $clientId, Configuration::get('RETAILCRM_API_VERSION'), false); + if (!empty($apiUrl) && !empty($apiKey)) { + $api = new RetailcrmProxy( + $apiUrl, + $apiKey, + static::getErrorLog() + ); + + $clientId = Configuration::get(static::CLIENT_ID); + $this->integrationModule($api, $clientId, false); + } return parent::uninstall() && - Configuration::deleteByName('RETAILCRM_ADDRESS') && - Configuration::deleteByName('RETAILCRM_API_TOKEN') && - Configuration::deleteByName('RETAILCRM_API_STATUS') && - Configuration::deleteByName('RETAILCRM_API_DELIVERY') && + Configuration::deleteByName(static::API_URL) && + Configuration::deleteByName(static::API_KEY) && + Configuration::deleteByName(static::DELIVERY) && + Configuration::deleteByName(static::STATUS) && + Configuration::deleteByName(static::PAYMENT) && + Configuration::deleteByName(static::DELIVERY_DEFAULT) && + Configuration::deleteByName(static::PAYMENT_DEFAULT) && + Configuration::deleteByName(static::STATUS_EXPORT) && + Configuration::deleteByName(static::CLIENT_ID) && + Configuration::deleteByName(static::COLLECTOR_ACTIVE) && + Configuration::deleteByName(static::COLLECTOR_KEY) && + Configuration::deleteByName(static::SYNC_CARTS_ACTIVE) && + Configuration::deleteByName(static::SYNC_CARTS_STATUS) && + Configuration::deleteByName(static::SYNC_CARTS_DELAY) && + Configuration::deleteByName(static::UPLOAD_ORDERS) && + Configuration::deleteByName(static::MODULE_LIST_CACHE_CHECKSUM) && + Configuration::deleteByName(static::ENABLE_CORPORATE_CLIENTS) && + Configuration::deleteByName(static::ENABLE_HISTORY_UPLOADS) && + Configuration::deleteByName(static::ENABLE_BALANCES_RECEIVING) && Configuration::deleteByName('RETAILCRM_LAST_SYNC') && - Configuration::deleteByName('RETAILCRM_API_VERSION') && + Configuration::deleteByName('RETAILCRM_LAST_ORDERS_SYNC') && Configuration::deleteByName('RETAILCRM_LAST_CUSTOMERS_SYNC') && - Configuration::deleteByName('RETAILCRM_API_SYNCHRONIZE_CARTS') && - Configuration::deleteByName('RETAILCRM_API_SYNCHRONIZED_CART_STATUS') && - Configuration::deleteByName('RETAILCRM_API_SYNCHRONIZED_CART_DELAY') && - Configuration::deleteByName('RETAILCRM_LAST_ORDERS_SYNC'); + $this->uninstallDB(); + } + + public function installDB() + { + return Db::getInstance()->execute( + 'CREATE TABLE IF NOT EXISTS `'._DB_PREFIX_.'retailcrm_abandonedcarts` ( + `id_cart` INT UNSIGNED UNIQUE NOT NULL, + `last_uploaded` DATETIME, + FOREIGN KEY (id_cart) REFERENCES '._DB_PREFIX_.'cart (id_cart) + ON DELETE CASCADE + ON UPDATE CASCADE + ) DEFAULT CHARSET=utf8;' + ); + } + + public function uninstallDB() + { + return Db::getInstance()->execute('DROP TABLE IF EXISTS `'._DB_PREFIX_.'retailcrm_abandonedcarts`;'); } public function getContent() { $output = null; - $address = Configuration::get('RETAILCRM_ADDRESS'); - $token = Configuration::get('RETAILCRM_API_TOKEN'); - $version = Configuration::get('RETAILCRM_API_VERSION'); + $address = Configuration::get(static::API_URL); + $token = Configuration::get(static::API_KEY); if (Tools::isSubmit('submit' . $this->name)) { - $ordersIds = (string)(Tools::getValue('RETAILCRM_UPLOAD_ORDERS_ID')); + $ordersIds = (string)(Tools::getValue(static::UPLOAD_ORDERS)); if (!empty($ordersIds)) { - $output .= $this->uploadOrders(static::partitionId($ordersIds)); + $output .= $this->uploadOrders(RetailcrmTools::partitionId($ordersIds)); } else { - $address = (string)(Tools::getValue('RETAILCRM_ADDRESS')); - $token = (string)(Tools::getValue('RETAILCRM_API_TOKEN')); - $version = (string)(Tools::getValue('RETAILCRM_API_VERSION')); - $delivery = json_encode(Tools::getValue('RETAILCRM_API_DELIVERY')); - $status = json_encode(Tools::getValue('RETAILCRM_API_STATUS')); - $payment = json_encode(Tools::getValue('RETAILCRM_API_PAYMENT')); - $deliveryDefault = json_encode(Tools::getValue('RETAILCRM_API_DELIVERY_DEFAULT')); - $paymentDefault = json_encode(Tools::getValue('RETAILCRM_API_PAYMENT_DEFAULT')); - $statusExport = (string)(Tools::getValue('RETAILCRM_STATUS_EXPORT')); - $collectorActive = (Tools::getValue('RETAILCRM_DAEMON_COLLECTOR_ACTIVE_1')); - $collectorKey = (string)(Tools::getValue('RETAILCRM_DAEMON_COLLECTOR_KEY')); - $clientId = Configuration::get('RETAILCRM_CLIENT_ID'); - $synchronizeCartsActive = (Tools::getValue('RETAILCRM_API_SYNCHRONIZE_CARTS_1')); - $synchronizedCartStatus = (string)(Tools::getValue('RETAILCRM_API_SYNCHRONIZED_CART_STATUS')); - $synchronizedCartDelay = (string)(Tools::getValue('RETAILCRM_API_SYNCHRONIZED_CART_DELAY')); - - $settings = array( - 'address' => $address, - 'token' => $token, - 'version' => $version, - 'clientId' => $clientId, - 'status' => $status, - 'statusExport' => $statusExport, - 'synchronizeCartStatus' => $synchronizedCartStatus - ); - - $output .= $this->validateForm($settings, $output); - - if ($output === '') { - Configuration::updateValue('RETAILCRM_ADDRESS', $address); - Configuration::updateValue('RETAILCRM_API_TOKEN', $token); - Configuration::updateValue('RETAILCRM_API_VERSION', $version); - Configuration::updateValue('RETAILCRM_API_DELIVERY', $delivery); - Configuration::updateValue('RETAILCRM_API_STATUS', $status); - Configuration::updateValue('RETAILCRM_API_PAYMENT', $payment); - Configuration::updateValue('RETAILCRM_API_DELIVERY_DEFAULT', $deliveryDefault); - Configuration::updateValue('RETAILCRM_API_PAYMENT_DEFAULT', $paymentDefault); - Configuration::updateValue('RETAILCRM_STATUS_EXPORT', $statusExport); - Configuration::updateValue('RETAILCRM_DAEMON_COLLECTOR_ACTIVE', $collectorActive); - Configuration::updateValue('RETAILCRM_DAEMON_COLLECTOR_KEY', $collectorKey); - Configuration::updateValue('RETAILCRM_API_SYNCHRONIZE_CARTS', $synchronizeCartsActive); - Configuration::updateValue('RETAILCRM_API_SYNCHRONIZED_CART_STATUS', $synchronizedCartStatus); - Configuration::updateValue('RETAILCRM_API_SYNCHRONIZED_CART_DELAY', $synchronizedCartDelay); - - $output .= $this->displayConfirmation($this->l('Settings updated')); - } - - if ($version == 5 && $this->isRegisteredInHook('actionPaymentCCAdd') == 0) { - $this->registerHook('actionPaymentCCAdd'); - } elseif ($version == 4 && $this->isRegisteredInHook('actionPaymentCCAdd') == 1) { - $hook_id = Hook::getIdByName('actionPaymentCCAdd'); - $this->unregisterHook($hook_id); - } + $output .= $this->saveSettings(); } } if ($address && $token) { - $this->api = new RetailcrmProxy($address, $token, $this->log, $version); + $this->api = new RetailcrmProxy($address, $token, $this->log); $this->reference = new RetailcrmReferences($this->api); } - $output .= $this->displayConfirmation( - $this->l('Timezone settings must be identical to both of your crm and shop') . - "$address/admin/settings#t-main" - ); + $templateFactory = new RetailcrmTemplateFactory($this->context->smarty, $this->assetsBase); - $this->context->controller->addCSS($this->assetsBase . '/css/retailcrm-upload.css'); - $this->context->controller->addJS($this->assetsBase . '/js/retailcrm-upload.js'); - $this->context->controller->addJS($this->assetsBase . '/js/exec-jobs.js'); - $this->display(__FILE__, 'retailcrm.tpl'); - - return $output . $this->displaySettingsForm() . $this->displayUploadOrdersForm(); + return $templateFactory + ->createTemplate($this) + ->setContext($this->context) + ->setErrors($this->getErrorMessages()) + ->setWarnings($this->getWarningMessage()) + ->setInformations($this->getInformationMessages()) + ->setConfirmations($this->getConfirmationMessages()) + ->render(__FILE__); } public function uploadOrders($orderIds) @@ -228,53 +280,58 @@ class RetailCRM extends Module return $this->displayConfirmation($this->l("At least one order ID should be specified")); } - $apiUrl = Configuration::get('RETAILCRM_ADDRESS'); - $apiKey = Configuration::get('RETAILCRM_API_TOKEN'); - $apiVersion = Configuration::get('RETAILCRM_API_VERSION'); + $apiUrl = Configuration::get(static::API_URL); + $apiKey = Configuration::get(static::API_KEY); + $isSuccessful = true; if (!empty($apiUrl) && !empty($apiKey)) { if (!($this->api instanceof RetailcrmProxy)) { - $this->api = new RetailcrmProxy($apiUrl, $apiKey, _PS_ROOT_DIR_ . '/retailcrm.log', $apiVersion); + $this->api = new RetailcrmProxy($apiUrl, $apiKey, _PS_ROOT_DIR_ . '/retailcrm.log'); } } else { return $this->displayError($this->l("Can't upload orders - set API key and API URL first!")); } - $orders = array(); - $customers = array(); - $isSuccessful = true; + $result = ''; foreach ($orderIds as $orderId) { $object = new Order($orderId); $customer = new Customer($object->id_customer); + $apiResponse = $this->api->ordersGet($object->id); + $existingOrder = (!empty($apiResponse) && isset($apiResponse['order'])) ? $apiResponse['order'] : array(); - array_push($customers, static::buildCrmCustomer($customer)); - array_push($orders, static::buildCrmOrder($object, $customer, null, true)); - } + try { + $orderBuilder = new RetailcrmOrderBuilder(); + $crmOrder = $orderBuilder + ->defaultLangFromConfiguration() + ->setApi($this->api) + ->setCmsOrder($object) + ->setCmsCustomer($customer) + ->buildOrderWithPreparedCustomer(); + } catch (\InvalidArgumentException $exception) { + $result .= $this->displayError($exception->getMessage()); + RetailcrmLogger::writeCaller(__METHOD__, $exception->getTraceAsString()); + } + + if (!empty($crmOrder)) { + $response = false; + + if (empty($existingOrder)) { + $response = $this->api->ordersCreate($crmOrder); + } else { + $response = $this->api->ordersEdit($crmOrder); + } + + $isSuccessful = $isSuccessful ? (is_bool($response) ? $response : $response->isSuccessful()) : false; - foreach ($customers as $item) { - if ($this->api->customersGet($item['externalId']) === false) { - $this->api->customersCreate($item); time_nanosleep(0, 50000000); } } - foreach ($orders as $item) { - if ($this->api->ordersGet($item['externalId']) === false) { - $response = $this->api->ordersCreate($item); - } else { - $response = $this->api->ordersEdit($item); - } - - $isSuccessful = is_bool($response) ? $response : $response->isSuccessful(); - - time_nanosleep(0, 50000000); - } - if ($isSuccessful) { return $this->displayConfirmation($this->l('All orders were uploaded successfully')); } else { - $result = $this->displayWarning($this->l('Not all orders were uploaded successfully')); + $result .= $this->displayWarning($this->l('Not all orders were uploaded successfully')); foreach (RetailcrmApiErrors::getErrors() as $error) { $result .= $this->displayError($error); @@ -284,742 +341,11 @@ class RetailCRM extends Module } } - /** - * Returns 'true' if provided date string is valid - * - * @param $date - * @param string $format - * - * @return bool - */ - public static function verifyDate($date, $format = "Y-m-d") - { - return $date !== "0000-00-00" && (bool)date_create_from_format($format, $date); - } - - /** - * Build array with order data for retailCRM from PrestaShop cart data - * - * @param Cart $cart Cart with data - * @param string $externalId External ID for order - * @param string $paymentType Payment type (buildCrmOrder requires it) - * @param string $status Status for order - * - * @return array - */ - public static function buildCrmOrderFromCart(Cart $cart = null, $externalId = '', $paymentType = '', $status = '') - { - if (empty($cart) || empty($paymentType) || empty($status)) { - return array(); - } - - $order = new Order(); - $order->id_cart = $cart->id; - $order->id_customer = $cart->id_customer; - $order->total_discounts = 0; - $order->module = $paymentType; - $order->payment = $paymentType; - $orderData = static::buildCrmOrder( - $order, - new Customer($cart->id_customer), - $cart, - false, - true, - true - ); - $orderData['externalId'] = $externalId; - $orderData['status'] = $status; - - unset($orderData['payments']); - - return $orderData; - } - - /** - * Build array with order data for retailCRM from PrestaShop order data - * - * @param Order $order PrestaShop Order - * @param Customer $customer PrestaShop Customer - * @param Cart $orderCart Cart for provided order. Optional - * @param bool $isStatusExport Use status for export - * @param bool $preferCustomerAddress Use customer address even if delivery address is provided - * @param bool $dataFromCart Prefer data from cart - * - * @return array retailCRM order data - */ - public static function buildCrmOrder( - Order $order, - Customer $customer = null, - Cart $orderCart = null, - $isStatusExport = false, - $preferCustomerAddress = false, - $dataFromCart = false - ) { - $apiVersion = Configuration::get('RETAILCRM_API_VERSION'); - $statusExport = Configuration::get('RETAILCRM_STATUS_EXPORT'); - $delivery = json_decode(Configuration::get('RETAILCRM_API_DELIVERY'), true); - $payment = json_decode(Configuration::get('RETAILCRM_API_PAYMENT'), true); - $status = json_decode(Configuration::get('RETAILCRM_API_STATUS'), true); - - if (Module::getInstanceByName('advancedcheckout') === false) { - $paymentType = $order->module; - } else { - $paymentType = $order->payment; - } - - if ($order->current_state == 0) { - $order_status = $statusExport; - - if (!$isStatusExport) { - $order_status = - array_key_exists($order->current_state, $status) - ? $status[$order->current_state] : 'new'; - } - } else { - $order_status = array_key_exists($order->current_state, $status) - ? $status[$order->current_state] - : $statusExport - ; - } - - $phone = ''; - $cart = $orderCart; - - if (is_null($cart)) { - $cart = new Cart($order->getCartIdStatic($order->id)); - } - - if (is_null($customer)) { - $customer = new Customer($order->id_customer); - } - - $crmOrder = array_filter(array( - 'externalId' => $order->id, - 'createdAt' => static::verifyDate($order->date_add, 'Y-m-d H:i:s') - ? $order->date_add : date('Y-m-d H:i:s'), - 'status' => $order_status, - 'firstName' => $customer->firstname, - 'lastName' => $customer->lastname, - 'email' => $customer->email, - )); - - $addressCollection = $cart->getAddressCollection(); - $address = new Address($order->id_address_delivery); - - if (is_null($address->id) || $preferCustomerAddress === true) { - $address = array_filter( - $addressCollection, - function ($v) use ($customer) { - return $v->id_customer == $customer->id; - } - ); - - if (is_array($address) && count($address) == 1) { - $address = reset($address); - } - } - - $address = static::addressParse($address); - $crmOrder = array_merge($crmOrder, $address['order']); - - if ($phone) { - $crmOrder['phone'] = $phone; - } - - if ($apiVersion != 5) { - if (array_key_exists($paymentType, $payment) && !empty($payment[$paymentType])) { - $crmOrder['paymentType'] = $payment[$paymentType]; - } - - $crmOrder['discount'] = round($order->total_discounts, 2); - } else { - $order_payment = array( - 'externalId' => $order->id .'#'. $order->reference, - 'amount' => round($order->total_paid, 2), - 'type' => $payment[$paymentType] ? $payment[$paymentType] : '' - ); - - $crmOrder['discountManualAmount'] = round($order->total_discounts, 2); - } - - if (isset($order_payment)) { - $crmOrder['payments'][] = $order_payment; - } else { - $crmOrder['payments'] = array(); - } - - $idCarrier = $dataFromCart ? $cart->id_carrier : $order->id_carrier; - - if (empty($idCarrier)) { - $idCarrier = $order->id_carrier; - $totalShipping = $order->total_shipping; - $totalShippingWithoutTax = $order->total_shipping_tax_excl; - } else { - $totalShipping = $dataFromCart ? $cart->getCarrierCost($idCarrier) : $order->total_shipping; - - if (!empty($totalShipping) && $totalShipping != 0) { - $totalShippingWithoutTax = $dataFromCart - ? $totalShipping - $cart->getCarrierCost($idCarrier, false) - : $order->total_shipping_tax_excl; - } else { - $totalShippingWithoutTax = $order->total_shipping_tax_excl; - } - } - - if (array_key_exists($idCarrier, $delivery) && !empty($delivery[$idCarrier])) { - $crmOrder['delivery']['code'] = $delivery[$idCarrier]; - } - - if (isset($totalShipping) && ((int) $totalShipping) > 0) { - $crmOrder['delivery']['cost'] = round($totalShipping, 2); - } - - if (isset($totalShippingWithoutTax) && $totalShippingWithoutTax > 0) { - $crmOrder['delivery']['netCost'] = round($totalShippingWithoutTax, 2); - } - - $comment = $order->getFirstMessage(); - - if ($comment !== false) { - $crmOrder['customerComment'] = $comment; - } - - if ($dataFromCart) { - $productStore = $cart; - $converter = function ($product) { - $product['product_attribute_id'] = $product['id_product_attribute']; - $product['product_quantity'] = $product['cart_quantity']; - $product['product_id'] = $product['id_product']; - $product['id_order_detail'] = $product['id_product']; - $product['product_name'] = $product['name']; - $product['product_price'] = $product['price']; - $product['purchase_supplier_price'] = $product['price']; - $product['product_price_wt'] = $product['price_wt']; - - return $product; - }; - } else { - $productStore = $order; - $converter = function ($product) { - return $product; - }; - } - - foreach ($productStore->getProducts() as $productData) { - $product = $converter($productData); - - if (isset($product['product_attribute_id']) && $product['product_attribute_id'] > 0) { - $productId = $product['product_id'] . '#' . $product['product_attribute_id']; - } else { - $productId = $product['product_id']; - } - - if (isset($product['attributes']) && $product['attributes']) { - $arProp = array(); - $count = 0; - $arAttr = explode(",", $product['attributes']); - - foreach ($arAttr as $valAttr) { - $arItem = explode(":", $valAttr); - - if ($arItem[0] && $arItem[1]) { - $arProp[$count]['name'] = trim($arItem[0]); - $arProp[$count]['value'] = trim($arItem[1]); - } - - $count++; - } - } - - $item = array( - "externalIds" => array( - array( - 'code' =>'prestashop', - 'value' => $productId."_".$product['id_order_detail'], - ), - ), - 'offer' => array('externalId' => $productId), - 'productName' => $product['product_name'], - 'quantity' => $product['product_quantity'], - 'initialPrice' => round($product['product_price'], 2), - /*'initialPrice' => !empty($item['rate']) - ? $item['price'] + ($item['price'] * $item['rate'] / 100) - : $item['price'],*/ - 'purchasePrice' => round($product['purchase_supplier_price'], 2) - ); - - if (true == Configuration::get('PS_TAX')) { - $item['initialPrice'] = round($product['product_price_wt'], 2); - } - - if (isset($arProp)) { - $item['properties'] = $arProp; - } - - $crmOrder['items'][] = $item; - } - - if ($order->id_customer) { - $crmOrder['customer']['externalId'] = $order->id_customer; - } - - return $crmOrder; - } - - /** - * Builds retailCRM customer data from PrestaShop customer data - * - * @param Customer $object - * @param array $address - * - * @return array - */ - public static function buildCrmCustomer(Customer $object, $address = array()) - { - return array_merge( - array( - 'externalId' => $object->id, - 'firstName' => $object->firstname, - 'lastName' => $object->lastname, - 'email' => $object->email, - 'createdAt' => self::verifyDate($object->date_add, 'Y-m-d H:i:s') - ? $object->date_add : date('Y-m-d H:i:s'), - 'birthday' => self::verifyDate($object->birthday, 'Y-m-d') - ? $object->birthday : date('Y-m-d', 0) - ), - $address - ); - } - - /** - * Split a string to id - * - * @param string $ids string with id - * - * @return array|string - */ - public static function partitionId($ids) - { - $ids = explode(',', $ids); - - $ranges = []; - - foreach ($ids as $idx => $uid) { - if (strpos($uid, '-')) { - $range = explode('-', $uid); - $ranges = array_merge($ranges, range($range[0], $range[1])); - unset($ids[$idx]); - } - } - - $ids = implode(',', array_merge($ids, $ranges)); - $ids = explode(',', $ids); - - return $ids; - } - - public function displaySettingsForm() - { - $this->displayConfirmation($this->l('Settings updated')); - - $default_lang = $this->default_lang; - $apiVersions = array( - array( - 'option_id' => '4', - 'name' => 'v4' - ), - array( - 'option_id' => '5', - 'name' => 'v5' - ) - ); - - $fields_form = array(); - - /* - * Network connection form - */ - $fields_form[]['form'] = array( - 'legend' => array( - 'title' => $this->l('Network connection'), - ), - 'input' => array( - array( - 'type' => 'select', - 'name' => 'RETAILCRM_API_VERSION', - 'label' => $this->l('API version'), - 'options' => array( - 'query' => $apiVersions, - 'id' => 'option_id', - 'name' => 'name' - ) - ), - array( - 'type' => 'text', - 'label' => $this->l('CRM address'), - 'name' => 'RETAILCRM_ADDRESS', - 'size' => 20, - 'required' => true - ), - array( - 'type' => 'text', - 'label' => $this->l('CRM token'), - 'name' => 'RETAILCRM_API_TOKEN', - 'size' => 20, - 'required' => true - ) - ), - 'submit' => array( - 'title' => $this->l('Save'), - 'class' => 'button' - ) - ); - - /* - * Daemon Collector - */ - $fields_form[]['form'] = array( - 'legend' => array('title' => $this->l('Daemon Collector')), - 'input' => array( - array( - 'type' => 'checkbox', - 'label' => $this->l('Activate'), - 'name' => 'RETAILCRM_DAEMON_COLLECTOR_ACTIVE', - 'values' => array( - 'query' => array( - array( - 'id_option' => 1, - ) - ), - 'id' => 'id_option', - 'name' => 'name' - ) - ), - array( - 'type' => 'text', - 'label' => $this->l('Site key'), - 'name' => 'RETAILCRM_DAEMON_COLLECTOR_KEY', - 'size' => 20, - 'required' => false - ) - ) - ); - - if ($this->api) { - /* - * Synchronize carts form - */ - $fields_form[]['form'] = array( - 'legend' => array( - 'title' => $this->l('Synchronization of buyer carts'), - ), - 'input' => array( - array( - 'type' => 'checkbox', - 'label' => $this->l('Create orders for abandoned carts of buyers'), - 'name' => 'RETAILCRM_API_SYNCHRONIZE_CARTS', - 'values' => array( - 'query' => array( - array( - 'id_option' => 1, - ) - ), - 'id' => 'id_option', - 'name' => 'name' - ) - ), - array( - 'type' => 'select', - 'name' => 'RETAILCRM_API_SYNCHRONIZED_CART_STATUS', - 'label' => $this->l('Order status for abandoned carts of buyers'), - 'options' => array( - 'query' => $this->reference->getStatuseDefaultExport(), - 'id' => 'id_option', - 'name' => 'name' - ) - ), - array( - 'type' => 'select', - 'name' => 'RETAILCRM_API_SYNCHRONIZED_CART_DELAY', - 'label' => $this->l('Upload abandoned carts'), - 'options' => array( - 'query' => array( - array( - 'id_option' => '0', - 'name' => $this->l('Immediately') - ), - array( - 'id_option' => '60', - 'name' => $this->l('After 1 minute') - ), - array( - 'id_option' => '300', - 'name' => $this->l('After 5 minutes') - ), - array( - 'id_option' => '600', - 'name' => $this->l('After 10 minutes') - ), - array( - 'id_option' => '900', - 'name' => $this->l('After 15 minutes') - ), - array( - 'id_option' => '1800', - 'name' => $this->l('After 30 minutes') - ), - array( - 'id_option' => '2700', - 'name' => $this->l('After 45 minute') - ), - array( - 'id_option' => '3600', - 'name' => $this->l('After 1 hour') - ), - ), - 'id' => 'id_option', - 'name' => 'name' - ) - ) - ) - ); - - /* - * Delivery - */ - $fields_form[]['form'] = array( - 'legend' => array('title' => $this->l('Delivery')), - 'input' => $this->reference->getDeliveryTypes(), - ); - - /* - * Order status - */ - $fields_form[]['form'] = array( - 'legend' => array('title' => $this->l('Order statuses')), - 'input' => $this->reference->getStatuses(), - ); - - /* - * Payment - */ - $fields_form[]['form'] = array( - 'legend' => array('title' => $this->l('Payment types')), - 'input' => $this->reference->getPaymentTypes(), - ); - - /* - * Default - */ - $fields_form[]['form'] = array( - 'legend' => array('title' => $this->l('Default')), - 'input' => $this->reference->getPaymentAndDeliveryForDefault( - array($this->l('Delivery method'), $this->l('Payment type')) - ), - ); - - /* - * Status in export - */ - $fields_form[]['form'] = array( - 'legend' => array('title' => $this->l('Default status')), - 'input' => array(array( - 'type' => 'select', - 'name' => 'RETAILCRM_STATUS_EXPORT', - 'label' => $this->l('Default status in export'), - 'options' => array( - 'query' => $this->reference->getStatuseDefaultExport(), - 'id' => 'id_option', - 'name' => 'name' - ) - )), - ); - } - - /* - * Display forms - */ - - $helper = new HelperForm(); - - $helper->module = $this; - $helper->name_controller = $this->name; - $helper->token = Tools::getAdminTokenLite('AdminModules'); - $helper->currentIndex = AdminController::$currentIndex . '&configure=' . $this->name; - - $helper->default_form_language = $default_lang; - $helper->allow_employee_form_lang = $default_lang; - - $helper->title = $this->displayName; - $helper->show_toolbar = true; - $helper->toolbar_scroll = true; - $helper->submit_action = 'submit' . $this->name; - $helper->toolbar_btn = array( - 'save' => - array( - 'desc' => $this->l('Save'), - 'href' => sprintf( - "%s&configure=%s&save%s&token=%s", - AdminController::$currentIndex, - $this->name, - $this->name, - Tools::getAdminTokenLite('AdminModules') - ) - ), - 'back' => array( - 'href' => AdminController::$currentIndex . '&token=' . Tools::getAdminTokenLite('AdminModules'), - 'desc' => $this->l('Back to list') - ) - ); - - $helper->fields_value['RETAILCRM_ADDRESS'] = Configuration::get('RETAILCRM_ADDRESS'); - $helper->fields_value['RETAILCRM_API_TOKEN'] = Configuration::get('RETAILCRM_API_TOKEN'); - $helper->fields_value['RETAILCRM_API_VERSION'] = Configuration::get('RETAILCRM_API_VERSION'); - $helper->fields_value['RETAILCRM_STATUS_EXPORT'] = Configuration::get('RETAILCRM_STATUS_EXPORT'); - $helper->fields_value['RETAILCRM_DAEMON_COLLECTOR_ACTIVE_1'] = Configuration::get('RETAILCRM_DAEMON_COLLECTOR_ACTIVE'); - $helper->fields_value['RETAILCRM_API_SYNCHRONIZE_CARTS_1'] = Configuration::get('RETAILCRM_API_SYNCHRONIZE_CARTS'); - $helper->fields_value['RETAILCRM_API_SYNCHRONIZED_CART_STATUS'] = Configuration::get('RETAILCRM_API_SYNCHRONIZED_CART_STATUS'); - $helper->fields_value['RETAILCRM_API_SYNCHRONIZED_CART_DELAY'] = Configuration::get('RETAILCRM_API_SYNCHRONIZED_CART_DELAY'); - $helper->fields_value['RETAILCRM_DAEMON_COLLECTOR_KEY'] = Configuration::get('RETAILCRM_DAEMON_COLLECTOR_KEY'); - - $deliverySettings = Configuration::get('RETAILCRM_API_DELIVERY'); - if (isset($deliverySettings) && $deliverySettings != '') { - $deliveryTypes = json_decode($deliverySettings); - if ($deliveryTypes) { - foreach ($deliveryTypes as $idx => $delivery) { - $name = 'RETAILCRM_API_DELIVERY[' . $idx . ']'; - $helper->fields_value[$name] = $delivery; - } - } - } - - $statusSettings = Configuration::get('RETAILCRM_API_STATUS'); - if (isset($statusSettings) && $statusSettings != '') { - $statusTypes = json_decode($statusSettings); - if ($statusTypes) { - foreach ($statusTypes as $idx => $status) { - $name = 'RETAILCRM_API_STATUS[' . $idx . ']'; - $helper->fields_value[$name] = $status; - } - } - } - - $paymentSettings = Configuration::get('RETAILCRM_API_PAYMENT'); - if (isset($paymentSettings) && $paymentSettings != '') { - $paymentTypes = json_decode($paymentSettings); - if ($paymentTypes) { - foreach ($paymentTypes as $idx => $payment) { - $name = 'RETAILCRM_API_PAYMENT[' . $idx . ']'; - $helper->fields_value[$name] = $payment; - } - } - } - - $paymentSettingsDefault = Configuration::get('RETAILCRM_API_PAYMENT_DEFAULT'); - if (isset($paymentSettingsDefault) && $paymentSettingsDefault != '') { - $paymentTypesDefault = json_decode($paymentSettingsDefault); - if ($paymentTypesDefault) { - $name = 'RETAILCRM_API_PAYMENT_DEFAULT'; - $helper->fields_value[$name] = $paymentTypesDefault; - } - } - - $deliverySettingsDefault = Configuration::get('RETAILCRM_API_DELIVERY_DEFAULT'); - if (isset($deliverySettingsDefault) && $deliverySettingsDefault != '') { - $deliveryTypesDefault = json_decode($deliverySettingsDefault); - if ($deliveryTypesDefault) { - $name = 'RETAILCRM_API_DELIVERY_DEFAULT'; - $helper->fields_value[$name] = $deliveryTypesDefault; - } - } - - $synchronizedCartsStatusDefault = Configuration::get('RETAILCRM_API_SYNCHRONIZED_CART_STATUS'); - if (isset($synchronizedCartsStatusDefault) && $synchronizedCartsStatusDefault != '') { - $synchronizedCartsStatus = json_decode($synchronizedCartsStatusDefault); - if ($synchronizedCartsStatus) { - $name = 'RETAILCRM_API_SYNCHRONIZED_CART_STATUS'; - $helper->fields_value[$name] = $synchronizedCartsStatus; - } - } - - $synchronizedCartsDelayDefault = Configuration::get('RETAILCRM_API_SYNCHRONIZED_CART_DELAY'); - if (isset($synchronizedCartsDelayDefault) && $synchronizedCartsDelayDefault != '') { - $synchronizedCartsDelay = json_decode($synchronizedCartsDelayDefault); - if ($synchronizedCartsDelay) { - $name = 'RETAILCRM_API_SYNCHRONIZED_CART_DELAY'; - $helper->fields_value[$name] = $synchronizedCartsDelay; - } - } - - return $helper->generateForm($fields_form); - } - - public function displayUploadOrdersForm() - { - $default_lang = $this->default_lang; - $fields_form = array(); - - if ($this->api) { - $fields_form[]['form'] = array( - 'legend' => array('title' => $this->l('Manual Order Upload')), - 'input' => array( - array( - 'type' => 'text', - 'label' => $this->l('Orders IDs'), - 'name' => 'RETAILCRM_UPLOAD_ORDERS_ID', - 'required' => false - ) - ), - 'submit' => array( - 'title' => $this->l('Upload'), - 'class' => 'button' - ) - ); - } - - $helper = new HelperForm(); - - $helper->module = $this; - $helper->name_controller = $this->name; - $helper->token = Tools::getAdminTokenLite('AdminModules'); - $helper->currentIndex = AdminController::$currentIndex . '&configure=' . $this->name; - $helper->id = "retailcrm_upload_form"; - - $helper->default_form_language = $default_lang; - $helper->allow_employee_form_lang = $default_lang; - - $helper->title = $this->displayName; - $helper->show_toolbar = true; - $helper->toolbar_scroll = true; - $helper->submit_action = 'submit' . $this->name; - $helper->toolbar_btn = array( - 'save' => - array( - 'desc' => $this->l('Save'), - 'href' => sprintf( - "%s&configure=%s&save%s&token=%s", - AdminController::$currentIndex, - $this->name, - $this->name, - Tools::getAdminTokenLite('AdminModules') - ) - ), - 'back' => array( - 'href' => AdminController::$currentIndex . '&token=' . Tools::getAdminTokenLite('AdminModules'), - 'desc' => $this->l('Back to list') - ) - ); - - $helper->fields_value['RETAILCRM_UPLOAD_ORDERS_ID'] = ''; - - return $helper->generateForm($fields_form); - } - public function hookActionCustomerAccountAdd($params) { if ($this->api) { $customer = $params['newCustomer']; - $customerSend = static::buildCrmCustomer($customer); + $customerSend = RetailcrmOrderBuilder::buildCrmCustomer($customer); $this->api->customersCreate($customerSend); @@ -1033,31 +359,73 @@ class RetailCRM extends Module public function hookActionCustomerAccountUpdate($params) { if ($this->api) { - $customer = $params['customer']; + /** @var Customer|CustomerCore|null $customer */ + $customer = isset($params['customer']) ? $params['customer'] : null; - $customerSend = static::buildCrmCustomer($customer); + /** @var Cart|CartCore|null $cart */ + $cart = isset($params['cart']) ? $params['cart'] : null; - $addreses = $customer->getAddresses($this->default_lang); - $address = array_shift($addreses); + /** @var array $customerSend */ + $customerSend = RetailcrmOrderBuilder::buildCrmCustomer($customer); - if (!empty($address)){ + /** @var \RetailcrmAddressBuilder $addressBuilder */ + $addressBuilder = new RetailcrmAddressBuilder(); + + /** @var Address|\AddressCore|array $address */ + $address = array(); + + if (empty($customer)) { + return false; + } + + // Necessary part if we don't want to overwrite other phone numbers. + if (isset($customerSend['externalId'])) { + $customerData = $this->api->customersGet($customerSend['externalId']); + + if ($customerData instanceof RetailcrmApiResponse + && $customerData->isSuccessful() + && $customerData->offsetExists('customer') + ) { + $customerSend['phones'] = $customerData['customer']['phones']; + } + } + + // Workaround: PrestaShop will return OLD address data, before editing. + // In order to circumvent this we are using post data to fill new address object. + if (Tools::getIsset('submitAddress') + && Tools::getIsset('id_customer') + && Tools::getIsset('id_address') + ) { + $address = new Address(Tools::getValue('id_address')); + + foreach (array_keys(Address::$definition['fields']) as $field) { + if (property_exists($address, $field) && Tools::getIsset($field)) { + $address->$field = Tools::getValue($field); + } + } + } else { + $addresses = $customer->getAddresses($this->default_lang); + $address = array_shift($addresses); + } + + if (!empty($address)) { + $addressBuilder->setMode(RetailcrmAddressBuilder::MODE_CUSTOMER); if (is_object($address)) { - $address = static::addressParse($address); + $addressBuilder->setAddress($address); } else { - $address = new Address($address['id_address']); - $address = static::addressParse($address); + $addressBuilder->setAddressId($address['id_address']); } - $customerSend = array_merge($customerSend, $address['customer']); + $addressBuilder->build(); + } elseif (!empty($cart)) { + $addressBuilder + ->setMode(RetailcrmAddressBuilder::MODE_ORDER_DELIVERY) + ->setAddressId($cart->id_address_invoice) + ->build(); } - if (isset($params['cart'])){ - $address = static::addressParse($params['cart']); - $customerSend = array_merge($customerSend, $address['customer']); - } - - $customerSend = array_merge($customerSend, isset($address['customer']) ? $address['customer'] : []); + $customerSend = RetailcrmTools::mergeCustomerAddress($customerSend, $addressBuilder->getDataArray()); $this->api->customersEdit($customerSend); @@ -1083,16 +451,42 @@ class RetailCRM extends Module public function hookActionPaymentConfirmation($params) { - if ($this->apiVersion == 4) { - $this->api->ordersEdit( - array( - 'externalId' => $params['id_order'], - 'paymentStatus' => 'paid' - ) - ); + return $this->hookActionOrderStatusPostUpdate($params); + } + + /** + * This will ensure that our delivery mapping will not lose associations with edited deliveries. + * PrestaShop doesn't actually edit delivery - it will hide it via `delete` flag in DB and create new one. + * That's why we need to intercept this here and update delivery ID in mapping if necessary. + * + * @param array $params + */ + public function hookActionCarrierUpdate($params) + { + if (!array_key_exists('id_carrier', $params) || !array_key_exists('carrier', $params)) { + return; } - return $this->hookActionOrderStatusPostUpdate($params); + /** @var Carrier|\CarrierCore $newCarrier */ + $newCarrier = $params['carrier']; + $oldCarrierId = $params['id_carrier']; + + if (!($newCarrier instanceof Carrier) && !($newCarrier instanceof CarrierCore)) { + return; + } + + $delivery = json_decode(Configuration::get(RetailCRM::DELIVERY), true); + $deliveryDefault = json_decode(Configuration::get(static::DELIVERY_DEFAULT), true); + + if ($oldCarrierId == $deliveryDefault) { + Configuration::updateValue(static::DELIVERY_DEFAULT, json_encode($newCarrier->id)); + } + + if (is_array($delivery) && array_key_exists($oldCarrierId, $delivery)) { + $delivery[$newCarrier->id] = $delivery[$oldCarrierId]; + unset($delivery[$oldCarrierId]); + Configuration::updateValue(static::DELIVERY, json_encode($delivery)); + } } public function hookActionOrderEdited($params) @@ -1103,19 +497,22 @@ class RetailCRM extends Module 'firstName' => $params['customer']->firstname, 'lastName' => $params['customer']->lastname, 'email' => $params['customer']->email, - 'createdAt' => self::verifyDate($params['order']->date_add, 'Y-m-d H:i:s') + 'createdAt' => RetailcrmTools::verifyDate($params['order']->date_add, 'Y-m-d H:i:s') ? $params['order']->date_add : date('Y-m-d H:i:s'), - 'delivery' => array('cost' => $params['order']->total_shipping) + 'delivery' => array('cost' => $params['order']->total_shipping), + 'discountManualAmount' => $params['order']->total_discounts ); - if ($this->apiVersion != 5) { - $order['discount'] = $params['order']->total_discounts; - } else { - $order['discountManualAmount'] = $params['order']->total_discounts; + try { + $orderdb = new Order($params['order']->id); + } catch (PrestaShopDatabaseException $exception) { + RetailcrmLogger::writeCaller('hookActionOrderEdited', $exception->getMessage()); + RetailcrmLogger::writeNoCaller($exception->getTraceAsString()); + } catch (PrestaShopException $exception) { + RetailcrmLogger::writeCaller('hookActionOrderEdited', $exception->getMessage()); + RetailcrmLogger::writeNoCaller($exception->getTraceAsString()); } - $orderdb = new Order($params['order']->id); - $comment = $orderdb->getFirstMessage(); if ($comment !== false) { $order['customerComment'] = $comment; @@ -1153,101 +550,51 @@ class RetailCRM extends Module return false; } - private static function addressParse($address) - { - if (!isset($customer)) { - $customer = []; - } - - if (!isset($order)) { - $order = []; - } - - if ($address instanceof Address) { - $postcode = $address->postcode; - $city = $address->city; - $addres_line = sprintf("%s %s", $address->address1, $address->address2); - $countryIso = CountryCore::getIsoById($address->id_country); - } - - if (!empty($postcode)) { - $customer['address']['index'] = $postcode; - $order['delivery']['address']['index'] = $postcode; - } - - if (!empty($city)) { - $customer['address']['city'] = $city; - $order['delivery']['address']['city'] = $city; - } - - if (!empty($addres_line)) { - $customer['address']['text'] = $addres_line; - $order['delivery']['address']['text'] = $addres_line; - } - - if (!empty($countryIso)) { - $order['countryIso'] = $countryIso; - $customer['address']['countryIso'] = $countryIso; - } - - $phones = static::getPhone($address); - $order = array_merge($order, $phones['order']); - $customer = array_merge($customer, $phones['customer']); - $addressArray = array('order' => $order, 'customer' => $customer); - - return $addressArray; - } - - private static function getPhone($address) - { - if (!isset($customer)) { - $customer = []; - } - - if (!isset($order)) { - $order = []; - } - - if (!empty($address->phone_mobile)){ - $order['phone'] = $address->phone_mobile; - $customer['phones'][] = array('number'=> $address->phone_mobile); - } - - if (!empty($address->phone)){ - $order['additionalPhone'] = $address->phone; - $customer['phones'][] = array('number'=> $address->phone); - } - - if (!isset($order['phone']) && !empty($order['additionalPhone'])){ - $order['phone'] = $order['additionalPhone']; - unset($order['additionalPhone']); - } - - $phonesArray = array('customer' => $customer, 'order' => $order); - - return $phonesArray; - } - public function hookActionOrderStatusPostUpdate($params) { - $delivery = json_decode(Configuration::get('RETAILCRM_API_DELIVERY'), true); - $payment = json_decode(Configuration::get('RETAILCRM_API_PAYMENT'), true); - $status = json_decode(Configuration::get('RETAILCRM_API_STATUS'), true); + $status = json_decode(Configuration::get(static::STATUS), true); if (isset($params['orderStatus'])) { + $cmsOrder = $params['order']; $cart = $params['cart']; - $response = $this->api->ordersGet(self::getCartOrderExternalId($cart)); - $order = static::buildCrmOrder($params['order'], $params['customer'], $cart, false); + $customer = $params['customer']; + $response = $this->api->ordersGet(RetailcrmTools::getCartOrderExternalId($cart)); + $crmOrder = isset($response['order']) ? $response['order'] : array(); + $orderBuilder = new RetailcrmOrderBuilder(); - if (!empty($response) && isset($response['order'])) { - $order['id'] = $response['order']['id']; + try { + $order = $orderBuilder + ->defaultLangFromConfiguration() + ->setApi($this->api) + ->setCmsOrder($cmsOrder) + ->setCmsCart($cart) + ->setCmsCustomer($customer) + ->buildOrderWithPreparedCustomer(); + } catch (\InvalidArgumentException $exception) { + RetailcrmLogger::writeCaller( + 'hookActionOrderStatusPostUpdate', + $exception->getMessage() + ); + RetailcrmLogger::writeNoCaller($exception->getTraceAsString()); + + return false; + } + + if (!empty($crmOrder)) { + $order['id'] = $crmOrder['id']; $this->api->ordersEdit($order, 'id'); + + if (empty($crmOrder['payments']) && !empty($order['payments'])) { + $payment = array_merge(reset($order['payments']), array( + 'order' => array('externalId' => $order['externalId']) + )); + $this->api->ordersPaymentCreate($payment); + } } else { $this->api->ordersCreate($order); } return true; - } elseif (isset($params['newOrderStatus'])) { $statusCode = $params['newOrderStatus']->id; @@ -1272,9 +619,10 @@ class RetailCRM extends Module public function hookActionPaymentCCAdd($params) { - $order_id = Order::getOrderByCartId($params['cart']->id); $payments = $this->reference->getSystemPaymentModules(); - $paymentCRM = json_decode(Configuration::get('RETAILCRM_API_PAYMENT'), true); + $paymentCRM = json_decode(Configuration::get(static::PAYMENT), true); + $payment = ""; + $payCode = ""; foreach ($payments as $valPay) { if ($valPay['name'] == $params['paymentCC']->payment_method) { @@ -1282,13 +630,17 @@ class RetailCRM extends Module } } - if (array_key_exists($payCode, $paymentCRM) && !empty($paymentCRM[$payCode])) { + if (!empty($payCode) && array_key_exists($payCode, $paymentCRM) && !empty($paymentCRM[$payCode])) { $payment = $paymentCRM[$payCode]; } - $response = $this->api->ordersGet($order_id); + if (empty($payment)) { + return false; + } - if ($response !== false) { + $response = $this->api->ordersGet(RetailcrmTools::getCartOrderExternalId($params['cart'])); + + if ($response !== false && isset($response['order'])) { $orderCRM = $response['order']; if ($orderCRM && $orderCRM['payments']) { @@ -1303,106 +655,109 @@ class RetailCRM extends Module } } } - } - if (isset($updatePayment)) { - $this->api->ordersPaymentEdit($updatePayment); + if (isset($updatePayment)) { + $this->api->ordersPaymentEdit($updatePayment); + } else { + $createPayment = array( + 'externalId' => $params['paymentCC']->id, + 'amount' => $params['paymentCC']->amount, + 'paidAt' => $params['paymentCC']->date_add, + 'type' => $payment, + 'status' => 'paid', + 'order' => array( + 'externalId' => RetailcrmTools::getCartOrderExternalId($params['cart']), + ), + ); - return true; - } else { - $createPayment = array( - 'externalId' => $params['paymentCC']->id, - 'amount' => $params['paymentCC']->amount, - 'paidAt' => $params['paymentCC']->date_add, - 'type' => $payment, - 'status' => 'paid', - 'order' => array( - 'externalId' => $order_id, - ), - ); - - $this->api->ordersPaymentCreate($createPayment); - - return true; - } - - return false; - } - - private function validateCrmAddress($address) - { - if (preg_match("/https:\/\/(.*).retailcrm.(pro|ru|es)/", $address) === 1) { - return true; - } - - return false; - } - - private function validateApiVersion($settings) - { - $api = new RetailcrmProxy( - $settings['address'], - $settings['token'], - _PS_ROOT_DIR_ . '/retailcrm.log', - $settings['version'] - ); - - $response = $api->deliveryTypesList(); - - if ($response !== false) { - if (!$settings['clientId']) { - $clientId = uniqid(); - $result = $this->integrationModule($api, $clientId, $settings['version']); - - if ($result) { - Configuration::updateValue('RETAILCRM_CLIENT_ID', $clientId); - } + $this->api->ordersPaymentCreate($createPayment); } - - return true; - } - - return false; - } - - private function validateStatuses($statuses, $statusExport, $cartStatus) - { - if ($cartStatus != '' && ($cartStatus == $statusExport || (stripos($statuses, $cartStatus) !== false))) { - return false; } return true; } - private function validateForm($settings, $output) - { - if (!$this->validateCrmAddress($settings['address']) || !Validate::isGenericName($settings['address'])) { - $output .= $this->displayError($this->l('Invalid or empty crm address')); - } elseif (!$settings['token'] || $settings['token'] == '') { - $output .= $this->displayError($this->l('Invalid or empty crm api token')); - } elseif (!$this->validateApiVersion($settings)) { - $output .= $this->displayError($this->l('The selected version of the API is unavailable')); - } elseif (!$this->validateStatuses( - $settings['status'], - $settings['statusExport'], - $settings['synchronizeCartStatus']) - ) { - $output .= $this->displayError($this->l('Order status for abandoned carts should not be used in other settings')); - } - - return $output; - } - /** - * Returns externalId for order - * - * @param Cart $cart + * Save settings handler * * @return string */ - public static function getCartOrderExternalId(Cart $cart) + private function saveSettings() { - return sprintf('pscart_%d', $cart->id); + $output = ''; + $url = (string) Tools::getValue(static::API_URL); + $apiKey = (string) Tools::getValue(static::API_KEY); + $consultantCode = (string) Tools::getValue(static::CONSULTANT_SCRIPT); + + if (!empty($url) && !empty($apiKey)) { + $settings = array( + 'url' => $url, + 'apiKey' => $apiKey, + 'address' => (string)(Tools::getValue(static::API_URL)), + 'delivery' => json_encode(Tools::getValue(static::DELIVERY)), + 'status' => json_encode(Tools::getValue(static::STATUS)), + 'payment' => json_encode(Tools::getValue(static::PAYMENT)), + 'deliveryDefault' => json_encode(Tools::getValue(static::DELIVERY_DEFAULT)), + 'paymentDefault' => json_encode(Tools::getValue(static::PAYMENT_DEFAULT)), + 'statusExport' => (string)(Tools::getValue(static::STATUS_EXPORT)), + 'enableCorporate' => (Tools::getValue(static::ENABLE_CORPORATE_CLIENTS) !== false), + 'enableHistoryUploads' => (Tools::getValue(static::ENABLE_HISTORY_UPLOADS) !== false), + 'enableBalancesReceiving' => (Tools::getValue(static::ENABLE_BALANCES_RECEIVING) !== false), + 'collectorActive' => (Tools::getValue(static::COLLECTOR_ACTIVE) !== false), + 'collectorKey' => (string)(Tools::getValue(static::COLLECTOR_KEY)), + 'clientId' => Configuration::get(static::CLIENT_ID), + 'synchronizeCartsActive' => (Tools::getValue(static::SYNC_CARTS_ACTIVE) !== false), + 'synchronizedCartStatus' => (string)(Tools::getValue(static::SYNC_CARTS_STATUS)), + 'synchronizedCartDelay' => (string)(Tools::getValue(static::SYNC_CARTS_DELAY)) + ); + + $output .= $this->validateForm($settings, $output); + + if ($output === '') { + Configuration::updateValue(static::API_URL, $settings['url']); + Configuration::updateValue(static::API_KEY, $settings['apiKey']); + Configuration::updateValue(static::DELIVERY, $settings['delivery']); + Configuration::updateValue(static::STATUS, $settings['status']); + Configuration::updateValue(static::PAYMENT, $settings['payment']); + Configuration::updateValue(static::DELIVERY_DEFAULT, $settings['deliveryDefault']); + Configuration::updateValue(static::PAYMENT_DEFAULT, $settings['paymentDefault']); + Configuration::updateValue(static::STATUS_EXPORT, $settings['statusExport']); + Configuration::updateValue(static::ENABLE_CORPORATE_CLIENTS, $settings['enableCorporate']); + Configuration::updateValue(static::ENABLE_HISTORY_UPLOADS, $settings['enableHistoryUploads']); + Configuration::updateValue(static::ENABLE_BALANCES_RECEIVING, $settings['enableBalancesReceiving']); + Configuration::updateValue(static::COLLECTOR_ACTIVE, $settings['collectorActive']); + Configuration::updateValue(static::COLLECTOR_KEY, $settings['collectorKey']); + Configuration::updateValue(static::SYNC_CARTS_ACTIVE, $settings['synchronizeCartsActive']); + Configuration::updateValue(static::SYNC_CARTS_STATUS, $settings['synchronizedCartStatus']); + Configuration::updateValue(static::SYNC_CARTS_DELAY, $settings['synchronizedCartDelay']); + + $this->apiUrl = $settings['url']; + $this->apiKey = $settings['apiKey']; + $this->api = new RetailcrmProxy($this->apiUrl, $this->apiKey, $this->log); + $this->reference = new RetailcrmReferences($this->api); + + if ($this->isRegisteredInHook('actionPaymentCCAdd') == 0) { + $this->registerHook('actionPaymentCCAdd'); + } + } + } + + if (!empty($consultantCode)) { + $extractor = new RetailcrmConsultantRcctExtractor(); + $rcct = $extractor->setConsultantScript($consultantCode)->build()->getDataString(); + + if (!empty($rcct)) { + Configuration::updateValue(static::CONSULTANT_SCRIPT, $consultantCode, true); + Configuration::updateValue(static::CONSULTANT_RCCT, $rcct); + Cache::getInstance()->set(static::CONSULTANT_RCCT, $rcct); + } else { + Configuration::deleteByName(static::CONSULTANT_SCRIPT); + Configuration::deleteByName(static::CONSULTANT_RCCT); + Cache::getInstance()->delete(static::CONSULTANT_RCCT); + } + } + + return $output; } /** @@ -1410,42 +765,27 @@ class RetailCRM extends Module * * @param \RetailcrmProxy $apiClient * @param string $clientId - * @param string $apiVersion * @param boolean $active * * @return boolean */ - private function integrationModule($apiClient, $clientId, $apiVersion, $active = true) + private function integrationModule($apiClient, $clientId, $active = true) { $scheme = isset($_SERVER['HTTPS']) ? 'https://' : 'http://'; $logo = 'https://s3.eu-central-1.amazonaws.com/retailcrm-billing/images/5b845ce986911-prestashop2.svg'; $integrationCode = 'prestashop'; $name = 'PrestaShop'; $accountUrl = $scheme . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; - - if ($apiVersion == '4') { - $configuration = array( - 'name' => $name, - 'code' => $integrationCode . '-' . $clientId, - 'logo' => $logo, - 'configurationUrl' => $accountUrl, - 'active' => $active - ); - - $response = $apiClient->marketplaceSettingsEdit($configuration); - } else { - $configuration = array( - 'clientId' => $clientId, - 'code' => $integrationCode . '-' . $clientId, - 'integrationCode' => $integrationCode, - 'active' => $active, - 'name' => $name, - 'logo' => $logo, - 'accountUrl' => $accountUrl - ); - - $response = $apiClient->integrationModulesEdit($configuration); - } + $configuration = array( + 'clientId' => $clientId, + 'code' => $integrationCode . '-' . $clientId, + 'integrationCode' => $integrationCode, + 'active' => $active, + 'name' => $name, + 'logo' => $logo, + 'accountUrl' => $accountUrl + ); + $response = $apiClient->integrationModulesEdit($configuration); if (!$response) { return false; @@ -1457,4 +797,519 @@ class RetailCRM extends Module return false; } + + /** + * Returns true if provided connection supports API v5 + * + * @param $settings + * + * @return bool + */ + private function validateApiVersion($settings) + { + /** @var \RetailcrmProxy|\RetailcrmApiClientV5 $api */ + $api = new RetailcrmProxy( + $settings['url'], + $settings['apiKey'], + $this->log + ); + + $response = $api->apiVersions(); + + if ($response !== false && isset($response['versions']) && !empty($response['versions'])) { + foreach ($response['versions'] as $version) { + if ($version == static::LATEST_API_VERSION + || Tools::substr($version, 0, 1) == static::LATEST_API_VERSION + ) { + return true; + } + } + } + + return false; + } + + /** + * Workaround to pass translate method into another classes + * + * @param $text + * + * @return mixed + */ + public function translate($text) + { + return $this->l($text); + } + + /** + * Cart status must be present and must be unique to cartsIds only + * + * @param string $statuses + * @param string $statusExport + * @param string $cartStatus + * + * @return bool + */ + private function validateCartStatus($statuses, $statusExport, $cartStatus) + { + if ($cartStatus != '' && ($cartStatus == $statusExport || stripos($statuses, $cartStatus))) { + return false; + } + + return true; + } + + /** + * Returns false if mapping is not valid in one-to-one relation + * + * @param string $statuses + * + * @return bool + */ + private function validateMappingOneToOne($statuses) + { + $data = json_decode($statuses, true); + + if (json_last_error() != JSON_ERROR_NONE || !is_array($data)) { + return true; + } + + $statusesList = array_filter(array_values($data)); + + if (count($statusesList) != count(array_unique($statusesList))) { + return false; + } + + return true; + } + + /** + * Settings form validator + * + * @param $settings + * @param $output + * + * @return string + */ + private function validateForm($settings, $output) + { + if (!RetailcrmTools::validateCrmAddress($settings['url']) || !Validate::isGenericName($settings['url'])) { + $output .= $this->displayError($this->l('Invalid or empty crm address')); + } elseif (!$settings['apiKey'] || $settings['apiKey'] == '') { + $output .= $this->displayError($this->l('Invalid or empty crm api token')); + } elseif (!$this->validateApiVersion($settings)) { + $output .= $this->displayError($this->l('The selected version of the API is unavailable')); + } elseif (!$this->validateCartStatus( + $settings['status'], + $settings['statusExport'], + $settings['synchronizedCartStatus'] + )) { + $output .= $this->displayError( + $this->l('Order status for abandoned carts should not be used in other settings') + ); + } elseif (!$this->validateMappingOneToOne($settings['status'])) { + $output .= $this->displayError( + $this->l('Order statuses should not repeat in statuses matrix') + ); + } elseif (!$this->validateMappingOneToOne($settings['delivery'])) { + $output .= $this->displayError( + $this->l('Delivery types should not repeat in delivery matrix') + ); + } elseif (!$this->validateMappingOneToOne($settings['payment'])) { + $output .= $this->displayError( + $this->l('Payment types should not repeat in payment matrix') + ); + } + + return $output; + } + + /** + * Returns error log path + * + * @return string + */ + public static function getErrorLog() + { + if (!defined('_PS_ROOT_DIR_')) { + return ''; + } + + return _PS_ROOT_DIR_ . '/retailcrm.log'; + } + + /** + * Loads data from modules list cache + * + * @return array|mixed + */ + private static function requireModulesCache() + { + if (file_exists(static::getModulesCache())) { + return require_once(static::getModulesCache()); + } + + return false; + } + + /** + * Returns path to modules list cache + * + * @return string + */ + private static function getModulesCache() + { + if (defined('_PS_CACHE_DIR_')) { + return _PS_CACHE_DIR_ . '/retailcrm_modules_cache.php'; + } + + if (!defined('_PS_ROOT_DIR_')) { + return ''; + } + + $cacheDir = _PS_ROOT_DIR_ . '/cache'; + + if (realpath($cacheDir) !== false && is_dir($cacheDir)) { + return $cacheDir . '/retailcrm_modules_cache.php'; + } + + return _PS_ROOT_DIR_ . '/retailcrm_modules_cache.php'; + } + + /** + * Returns all module settings + * + * @return array + */ + public static function getSettings() + { + $syncCartsDelay = (string) (Configuration::get(static::SYNC_CARTS_DELAY)); + + // Use 15 minutes as default interval but don't change immediate interval to it if user already made decision + if (empty($syncCartsDelay) && $syncCartsDelay !== "0") { + $syncCartsDelay = "900"; + } + + return array( + 'url' => (string)(Configuration::get(static::API_URL)), + 'apiKey' => (string)(Configuration::get(static::API_KEY)), + 'delivery' => json_decode(Configuration::get(static::DELIVERY), true), + 'status' => json_decode(Configuration::get(static::STATUS), true), + 'payment' => json_decode(Configuration::get(static::PAYMENT), true), + 'deliveryDefault' => json_decode(Configuration::get(static::DELIVERY_DEFAULT), true), + 'paymentDefault' => json_decode(Configuration::get(static::PAYMENT_DEFAULT), true), + 'statusExport' => (string)(Configuration::get(static::STATUS_EXPORT)), + 'collectorActive' => (Configuration::get(static::COLLECTOR_ACTIVE)), + 'collectorKey' => (string)(Configuration::get(static::COLLECTOR_KEY)), + 'clientId' => Configuration::get(static::CLIENT_ID), + 'synchronizeCartsActive' => (Configuration::get(static::SYNC_CARTS_ACTIVE)), + 'synchronizedCartStatus' => (string)(Configuration::get(static::SYNC_CARTS_STATUS)), + 'synchronizedCartDelay' => $syncCartsDelay, + 'consultantScript' => (string)(Configuration::get(static::CONSULTANT_SCRIPT)), + 'enableCorporate' => (bool)(Configuration::get(static::ENABLE_CORPORATE_CLIENTS)), + 'enableHistoryUploads' => (bool)(Configuration::get(static::ENABLE_HISTORY_UPLOADS)), + 'enableBalancesReceiving' => (bool)(Configuration::get(static::ENABLE_BALANCES_RECEIVING)), + ); + } + + /** + * Returns all settings names in DB + * + * @return array + */ + public static function getSettingsNames() + { + return array( + 'urlName' => static::API_URL, + 'apiKeyName' => static::API_KEY, + 'deliveryName' => static::DELIVERY, + 'statusName' => static::STATUS, + 'paymentName' => static::PAYMENT, + 'deliveryDefaultName' => static::DELIVERY_DEFAULT, + 'paymentDefaultName' => static::PAYMENT_DEFAULT, + 'statusExportName' => static::STATUS_EXPORT, + 'collectorActiveName' => static::COLLECTOR_ACTIVE, + 'collectorKeyName' => static::COLLECTOR_KEY, + 'clientIdName' => static::CLIENT_ID, + 'synchronizeCartsActiveName' => static::SYNC_CARTS_ACTIVE, + 'synchronizedCartStatusName' => static::SYNC_CARTS_STATUS, + 'synchronizedCartDelayName' => static::SYNC_CARTS_DELAY, + 'uploadOrders' => static::UPLOAD_ORDERS, + 'consultantScriptName' => static::CONSULTANT_SCRIPT, + 'enableCorporateName' => static::ENABLE_CORPORATE_CLIENTS, + 'enableHistoryUploadsName' => static::ENABLE_HISTORY_UPLOADS, + 'enableBalancesReceivingName' => static::ENABLE_BALANCES_RECEIVING + ); + } + + /** + * Returns modules list, caches result. Recreates cache when needed. + * Activity indicator in cache will be rewrited by current state. + * + * @return array + */ + public static function getCachedCmsModulesList() + { + $storedHash = (string) Configuration::get(static::MODULE_LIST_CACHE_CHECKSUM); + $calculatedHash = md5(implode('#', Module::getModulesDirOnDisk(true))); + + if ($storedHash != $calculatedHash) { + $serializedModules = array(); + static::$moduleListCache = Module::getModulesOnDisk(true); + + foreach (static::$moduleListCache as $module) { + $serializedModules[] = json_encode($module); + } + + Configuration::updateValue(static::MODULE_LIST_CACHE_CHECKSUM, $calculatedHash); + static::writeModulesCache($serializedModules); + + return static::$moduleListCache; + } else { + try { + if (is_array(static::$moduleListCache)) { + return static::$moduleListCache; + } + + $modulesList = static::requireModulesCache(); + + if ($modulesList === false) { + Configuration::updateValue(static::MODULE_LIST_CACHE_CHECKSUM, 'not exist'); + + return static::getCachedCmsModulesList(); + } + + static::$moduleListCache = array(); + + foreach ($modulesList as $serializedModule) { + $deserialized = json_decode($serializedModule); + + if ($deserialized instanceof stdClass + && property_exists($deserialized, 'name') + && property_exists($deserialized, 'active') + ) { + $deserialized->active = Module::isEnabled($deserialized->name); + static::$moduleListCache[] = $deserialized; + } + } + + static::$moduleListCache = array_filter(static::$moduleListCache); + unset($modulesList); + + return static::$moduleListCache; + } catch (Exception $exception) { + RetailcrmLogger::writeCaller(__METHOD__, $exception->getMessage()); + RetailcrmLogger::writeNoCaller($exception->getTraceAsString()); + Configuration::updateValue(static::MODULE_LIST_CACHE_CHECKSUM, 'exception'); + + return static::getCachedCmsModulesList(); + } catch (Throwable $throwable) { + RetailcrmLogger::writeCaller(__METHOD__, $throwable->getMessage()); + RetailcrmLogger::writeNoCaller($throwable->getTraceAsString()); + Configuration::updateValue(static::MODULE_LIST_CACHE_CHECKSUM, 'throwable'); + + return static::getCachedCmsModulesList(); + } + } + } + + /** + * Writes module list to cache file. + * + * @param $data + */ + private static function writeModulesCache($data) + { + $file = fopen(static::getModulesCache(), 'w+'); + + if ($file !== false) { + fwrite($file, ' '0', + 'name' => $this->l('Immediately') + ), + array( + 'id_option' => '60', + 'name' => $this->l('After 1 minute') + ), + array( + 'id_option' => '300', + 'name' => $this->l('After 5 minutes') + ), + array( + 'id_option' => '600', + 'name' => $this->l('After 10 minutes') + ), + array( + 'id_option' => '900', + 'name' => $this->l('After 15 minutes') + ), + array( + 'id_option' => '1800', + 'name' => $this->l('After 30 minutes') + ), + array( + 'id_option' => '2700', + 'name' => $this->l('After 45 minute') + ), + array( + 'id_option' => '3600', + 'name' => $this->l('After 1 hour') + ), + ); + } + + /** + * Initializes arrays of messages + */ + private function initializeTemplateMessages() + { + if (is_null($this->templateErrors)) { + $this->templateErrors = array(); + } + + if (is_null($this->templateWarnings)) { + $this->templateWarnings = array(); + } + + if (is_null($this->templateConfirms)) { + $this->templateConfirms = array(); + } + + if (is_null($this->templateErrors)) { + $this->templateInfos = array(); + } + } + + /** + * Returns error messages + * + * @return array + */ + protected function getErrorMessages() + { + if (empty($this->templateErrors)) { + return array(); + } + + return $this->templateErrors; + } + + /** + * Returns warning messages + * + * @return array + */ + protected function getWarningMessage() + { + if (empty($this->templateWarnings)) { + return array(); + } + + return $this->templateWarnings; + } + + /** + * Returns information messages + * + * @return array + */ + protected function getInformationMessages() + { + if (empty($this->templateInfos)) { + return array(); + } + + return $this->templateInfos; + } + + /** + * Returns confirmation messages + * + * @return array + */ + protected function getConfirmationMessages() + { + if (empty($this->templateConfirms)) { + return array(); + } + + return $this->templateConfirms; + } + + /** + * Replacement for default error message helper + * + * @param string|array $message + * + * @return string + */ + public function displayError($message) + { + $this->initializeTemplateMessages(); + $this->templateErrors[] = $message; + + return ' '; + } + + /** + * Replacement for default warning message helper + * + * @param string|array $message + * + * @return string + */ + public function displayWarning($message) + { + $this->initializeTemplateMessages(); + $this->templateWarnings[] = $message; + + return ' '; + } + + /** + * Replacement for default warning message helper + * + * @param string|array $message + * + * @return string + */ + public function displayConfirmation($message) + { + $this->initializeTemplateMessages(); + $this->templateConfirms[] = $message; + + return ' '; + } + + /** + * Replacement for default warning message helper + * + * @param string|array $message + * + * @return string + */ + public function displayInformation($message) + { + $this->initializeTemplateMessages(); + $this->templateInfos[] = $message; + + return ' '; + } } diff --git a/retailcrm/translations/es.php b/retailcrm/translations/es.php old mode 100755 new mode 100644 index f7b3285..958c39d --- a/retailcrm/translations/es.php +++ b/retailcrm/translations/es.php @@ -2,71 +2,17 @@ global $_MODULE; $_MODULE = array(); -$_MODULE['<{retailcrm}prestashop>retailcrm_7d1d9327371097419bdf83ffa671d2f2'] = 'Versión de la API'; -$_MODULE['<{retailcrm}prestashop>retailcrm_463dc31aa1a0b6e871b1a9fed8e9860a'] = 'retailCRM'; -$_MODULE['<{retailcrm}prestashop>retailcrm_30de6237576b9a24f6fc599c22a35a4b'] = 'El módulo de integración con retailCRM'; +$_MODULE['<{retailcrm}prestashop>retailcrm_b0edd77b179acca4cb3572c4393db254'] = 'retailCRM'; +$_MODULE['<{retailcrm}prestashop>retailcrm_4aa0e53499eeb8383a6330c96b9ed7c3'] = 'Módulo de integración para retailCRM'; $_MODULE['<{retailcrm}prestashop>retailcrm_876f23178c29dc2552c0b48bf23cd9bd'] = '¿Está seguro de que desea eliminar el módulo?'; +$_MODULE['<{retailcrm}prestashop>retailcrm_6bd461d1fc51b3294c6513cecc24758d'] = 'Los pedidos han sido cargados con éxito'; +$_MODULE['<{retailcrm}prestashop>retailcrm_9a7fc06b4b2359f1f26f75fbbe27a3e8'] = 'No todos los pedidos se han cargado con existo'; $_MODULE['<{retailcrm}prestashop>retailcrm_b9b2d9f66d0112f3aae7dbdbd4e22a43'] = 'La dirección del CRM es incorrecta o está vacía'; $_MODULE['<{retailcrm}prestashop>retailcrm_942010ef43f3fec28741f62a0d9ff29c'] = 'La clave CRM es incorrecta o está vacía'; -$_MODULE['<{retailcrm}prestashop>retailcrm_fba05687b61bc936d1a9a92371ba8bcf'] = '¡Atención! La zona horaria de CRM debe coincidir con la zona horaria de la tienda, la configuración de la zona horaria de CRM se puede establecer en:'; -$_MODULE['<{retailcrm}prestashop>retailcrm_5effd5157947e8ba4a08883f198b2e31'] = 'La dirección del CRM no es válida o está vacía'; -$_MODULE['<{retailcrm}prestashop>retailcrm_576300f5b6faeb746bb6d034d98e7afd'] = 'La clave de API no es válida o está vacía'; -$_MODULE['<{retailcrm}prestashop>retailcrm_c888438d14855d7d96a2724ee9c306bd'] = 'Los ajustes están actualizados'; -$_MODULE['<{retailcrm}prestashop>retailcrm_51af428aa0dcceb5230acb267ecb91c5'] = 'La configuración de la conexión'; -$_MODULE['<{retailcrm}prestashop>retailcrm_4cbd5dbeeef7392e50358b1bc00dd592'] = 'URL CRM'; -$_MODULE['<{retailcrm}prestashop>retailcrm_7f775042e08eddee6bbfd8fbe0add4a3'] = 'La clave API'; -$_MODULE['<{retailcrm}prestashop>retailcrm_52a13123e134b8b72b6299bc14a36aad'] = 'Daemon Collector'; -$_MODULE['<{retailcrm}prestashop>retailcrm_a13367a8e2a3f3bf4f3409079e3fdf87'] = 'Active'; -$_MODULE['<{retailcrm}prestashop>retailcrm_f75d8fa5c89351544d372cf90528ccf2'] = 'Clave de la página web'; -$_MODULE['<{retailcrm}prestashop>retailcrm_c9cc8cce247e49bae79f15173ce97354'] = 'Guardar'; -$_MODULE['<{retailcrm}prestashop>retailcrm_065ab3a28ca4f16f55f103adc7d0226f'] = 'Los métodos del envío'; -$_MODULE['<{retailcrm}prestashop>retailcrm_33af8066d3c83110d4bd897f687cedd2'] = 'Los estados de pedidos'; -$_MODULE['<{retailcrm}prestashop>retailcrm_bab959acc06bb03897b294fbb892be6b'] = 'Los métodos de pago'; -$_MODULE['<{retailcrm}prestashop>retailcrm_6310f29293c902c64db619c29179d99a'] = 'Los método de envío'; -$_MODULE['<{retailcrm}prestashop>retailcrm_4dbcb38bbbff5d4a402f2575c57a35e6'] = 'El tipo de pago'; -$_MODULE['<{retailcrm}prestashop>retailcrm_dd7bf230fde8d4836917806aff6a6b27'] = 'Dirección'; -$_MODULE['<{retailcrm}prestashop>retailcrm_630f6dc397fe74e52d5189e2c80f282b'] = 'Volver a la lista'; -$_MODULE['<{retailcrm}prestashop>retailcrm_5c1cf6cfec2dad86c8ca5286a0294516'] = 'Nombre'; -$_MODULE['<{retailcrm}prestashop>retailcrm_c695cfe527a6fcd680114851b86b7555'] = 'Apellido'; -$_MODULE['<{retailcrm}prestashop>retailcrm_f9dd946cc89c1f3b41a0edbe0f36931d'] = 'Teléfono'; -$_MODULE['<{retailcrm}prestashop>retailcrm_61a649a33f2869e5e35fbb7aff3a80d9'] = 'Email'; -$_MODULE['<{retailcrm}prestashop>retailcrm_2664f03ac6b8bb9eee4287720e407db3'] = 'Dirección'; -$_MODULE['<{retailcrm}prestashop>retailcrm_6ddc09dc456001d9854e9fe670374eb2'] = 'País'; -$_MODULE['<{retailcrm}prestashop>retailcrm_69aede266809f89b89fe70681f6a129f'] = 'Provincia/Región/República'; -$_MODULE['<{retailcrm}prestashop>retailcrm_859214628431995197c0558f7b5f8ffc'] = 'Ciudad'; -$_MODULE['<{retailcrm}prestashop>retailcrm_4348f938bbddd8475e967ccb47ecb234'] = 'Código Postal'; -$_MODULE['<{retailcrm}prestashop>retailcrm_78fce82336bbbdca7f6da7564b8f9325'] = 'Calle'; -$_MODULE['<{retailcrm}prestashop>retailcrm_71a6834884666147c0334f0c40bc7295'] = 'Casa/Edificio'; -$_MODULE['<{retailcrm}prestashop>retailcrm_f88a77e3d68d251c3dc4008c327b5a0c'] = 'Piso'; -$_MODULE['<{retailcrm}prestashop>retailcrm_d977f846d110fcb7f71c6f97330c9d10'] = 'Número del piso y la letra'; -$_MODULE['<{retailcrm}prestashop>retailcrm_56c1e354d36beb85b0d881c5b2e24cbe'] = 'Planta'; -$_MODULE['<{retailcrm}prestashop>retailcrm_4d34f53389ed7f28ca91fc31ea360a66'] = 'Bloque'; -$_MODULE['<{retailcrm}prestashop>retailcrm_49354b452ec305136a56fe7731834156'] = 'Casa/Edificio'; -$_MODULE['<{retailcrm}prestashop>retailcrm_04176f095283bc729f1e3926967e7034'] = 'Nombre'; -$_MODULE['<{retailcrm}prestashop>retailcrm_dff4bf10409100d989495c6d5486035e'] = 'Apellido'; -$_MODULE['<{retailcrm}prestashop>retailcrm_1c76cbfe21c6f44c1d1e59d54f3e4420'] = 'Empresa'; -$_MODULE['<{retailcrm}prestashop>retailcrm_1aadcc03a9dbba84a3c5a5cbfde8a162'] = 'NIF'; -$_MODULE['<{retailcrm}prestashop>retailcrm_93d03fe37ab3c6abc2a19dd8e41543bd'] = 'Línea de dirección 1'; -$_MODULE['<{retailcrm}prestashop>retailcrm_22fcffe02ab9eda5b769387122f2ddce'] = 'Línea de dirección 2'; -$_MODULE['<{retailcrm}prestashop>retailcrm_8bcdc441379cbf584638b0589a3f9adb'] = 'Código Postal'; -$_MODULE['<{retailcrm}prestashop>retailcrm_57d056ed0984166336b7879c2af3657f'] = 'Ciudad'; -$_MODULE['<{retailcrm}prestashop>retailcrm_bcc254b55c4a1babdf1dcb82c207506b'] = 'Teléfono'; -$_MODULE['<{retailcrm}prestashop>retailcrm_f0e1fc6f97d36cb80f29196e2662ffde'] = 'Teléfono móvil'; -$_MODULE['<{retailcrm}prestashop>retailcrm_7a1920d61156abc05a60135aefe8bc67'] = 'Por defecto'; -$_MODULE['<{retailcrm}prestashop>retailcrm_cc18dd262eff97c4dd4b56f750896adb'] = 'Estado predeterminado'; -$_MODULE['<{retailcrm}prestashop>retailcrm_a33b96f0ce0f1227132f1cb3cf1c9e88'] = 'Estado del pedido al exportar por lotes'; $_MODULE['<{retailcrm}prestashop>retailcrm_1bd340aeb42a5ee0318784c2cffed8a9'] = 'La versión seleccionada de la API no está disponible'; -$_MODULE['<{retailcrm}prestashop>retailcrm_c2784d9473fed62ae67fe8597011166a'] = 'Exportación manual de los pedidos'; -$_MODULE['<{retailcrm}prestashop>retailcrm_acfa058ec9e6e4745eddc0cae3f0f881'] = 'Identificador del pedido'; -$_MODULE['<{retailcrm}prestashop>retailcrm_91412465ea9169dfd901dd5e7c96dd99'] = 'Exportar'; -$_MODULE['<{retailcrm}prestashop>retailcrm_6bd461d1fc51b3294c6513cecc24758d'] = 'Los pedidos han sido cargados con éxito'; -$_MODULE['<{retailcrm}prestashop>retailcrm_3518f7b8d79f91da4c91772b4c46db94'] = 'No se han podido cargar algunos pedidos'; -$_MODULE['<{retailcrm}prestashop>retailcrm_9a7fc06b4b2359f1f26f75fbbe27a3e8'] = 'No todos los pedidos se han cargado con existo'; -$_MODULE['<{retailcrm}prestashop>retailcrm_917afe348e09163269225a89a825e634'] = 'Sincronización de carritos de compradores'; -$_MODULE['<{retailcrm}prestashop>retailcrm_d8e002d770b6f98af7b7ae9a0e5acfe9'] = 'Crear pedidos para carritos abandonados de compradores'; -$_MODULE['<{retailcrm}prestashop>retailcrm_35b5a9139a54caeb925556ceb2c38086'] = 'Estado del pedido para carritos abandonados de compradores'; $_MODULE['<{retailcrm}prestashop>retailcrm_b9c4e8fe56eabcc4c7913ebb2f8eb388'] = 'Estado del pedido para carritos abandonados no debe ser utilizado en otros ajustes'; -$_MODULE['<{retailcrm}prestashop>retailcrm_a0d135501a738c3c98de385dc28cda61'] = 'Cargar carritos abandonados'; +$_MODULE['<{retailcrm}prestashop>retailcrm_a52213fa61ecf700d1a6091d9769c9a8'] = 'Los tipos de entrega no deben repetirse en la matriz de entrega'; +$_MODULE['<{retailcrm}prestashop>retailcrm_f08acd4b354f4d5f4e531ca1972e4504'] = 'Los tipos de pago no deben repetirse en la matriz de pagos'; $_MODULE['<{retailcrm}prestashop>retailcrm_fd83e0ccb3e6312a62f888dd496dd0a5'] = 'Instantáneamente'; $_MODULE['<{retailcrm}prestashop>retailcrm_6c70ac5cc9fe8dff4f3bfe765dfe8798'] = 'Tras 1 minuto'; $_MODULE['<{retailcrm}prestashop>retailcrm_be76333ef50b23b6337d263fc87fe11a'] = 'Tras 5 minutos'; @@ -74,4 +20,149 @@ $_MODULE['<{retailcrm}prestashop>retailcrm_6e74495fad332de9a8207b5416de3cce'] = $_MODULE['<{retailcrm}prestashop>retailcrm_d5bb7c2cb1565fb1568924b01847b330'] = 'Tras 15 minutos'; $_MODULE['<{retailcrm}prestashop>retailcrm_9d3095e54f694bb41ef4a3e62ed90e7a'] = 'Tras 30 minutos'; $_MODULE['<{retailcrm}prestashop>retailcrm_dfb403fd86851c7d9f97706dff5a2327'] = 'Tras 45 minutos'; -$_MODULE['<{retailcrm}prestashop>retailcrm_4b5e6470d5d85448fcd89c828352d25e'] = 'Tras 1 hora'; \ No newline at end of file +$_MODULE['<{retailcrm}prestashop>retailcrm_4b5e6470d5d85448fcd89c828352d25e'] = 'Tras 1 hora'; +$_MODULE['<{retailcrm}prestashop>index_acbcb05077815fe6e9a7620b1d952462'] = 'retailCRM es un servicio para tiendas online, el cual ayuda a dejar de perder pedidos y así mejorar las ganancias de tu comercio online en todas las etapas del embudo de ventas. '; +$_MODULE['<{retailcrm}prestashop>index_09fe407e2337c1120980a2b4ee72ac6e'] = 'Tengo una cuenta en retailCRM'; +$_MODULE['<{retailcrm}prestashop>index_e81c4e4f2b7b93b481e13a8553c2ae1b'] = 'o'; +$_MODULE['<{retailcrm}prestashop>index_7a0a77aaf1bdd053fca69fa882c87df0'] = 'Obtenga retailCRM gratis'; +$_MODULE['<{retailcrm}prestashop>index_061b368c43f85d3fe2c7ccc842883a40'] = 'Configuración de la conexión'; +$_MODULE['<{retailcrm}prestashop>index_86ee1fa7924a67e54ac66e11c214e674'] = 'URL de retailCRM'; +$_MODULE['<{retailcrm}prestashop>index_656a6828d7ef1bb791e42087c4b5ee6e'] = 'Accesos API retailCRM'; +$_MODULE['<{retailcrm}prestashop>index_c9cc8cce247e49bae79f15173ce97354'] = 'Guardar'; +$_MODULE['<{retailcrm}prestashop>index_b5a7adde1af5c87d7fd797b6245c2a39'] = 'Descripción'; +$_MODULE['<{retailcrm}prestashop>index_9aa698f602b1e5694855cee73a683488'] = 'Contactos'; +$_MODULE['<{retailcrm}prestashop>index_764fa884e3fba8a3f40422aa1eadde23'] = 'Deja de perder pedidos'; +$_MODULE['<{retailcrm}prestashop>index_070f41773a46ce08231e11316603a099'] = 'Livechat es una forma activa de incitar al diálogo que inmediatamente te ayuda a recibir más pedidos en la tienda online.'; +$_MODULE['<{retailcrm}prestashop>index_87ef6941837db5b2370eb2c04a2f9e73'] = 'Chatbot, Facebook Messenger y WhatsApp en una misma ventana, te ayudan a no perder leads “frescos” que están a punto de hacer su pedido.'; +$_MODULE['<{retailcrm}prestashop>index_0e307da9d0c9ee16b47ef71c39f236a3'] = 'Emails de bienvenida te ayudarán a motivar a los clientes a realizar su primer pedido.'; +$_MODULE['<{retailcrm}prestashop>index_e83c1eb5df794bf22e69eb893e80fdd6'] = 'Motivar a concretar la compra'; +$_MODULE['<{retailcrm}prestashop>index_456329795d41ba012fc4fb3ed063d1fe'] = 'Upsells es la mejor opción para mejorar el ticket medio de manera automática.'; +$_MODULE['<{retailcrm}prestashop>index_4ab044d4168a44dbe50ecc01181e81ad'] = 'La gestión de carritos abandonados incrementa la cantidad de pedidos completados en la tienda online.'; +$_MODULE['<{retailcrm}prestashop>index_0f93ca5bf76e978aa9162e7fc53897ea'] = 'Gestiona los pedidos'; +$_MODULE['<{retailcrm}prestashop>index_3efd2720a5c93a8fe785084d925024ce'] = 'Con ayuda del CRM podrás recibir pedidos, distribuirlos entre tus empleados, controlar los estados y cerrarlos.'; +$_MODULE['<{retailcrm}prestashop>index_8823fb21e79cd316df376e80bb635329'] = 'Las notificaciones sobre el cambio de estado de cada pedido te ayuda a mantener a los clientes informados sobre sus pedidos.'; +$_MODULE['<{retailcrm}prestashop>index_2280ad04ce9fc872f86e839265f170a2'] = 'SalesApp es una aplicación para puntos de venta, la cual te ayudará a mejorar las ventas offline y a crear una base de clientes en un mismo sistema.'; +$_MODULE['<{retailcrm}prestashop>index_9f1ddb1081aee21a39383a5be24e6c78'] = 'La integración con el catálogo permite controlar el stock de tus productos, los precios y sus movimientos.'; +$_MODULE['<{retailcrm}prestashop>index_94d467d04e7d7b0c92df78f3de00fb20'] = 'Retén a tus actuales clientes'; +$_MODULE['<{retailcrm}prestashop>index_7f5875d2c134ba80d0ae9a5b51b2a805'] = 'CDP (Customer Data Platform) agrupa toda la información de tus clientes desde distintos canales y crea un perfil 360° de cada uno de ellos.'; +$_MODULE['<{retailcrm}prestashop>index_2708fc15917156fafb712217dcebdab5'] = 'La segmentación de la base de clientes te ayuda a hacer la comunicación con tus clientes más relevante y precisa.'; +$_MODULE['<{retailcrm}prestashop>index_d2d8dd2103f64290845f5635ce185270'] = 'Las campañas de mailing, SMS, WhatsApp y Facebook Messenger incrementarán la frecuencia de compra de tus clientes actuales.'; +$_MODULE['<{retailcrm}prestashop>index_b0e12648f812bedb79fe86c8f66cec8a'] = 'La regla “Productos de consumo regular” te ayuda a recordarle a tus clientes para que vuelvan a hacer la compra antes de que se les agoten sus productos.'; +$_MODULE['<{retailcrm}prestashop>index_02f67e7fb237e6fa9eb746fa0f721e96'] = 'Reanima a clientes inactivos'; +$_MODULE['<{retailcrm}prestashop>index_d5eb65bf655de38d7ac070bfdc328a74'] = 'Con ayuda de retargeting en el CRM podrás iniciar campañas utilizando los segmentos de tu base de clientes.'; +$_MODULE['<{retailcrm}prestashop>index_9f8f75ffd4d9e4f326576dfdc5570739'] = 'Las visitas con abandono te permiten registrar los productos que el cliente estaba viendo, así podrás proponerle completar su pedido.'; +$_MODULE['<{retailcrm}prestashop>index_f78799eda5746aebce16dfbc6c824b71'] = 'Las campañas para reactivar clientes te ayudarán a recuperar a aquellos que se habían perdido y así lograr que vuelvan a tu tienda online.'; +$_MODULE['<{retailcrm}prestashop>index_3b863a2b271ee8ebcceb0e79c51711b7'] = 'retailCRM mejorará la efectividad de todos tus canales de marketing'; +$_MODULE['<{retailcrm}prestashop>index_17b39a0118f63cf041abfb9d92d12414'] = 'LiveChat'; +$_MODULE['<{retailcrm}prestashop>index_ce8ae9da5b7cd6c3df2929543a9af92d'] = 'Email'; +$_MODULE['<{retailcrm}prestashop>index_31f803c0e3b881bf2fc62b248c8aaace'] = 'Facebook Messenger'; +$_MODULE['<{retailcrm}prestashop>index_4cecb21b44628b17c436739bf6301af2'] = 'SMS'; +$_MODULE['<{retailcrm}prestashop>index_2ca3885b024c5983c60a69c6af0ecd28'] = 'Retargeting'; +$_MODULE['<{retailcrm}prestashop>index_9d4f613c288a9cf21d59cc45f1d3dc2c'] = '¿Hay un trial del módulo?'; +$_MODULE['<{retailcrm}prestashop>index_4427ea757e4bf7ccea501cef064fdeaa'] = 'El módulo cuenta con una versión trial de 14 días en los cuales podrás trabajar con ayuda del módulo de retailCRM.'; +$_MODULE['<{retailcrm}prestashop>index_3b15dabe24b3ea13a55b08ca7abf1a94'] = '¿Qué es un usuario?'; +$_MODULE['<{retailcrm}prestashop>index_e4ed8cbeb5e181f15ad0192772ef76dd'] = 'Un usuario es la persona que trabajará con el módulo de retailCRM es como el representante de tu negocio o tu web. Cada usuario puede crear un perfil personal y tener su propio acceso al panel de la herramienta.'; +$_MODULE['<{retailcrm}prestashop>index_65991f2dd292e02d64d248906dfe0f40'] = '¿En qué idiomas está disponible el módulo?'; +$_MODULE['<{retailcrm}prestashop>index_fa224ef4c21784276945b35acdfe5605'] = 'El módulo de retailCRM está disponible en los siguientes idiomas:'; +$_MODULE['<{retailcrm}prestashop>index_cb5480c32e71778852b08ae1e8712775'] = 'Español'; +$_MODULE['<{retailcrm}prestashop>index_78463a384a5aa4fad5fa73e2f506ecfc'] = 'Inglés'; +$_MODULE['<{retailcrm}prestashop>index_deba6920e70615401385fe1fb5a379ec'] = 'Ruso'; +$_MODULE['<{retailcrm}prestashop>index_59064b34ae482528c8dbeb1b0214ee12'] = '¿Cuánto tiempo dura el trial?'; +$_MODULE['<{retailcrm}prestashop>index_26a6451f37fce97782d596fe7373e834'] = 'El tiempo de duración de la versión trial del módulo de retailCRM es de 14 días.'; +$_MODULE['<{retailcrm}prestashop>index_d8ff508a2fce371d8c36bd2bedbaecf6'] = '¿Se paga por usuario o se paga por cuenta?'; +$_MODULE['<{retailcrm}prestashop>index_5e2c0f6429d1304965531a63cf35dfd7'] = 'El pago se realiza por usuario, si se agrega a otro usuario dentro del sistema de retailCRM se realizaría el pago por dos usuarios. Cada usuario tiene derecho a una cuenta (web-chat y redes sociales). En caso de que un usuario necesite trabajar con más de una cuenta, es necesario ponerse en contacto con el equipo de retailCRM.'; +$_MODULE['<{retailcrm}prestashop>index_a833bd40df33cff491112eb9316fb050'] = '¿Cómo puedo realizar el pago?'; +$_MODULE['<{retailcrm}prestashop>index_4889fefd090fe608a9b5403d02e2e97f'] = 'Los métodos para realizar el pago son:'; +$_MODULE['<{retailcrm}prestashop>index_95428f32e5c696cf71baccb776bc5c15'] = 'Transferencia bancaria'; +$_MODULE['<{retailcrm}prestashop>index_e7f9e382dc50889098cbe56f2554c77b'] = 'Tarjeta bancaria'; +$_MODULE['<{retailcrm}prestashop>index_7088f1d1d9c91d8b75e9882ffd78540c'] = 'Datos de contacto'; +$_MODULE['<{retailcrm}prestashop>index_50f158e2507321f1a5b6f8fb9e350818'] = 'Escríbenos en caso de preguntas o dudas'; +$_MODULE['<{retailcrm}prestashop>settings_c2cc7082a89c1ad6631a2f66af5f00c0'] = 'Conexión'; +$_MODULE['<{retailcrm}prestashop>settings_52a13123e134b8b72b6299bc14a36aad'] = 'Daemon Collector'; +$_MODULE['<{retailcrm}prestashop>settings_6bcde6286f8d1b76063ee52104a240cf'] = 'Carritos abandonados'; +$_MODULE['<{retailcrm}prestashop>settings_065ab3a28ca4f16f55f103adc7d0226f'] = 'Los métodos del envío'; +$_MODULE['<{retailcrm}prestashop>settings_33af8066d3c83110d4bd897f687cedd2'] = 'Los estados de pedidos'; +$_MODULE['<{retailcrm}prestashop>settings_bab959acc06bb03897b294fbb892be6b'] = 'Los métodos de pago'; +$_MODULE['<{retailcrm}prestashop>settings_7a1920d61156abc05a60135aefe8bc67'] = 'Por defecto'; +$_MODULE['<{retailcrm}prestashop>settings_20cacc01d0de8bc6e9c9846f477e886b'] = 'Subir pedidos'; +$_MODULE['<{retailcrm}prestashop>settings_71098155ccc0a0d6e0b501fbee37e7a9'] = 'LiveChat'; +$_MODULE['<{retailcrm}prestashop>settings_061b368c43f85d3fe2c7ccc842883a40'] = 'La configuración de la conexión'; +$_MODULE['<{retailcrm}prestashop>settings_86ee1fa7924a67e54ac66e11c214e674'] = 'retailCRM URL'; +$_MODULE['<{retailcrm}prestashop>settings_656a6828d7ef1bb791e42087c4b5ee6e'] = 'API key'; +$_MODULE['<{retailcrm}prestashop>settings_f8d7c52aa84f358caedb96fda86809da'] = 'Permitir el soporte a clientes corporativos'; +$_MODULE['<{retailcrm}prestashop>settings_4d3d769b812b6faa6b76e1a8abaece2d'] = 'Active'; +$_MODULE['<{retailcrm}prestashop>settings_f75d8fa5c89351544d372cf90528ccf2'] = 'Clave de la página web'; +$_MODULE['<{retailcrm}prestashop>settings_917afe348e09163269225a89a825e634'] = 'Sincronización de carritos de compradores'; +$_MODULE['<{retailcrm}prestashop>settings_d8e002d770b6f98af7b7ae9a0e5acfe9'] = 'Crear pedidos para carritos abandonados de compradores'; +$_MODULE['<{retailcrm}prestashop>settings_35b5a9139a54caeb925556ceb2c38086'] = 'Estado del pedido para carritos abandonados de compradores'; +$_MODULE['<{retailcrm}prestashop>settings_9b9cf9f8778f69b4c6cf37e66f886be8'] = 'Elige el estado'; +$_MODULE['<{retailcrm}prestashop>settings_a0d135501a738c3c98de385dc28cda61'] = 'Cargar carritos abandonados'; +$_MODULE['<{retailcrm}prestashop>settings_27096e1243f98e1b3300f57ff1c76456'] = 'Elige la demora'; +$_MODULE['<{retailcrm}prestashop>settings_5b385947acf10ac0c5521161ce96aaa7'] = 'Elige la entrega'; +$_MODULE['<{retailcrm}prestashop>settings_7dcc1208fa03381346955c6732d9ea85'] = 'Elige el tipo'; +$_MODULE['<{retailcrm}prestashop>settings_acfa058ec9e6e4745eddc0cae3f0f881'] = 'Identificador del pedido'; +$_MODULE['<{retailcrm}prestashop>settings_91412465ea9169dfd901dd5e7c96dd99'] = 'Exportar'; +$_MODULE['<{retailcrm}prestashop>settings_c9cc8cce247e49bae79f15173ce97354'] = 'Guardar'; +$_MODULE['<{retailcrm}prestashop>settings_4f18e3f1c9941a6ec5a38bc716c521b4'] = 'Código que necesita insertar en la web'; +$_MODULE['<{retailcrm}prestashop>retailcrm_c888438d14855d7d96a2724ee9c306bd'] = 'Los ajustes están actualizados'; +$_MODULE['<{retailcrm}prestashop>retailcrm_51af428aa0dcceb5230acb267ecb91c5'] = 'La configuración de la conexión'; +$_MODULE['<{retailcrm}prestashop>retailcrm_7d1d9327371097419bdf83ffa671d2f2'] = 'Versión de la API'; +$_MODULE['<{retailcrm}prestashop>retailcrm_4cbd5dbeeef7392e50358b1bc00dd592'] = 'URL CRM'; +$_MODULE['<{retailcrm}prestashop>retailcrm_7f775042e08eddee6bbfd8fbe0add4a3'] = 'La clave API'; +$_MODULE['<{retailcrm}prestashop>retailcrm_c9cc8cce247e49bae79f15173ce97354'] = 'Guardar'; +$_MODULE['<{retailcrm}prestashop>retailcrm_52a13123e134b8b72b6299bc14a36aad'] = 'Daemon Collector'; +$_MODULE['<{retailcrm}prestashop>retailcrm_a13367a8e2a3f3bf4f3409079e3fdf87'] = 'Active'; +$_MODULE['<{retailcrm}prestashop>retailcrm_f75d8fa5c89351544d372cf90528ccf2'] = 'Clave de la página web'; +$_MODULE['<{retailcrm}prestashop>retailcrm_917afe348e09163269225a89a825e634'] = 'Sincronización de carritos de compradores'; +$_MODULE['<{retailcrm}prestashop>retailcrm_d8e002d770b6f98af7b7ae9a0e5acfe9'] = 'Crear pedidos para carritos abandonados de compradores'; +$_MODULE['<{retailcrm}prestashop>retailcrm_35b5a9139a54caeb925556ceb2c38086'] = 'Estado del pedido para carritos abandonados de compradores'; +$_MODULE['<{retailcrm}prestashop>retailcrm_a0d135501a738c3c98de385dc28cda61'] = 'Cargar carritos abandonados'; +$_MODULE['<{retailcrm}prestashop>retailcrm_065ab3a28ca4f16f55f103adc7d0226f'] = 'Los métodos del envío'; +$_MODULE['<{retailcrm}prestashop>retailcrm_33af8066d3c83110d4bd897f687cedd2'] = 'Los estados de pedidos'; +$_MODULE['<{retailcrm}prestashop>retailcrm_bab959acc06bb03897b294fbb892be6b'] = 'Los métodos de pago'; +$_MODULE['<{retailcrm}prestashop>retailcrm_7a1920d61156abc05a60135aefe8bc67'] = 'Por defecto'; +$_MODULE['<{retailcrm}prestashop>retailcrm_6310f29293c902c64db619c29179d99a'] = 'Los método de envío'; +$_MODULE['<{retailcrm}prestashop>retailcrm_4dbcb38bbbff5d4a402f2575c57a35e6'] = 'El tipo de pago'; +$_MODULE['<{retailcrm}prestashop>retailcrm_cc18dd262eff97c4dd4b56f750896adb'] = 'Estado predeterminado'; +$_MODULE['<{retailcrm}prestashop>retailcrm_a33b96f0ce0f1227132f1cb3cf1c9e88'] = 'Estado del pedido al exportar por lotes'; +$_MODULE['<{retailcrm}prestashop>retailcrm_630f6dc397fe74e52d5189e2c80f282b'] = 'Volver a la lista'; +$_MODULE['<{retailcrm}prestashop>retailcrm_c2784d9473fed62ae67fe8597011166a'] = 'Exportación manual de los pedidos'; +$_MODULE['<{retailcrm}prestashop>retailcrm_acfa058ec9e6e4745eddc0cae3f0f881'] = 'Identificador del pedido'; +$_MODULE['<{retailcrm}prestashop>retailcrm_91412465ea9169dfd901dd5e7c96dd99'] = 'Exportar'; +$_MODULE['<{retailcrm}prestashop>retailcrm_b9b2d9f66d0112f3aae7dbdbd4e22a43'] = 'La dirección del CRM es incorrecta o está vacía'; +$_MODULE['<{retailcrm}prestashop>retailcrm_942010ef43f3fec28741f62a0d9ff29c'] = 'La clave CRM es incorrecta o está vacía'; +$_MODULE['<{retailcrm}prestashop>retailcrm_1bd340aeb42a5ee0318784c2cffed8a9'] = 'La versión seleccionada de la API no está disponible'; +$_MODULE['<{retailcrm}prestashop>retailcrm_b9c4e8fe56eabcc4c7913ebb2f8eb388'] = 'Estado del pedido para carritos abandonados no debe ser utilizado en otros ajustes'; +$_MODULE['<{retailcrm}prestashop>settings_c2cc7082a89c1ad6631a2f66af5f00c0'] = 'Conexión'; +$_MODULE['<{retailcrm}prestashop>settings_52a13123e134b8b72b6299bc14a36aad'] = 'Daemon Collector'; +$_MODULE['<{retailcrm}prestashop>settings_6bcde6286f8d1b76063ee52104a240cf'] = 'Carritos abandonados'; +$_MODULE['<{retailcrm}prestashop>settings_065ab3a28ca4f16f55f103adc7d0226f'] = 'Los métodos del envío'; +$_MODULE['<{retailcrm}prestashop>settings_33af8066d3c83110d4bd897f687cedd2'] = 'Los estados de pedidos'; +$_MODULE['<{retailcrm}prestashop>settings_bab959acc06bb03897b294fbb892be6b'] = 'Los métodos de pago'; +$_MODULE['<{retailcrm}prestashop>settings_7a1920d61156abc05a60135aefe8bc67'] = 'Por defecto'; +$_MODULE['<{retailcrm}prestashop>settings_061b368c43f85d3fe2c7ccc842883a40'] = 'La configuración de la conexión'; +$_MODULE['<{retailcrm}prestashop>settings_86ee1fa7924a67e54ac66e11c214e674'] = 'retailCRM URL'; +$_MODULE['<{retailcrm}prestashop>settings_656a6828d7ef1bb791e42087c4b5ee6e'] = 'API key'; +$_MODULE['<{retailcrm}prestashop>settings_4d3d769b812b6faa6b76e1a8abaece2d'] = 'Active'; +$_MODULE['<{retailcrm}prestashop>settings_f75d8fa5c89351544d372cf90528ccf2'] = 'Clave de la página web'; +$_MODULE['<{retailcrm}prestashop>settings_917afe348e09163269225a89a825e634'] = 'Sincronización de carritos de compradores'; +$_MODULE['<{retailcrm}prestashop>settings_d8e002d770b6f98af7b7ae9a0e5acfe9'] = 'Crear pedidos para carritos abandonados de compradores'; +$_MODULE['<{retailcrm}prestashop>settings_35b5a9139a54caeb925556ceb2c38086'] = 'Estado del pedido para carritos abandonados de compradores'; +$_MODULE['<{retailcrm}prestashop>settings_a0d135501a738c3c98de385dc28cda61'] = 'Cargar carritos abandonados'; +$_MODULE['<{retailcrm}prestashop>settings_acfa058ec9e6e4745eddc0cae3f0f881'] = 'Identificador del pedido'; +$_MODULE['<{retailcrm}prestashop>settings_91412465ea9169dfd901dd5e7c96dd99'] = 'Exportar'; +$_MODULE['<{retailcrm}prestashop>settings_c9cc8cce247e49bae79f15173ce97354'] = 'Guardar'; +$_MODULE['<{retailcrm}prestashop>retailcrm_4aa0e53499eeb8383a6330c96b9ed7c3'] = 'Módulo de integración para retailCRM'; +$_MODULE['<{retailcrm}prestashop>settings_20cacc01d0de8bc6e9c9846f477e886b'] = 'Subir pedidos'; +$_MODULE['<{retailcrm}prestashop>settings_71098155ccc0a0d6e0b501fbee37e7a9'] = 'LiveChat'; +$_MODULE['<{retailcrm}prestashop>settings_9b9cf9f8778f69b4c6cf37e66f886be8'] = 'Elige el estado'; +$_MODULE['<{retailcrm}prestashop>settings_27096e1243f98e1b3300f57ff1c76456'] = 'Elige la demora'; +$_MODULE['<{retailcrm}prestashop>settings_5b385947acf10ac0c5521161ce96aaa7'] = 'Elige la entrega'; +$_MODULE['<{retailcrm}prestashop>settings_7dcc1208fa03381346955c6732d9ea85'] = 'Elige el tipo'; +$_MODULE['<{retailcrm}prestashop>settings_4f18e3f1c9941a6ec5a38bc716c521b4'] = 'Código que necesita insertar en la web'; +$_MODULE['<{retailcrm}prestashop>index_84cff42a89e3866f0ed4f2979d340895'] = 'Solución para convertir más oportunidades de venta a través del chat web y Facebook Messenger, disponible 24/7, incluso cuando no estás en línea.'; +$_MODULE['<{retailcrm}prestashop>index_50f158e2507321f1a5b6f8fb9e350818'] = 'Escríbenos en caso de preguntas o dudas'; +$_MODULE['<{retailcrm}prestashop>retailcrm_39e90036af004a005ccbccbe9a9c19c2'] = 'Los estados de orden no deben repetirse en la matriz de estados'; +$_MODULE['<{retailcrm}prestashop>settings_8ffa3281a35a0d80fef2cac0fa680523'] = 'Habilitar la carga del historial'; +$_MODULE['<{retailcrm}prestashop>settings_80e47b10a89a3f22e0def96577ee8b25'] = 'Recibir las existencias del retailCRM'; +$_MODULE['<{retailcrm}prestashop>settings_7d320ac32b103449d18b51f47e7b1329'] = 'Activar solo si está habilitada la opción \"Clientes corporativos\" en retailCRM'; diff --git a/retailcrm/translations/index.php b/retailcrm/translations/index.php old mode 100755 new mode 100644 diff --git a/retailcrm/translations/ru.php b/retailcrm/translations/ru.php old mode 100755 new mode 100644 index 2e7be52..13fc820 --- a/retailcrm/translations/ru.php +++ b/retailcrm/translations/ru.php @@ -2,71 +2,17 @@ global $_MODULE; $_MODULE = array(); -$_MODULE['<{retailcrm}prestashop>retailcrm_7d1d9327371097419bdf83ffa671d2f2'] = 'Версия API'; -$_MODULE['<{retailcrm}prestashop>retailcrm_463dc31aa1a0b6e871b1a9fed8e9860a'] = 'RetailCRM'; -$_MODULE['<{retailcrm}prestashop>retailcrm_30de6237576b9a24f6fc599c22a35a4b'] = 'Модуль интеграции с RetailCRM'; +$_MODULE['<{retailcrm}prestashop>retailcrm_b0edd77b179acca4cb3572c4393db254'] = 'retailCRM'; +$_MODULE['<{retailcrm}prestashop>retailcrm_4aa0e53499eeb8383a6330c96b9ed7c3'] = 'Интеграционный модуль для retailCRM'; $_MODULE['<{retailcrm}prestashop>retailcrm_876f23178c29dc2552c0b48bf23cd9bd'] = 'Вы уверены, что хотите удалить модуль?'; +$_MODULE['<{retailcrm}prestashop>retailcrm_6bd461d1fc51b3294c6513cecc24758d'] = 'Все заказы успешно загружены'; +$_MODULE['<{retailcrm}prestashop>retailcrm_9a7fc06b4b2359f1f26f75fbbe27a3e8'] = 'Не все заказы загружены успешно'; $_MODULE['<{retailcrm}prestashop>retailcrm_b9b2d9f66d0112f3aae7dbdbd4e22a43'] = 'Некорректный или пустой адрес CRM'; $_MODULE['<{retailcrm}prestashop>retailcrm_942010ef43f3fec28741f62a0d9ff29c'] = 'Некорректный или пустой ключ CRM'; -$_MODULE['<{retailcrm}prestashop>retailcrm_fba05687b61bc936d1a9a92371ba8bcf'] = 'Внимание! Часовой пояс в CRM должен совпадать с часовым поясом в магазине, настроки часового пояса CRM можно задать по адресу:'; -$_MODULE['<{retailcrm}prestashop>retailcrm_5effd5157947e8ba4a08883f198b2e31'] = 'Неверный или пустой адрес CRM'; -$_MODULE['<{retailcrm}prestashop>retailcrm_576300f5b6faeb746bb6d034d98e7afd'] = 'Неверный или пустой API ключ'; -$_MODULE['<{retailcrm}prestashop>retailcrm_c888438d14855d7d96a2724ee9c306bd'] = 'Настройки обновлены'; -$_MODULE['<{retailcrm}prestashop>retailcrm_51af428aa0dcceb5230acb267ecb91c5'] = 'Настройка соединения'; -$_MODULE['<{retailcrm}prestashop>retailcrm_4cbd5dbeeef7392e50358b1bc00dd592'] = 'URL адрес CRM'; -$_MODULE['<{retailcrm}prestashop>retailcrm_7f775042e08eddee6bbfd8fbe0add4a3'] = 'API ключ'; -$_MODULE['<{retailcrm}prestashop>retailcrm_52a13123e134b8b72b6299bc14a36aad'] = 'Daemon Collector'; -$_MODULE['<{retailcrm}prestashop>retailcrm_a13367a8e2a3f3bf4f3409079e3fdf87'] = 'Активировать'; -$_MODULE['<{retailcrm}prestashop>retailcrm_f75d8fa5c89351544d372cf90528ccf2'] = 'Ключ сайта'; -$_MODULE['<{retailcrm}prestashop>retailcrm_c9cc8cce247e49bae79f15173ce97354'] = 'Сохранить'; -$_MODULE['<{retailcrm}prestashop>retailcrm_065ab3a28ca4f16f55f103adc7d0226f'] = 'Способы доставки'; -$_MODULE['<{retailcrm}prestashop>retailcrm_33af8066d3c83110d4bd897f687cedd2'] = 'Статусы заказов'; -$_MODULE['<{retailcrm}prestashop>retailcrm_bab959acc06bb03897b294fbb892be6b'] = 'Способы оплаты'; -$_MODULE['<{retailcrm}prestashop>retailcrm_6310f29293c902c64db619c29179d99a'] = 'Способ доставки'; -$_MODULE['<{retailcrm}prestashop>retailcrm_4dbcb38bbbff5d4a402f2575c57a35e6'] = 'Тип оплаты'; -$_MODULE['<{retailcrm}prestashop>retailcrm_dd7bf230fde8d4836917806aff6a6b27'] = 'Адрес'; -$_MODULE['<{retailcrm}prestashop>retailcrm_630f6dc397fe74e52d5189e2c80f282b'] = 'Вернуться к списку'; -$_MODULE['<{retailcrm}prestashop>retailcrm_5c1cf6cfec2dad86c8ca5286a0294516'] = 'Имя'; -$_MODULE['<{retailcrm}prestashop>retailcrm_c695cfe527a6fcd680114851b86b7555'] = 'Фамилия'; -$_MODULE['<{retailcrm}prestashop>retailcrm_f9dd946cc89c1f3b41a0edbe0f36931d'] = 'Телефон'; -$_MODULE['<{retailcrm}prestashop>retailcrm_61a649a33f2869e5e35fbb7aff3a80d9'] = 'Email'; -$_MODULE['<{retailcrm}prestashop>retailcrm_2664f03ac6b8bb9eee4287720e407db3'] = 'Адрес'; -$_MODULE['<{retailcrm}prestashop>retailcrm_6ddc09dc456001d9854e9fe670374eb2'] = 'Страна'; -$_MODULE['<{retailcrm}prestashop>retailcrm_69aede266809f89b89fe70681f6a129f'] = 'Область/Край/Республика'; -$_MODULE['<{retailcrm}prestashop>retailcrm_859214628431995197c0558f7b5f8ffc'] = 'Город'; -$_MODULE['<{retailcrm}prestashop>retailcrm_4348f938bbddd8475e967ccb47ecb234'] = 'Почтовый индекс'; -$_MODULE['<{retailcrm}prestashop>retailcrm_78fce82336bbbdca7f6da7564b8f9325'] = 'Улица'; -$_MODULE['<{retailcrm}prestashop>retailcrm_71a6834884666147c0334f0c40bc7295'] = 'Дом/Строение'; -$_MODULE['<{retailcrm}prestashop>retailcrm_f88a77e3d68d251c3dc4008c327b5a0c'] = 'Квартира'; -$_MODULE['<{retailcrm}prestashop>retailcrm_d977f846d110fcb7f71c6f97330c9d10'] = 'Код домофона'; -$_MODULE['<{retailcrm}prestashop>retailcrm_56c1e354d36beb85b0d881c5b2e24cbe'] = 'Этаж'; -$_MODULE['<{retailcrm}prestashop>retailcrm_4d34f53389ed7f28ca91fc31ea360a66'] = 'Корпус'; -$_MODULE['<{retailcrm}prestashop>retailcrm_49354b452ec305136a56fe7731834156'] = 'Дом/Строение'; -$_MODULE['<{retailcrm}prestashop>retailcrm_04176f095283bc729f1e3926967e7034'] = 'Имя'; -$_MODULE['<{retailcrm}prestashop>retailcrm_dff4bf10409100d989495c6d5486035e'] = 'Фамилия'; -$_MODULE['<{retailcrm}prestashop>retailcrm_1c76cbfe21c6f44c1d1e59d54f3e4420'] = 'Компания'; -$_MODULE['<{retailcrm}prestashop>retailcrm_1aadcc03a9dbba84a3c5a5cbfde8a162'] = 'ИНН'; -$_MODULE['<{retailcrm}prestashop>retailcrm_93d03fe37ab3c6abc2a19dd8e41543bd'] = 'Адрес строка 1'; -$_MODULE['<{retailcrm}prestashop>retailcrm_22fcffe02ab9eda5b769387122f2ddce'] = 'Адрес строка 2'; -$_MODULE['<{retailcrm}prestashop>retailcrm_8bcdc441379cbf584638b0589a3f9adb'] = 'Почтовый индекс'; -$_MODULE['<{retailcrm}prestashop>retailcrm_57d056ed0984166336b7879c2af3657f'] = 'Город'; -$_MODULE['<{retailcrm}prestashop>retailcrm_bcc254b55c4a1babdf1dcb82c207506b'] = 'Телефон'; -$_MODULE['<{retailcrm}prestashop>retailcrm_f0e1fc6f97d36cb80f29196e2662ffde'] = 'Мобильный телефон'; -$_MODULE['<{retailcrm}prestashop>retailcrm_7a1920d61156abc05a60135aefe8bc67'] = 'По умолчанию'; -$_MODULE['<{retailcrm}prestashop>retailcrm_cc18dd262eff97c4dd4b56f750896adb'] = 'Статус по умолчанию'; -$_MODULE['<{retailcrm}prestashop>retailcrm_a33b96f0ce0f1227132f1cb3cf1c9e88'] = 'Статус заказа при пакетной выгрузке '; $_MODULE['<{retailcrm}prestashop>retailcrm_1bd340aeb42a5ee0318784c2cffed8a9'] = 'Выбранная версия API недоступна'; -$_MODULE['<{retailcrm}prestashop>retailcrm_c2784d9473fed62ae67fe8597011166a'] = 'Ручная выгрузка заказов'; -$_MODULE['<{retailcrm}prestashop>retailcrm_acfa058ec9e6e4745eddc0cae3f0f881'] = 'Идентификаторы заказов'; -$_MODULE['<{retailcrm}prestashop>retailcrm_91412465ea9169dfd901dd5e7c96dd99'] = 'Выгрузить'; -$_MODULE['<{retailcrm}prestashop>retailcrm_6bd461d1fc51b3294c6513cecc24758d'] = 'Все заказы успешно загружены'; -$_MODULE['<{retailcrm}prestashop>retailcrm_3518f7b8d79f91da4c91772b4c46db94'] = 'Некоторые заказы не удалось загрузить'; -$_MODULE['<{retailcrm}prestashop>retailcrm_9a7fc06b4b2359f1f26f75fbbe27a3e8'] = 'Не все заказы загружены успешно'; -$_MODULE['<{retailcrm}prestashop>retailcrm_917afe348e09163269225a89a825e634'] = 'Синхронизация корзин покупателей'; -$_MODULE['<{retailcrm}prestashop>retailcrm_d8e002d770b6f98af7b7ae9a0e5acfe9'] = 'Создавать заказы для брошенных корзин покупателей'; -$_MODULE['<{retailcrm}prestashop>retailcrm_35b5a9139a54caeb925556ceb2c38086'] = 'Статус заказа для брошенных корзин покупателей'; $_MODULE['<{retailcrm}prestashop>retailcrm_b9c4e8fe56eabcc4c7913ebb2f8eb388'] = 'Статус заказа для брошенных корзин не должен использоваться в других настройках'; -$_MODULE['<{retailcrm}prestashop>retailcrm_a0d135501a738c3c98de385dc28cda61'] = 'Выгружать брошенные корзины'; +$_MODULE['<{retailcrm}prestashop>retailcrm_a52213fa61ecf700d1a6091d9769c9a8'] = 'Типы доставок не должны повторяться в матрице соответствий типов доставок'; +$_MODULE['<{retailcrm}prestashop>retailcrm_f08acd4b354f4d5f4e531ca1972e4504'] = 'Способы оплат не должны повторяться в матрице соответствий способов оплат'; $_MODULE['<{retailcrm}prestashop>retailcrm_fd83e0ccb3e6312a62f888dd496dd0a5'] = 'Мгновенно'; $_MODULE['<{retailcrm}prestashop>retailcrm_6c70ac5cc9fe8dff4f3bfe765dfe8798'] = 'Через 1 минуту'; $_MODULE['<{retailcrm}prestashop>retailcrm_be76333ef50b23b6337d263fc87fe11a'] = 'Через 5 минут'; @@ -74,4 +20,106 @@ $_MODULE['<{retailcrm}prestashop>retailcrm_6e74495fad332de9a8207b5416de3cce'] = $_MODULE['<{retailcrm}prestashop>retailcrm_d5bb7c2cb1565fb1568924b01847b330'] = 'Через 15 минут'; $_MODULE['<{retailcrm}prestashop>retailcrm_9d3095e54f694bb41ef4a3e62ed90e7a'] = 'Через 30 минут'; $_MODULE['<{retailcrm}prestashop>retailcrm_dfb403fd86851c7d9f97706dff5a2327'] = 'Через 45 минут'; -$_MODULE['<{retailcrm}prestashop>retailcrm_4b5e6470d5d85448fcd89c828352d25e'] = 'Через 1 час'; \ No newline at end of file +$_MODULE['<{retailcrm}prestashop>retailcrm_4b5e6470d5d85448fcd89c828352d25e'] = 'Через 1 час'; +$_MODULE['<{retailcrm}prestashop>index_acbcb05077815fe6e9a7620b1d952462'] = 'retailCRM — сервис для интернет магазинов, который поможет перестать терять заказы и увеличить доход на всех этапах воронки.'; +$_MODULE['<{retailcrm}prestashop>index_09fe407e2337c1120980a2b4ee72ac6e'] = 'У меня уже есть аккаунт retailCRM'; +$_MODULE['<{retailcrm}prestashop>index_e81c4e4f2b7b93b481e13a8553c2ae1b'] = 'или'; +$_MODULE['<{retailcrm}prestashop>index_7a0a77aaf1bdd053fca69fa882c87df0'] = 'Получить retailCRM бесплатно'; +$_MODULE['<{retailcrm}prestashop>index_061b368c43f85d3fe2c7ccc842883a40'] = 'Настройка соединения'; +$_MODULE['<{retailcrm}prestashop>index_86ee1fa7924a67e54ac66e11c214e674'] = 'URL адрес retailCRM'; +$_MODULE['<{retailcrm}prestashop>index_656a6828d7ef1bb791e42087c4b5ee6e'] = 'API ключ retailCRM'; +$_MODULE['<{retailcrm}prestashop>index_c9cc8cce247e49bae79f15173ce97354'] = 'Сохранить'; +$_MODULE['<{retailcrm}prestashop>index_b5a7adde1af5c87d7fd797b6245c2a39'] = 'Описание'; +$_MODULE['<{retailcrm}prestashop>index_9aa698f602b1e5694855cee73a683488'] = 'Контакты'; +$_MODULE['<{retailcrm}prestashop>index_764fa884e3fba8a3f40422aa1eadde23'] = 'Перестаньте терять лиды:'; +$_MODULE['<{retailcrm}prestashop>index_070f41773a46ce08231e11316603a099'] = 'LiveChat с активным вовлечением, поможет получить больше заказов с сайта'; +$_MODULE['<{retailcrm}prestashop>index_87ef6941837db5b2370eb2c04a2f9e73'] = 'Чат-боты и единый Inbox для Facebook Messengers и WhatsApp помогут перестать терять горячих лидов, готовых вот-вот купить'; +$_MODULE['<{retailcrm}prestashop>index_0e307da9d0c9ee16b47ef71c39f236a3'] = 'Welcome-цепочки прогреют ваши лиды и подтолкнут их к первой покупке.'; +$_MODULE['<{retailcrm}prestashop>index_e83c1eb5df794bf22e69eb893e80fdd6'] = 'Доводите заказы до оплаты:'; +$_MODULE['<{retailcrm}prestashop>index_456329795d41ba012fc4fb3ed063d1fe'] = 'Допродажи увеличат средний чек ваших заказов в автоматическом режиме'; +$_MODULE['<{retailcrm}prestashop>index_4ab044d4168a44dbe50ecc01181e81ad'] = 'Сценарий Брошенная корзина повысит количество оплаченных заказов'; +$_MODULE['<{retailcrm}prestashop>index_0f93ca5bf76e978aa9162e7fc53897ea'] = 'Управляйте выполнением заказа:'; +$_MODULE['<{retailcrm}prestashop>index_3efd2720a5c93a8fe785084d925024ce'] = 'CRM-система поможет получать заказы, распределять их между сотрудниками, управлять их статусами и выполнять их'; +$_MODULE['<{retailcrm}prestashop>index_8823fb21e79cd316df376e80bb635329'] = 'Уведомления о статусе заказа помогут автоматически информировать клиента о том, что происходит с его заказом'; +$_MODULE['<{retailcrm}prestashop>index_2280ad04ce9fc872f86e839265f170a2'] = 'SalesApp — приложение для розничных точек, которое поможет повысить продажи в офлайне и собрать клиенсткую базу в единой системе'; +$_MODULE['<{retailcrm}prestashop>index_9f1ddb1081aee21a39383a5be24e6c78'] = 'Интеграция с каталогом поможет учитывать остатки, цены и местонахождение товаров'; +$_MODULE['<{retailcrm}prestashop>index_94d467d04e7d7b0c92df78f3de00fb20'] = 'Удерживайте ваших текущих клиентов:'; +$_MODULE['<{retailcrm}prestashop>index_7f5875d2c134ba80d0ae9a5b51b2a805'] = 'CDP объединит данные ваших клиентов из разных источников и построит профиль 360°'; +$_MODULE['<{retailcrm}prestashop>index_2708fc15917156fafb712217dcebdab5'] = 'Сегменты помогут разделить вашу базу на небольшие группы, чтобы сделать ваши коммуникации релевантнее'; +$_MODULE['<{retailcrm}prestashop>index_d2d8dd2103f64290845f5635ce185270'] = 'Рассылки в Email, SMS, WhatsApp и Facebook Messenger увеличат частоту покупок вашей клиентской базы'; +$_MODULE['<{retailcrm}prestashop>index_b0e12648f812bedb79fe86c8f66cec8a'] = 'Сценарий \"Товары расходники\" поможет автоматически напоминать о необходимости пополнить запасы'; +$_MODULE['<{retailcrm}prestashop>index_02f67e7fb237e6fa9eb746fa0f721e96'] = 'Возвращайте ушедших клиентов:'; +$_MODULE['<{retailcrm}prestashop>index_d5eb65bf655de38d7ac070bfdc328a74'] = 'CRM-ремаркетинг поможет запускать рекламу, используя сегменты из retailCRM'; +$_MODULE['<{retailcrm}prestashop>index_9f8f75ffd4d9e4f326576dfdc5570739'] = 'Брошенный просмотр сохранит товары, которые клиент смотрел на сайте и предложит оплатить их'; +$_MODULE['<{retailcrm}prestashop>index_f78799eda5746aebce16dfbc6c824b71'] = 'Реактивационные кампании будут возвращать потерянных клиентов обратно в ваш магазин'; +$_MODULE['<{retailcrm}prestashop>index_3b863a2b271ee8ebcceb0e79c51711b7'] = 'retailCRM повысит эффективность всех ваших маркетинговых каналов:'; +$_MODULE['<{retailcrm}prestashop>index_17b39a0118f63cf041abfb9d92d12414'] = 'LiveChat'; +$_MODULE['<{retailcrm}prestashop>index_ce8ae9da5b7cd6c3df2929543a9af92d'] = 'Email'; +$_MODULE['<{retailcrm}prestashop>index_31f803c0e3b881bf2fc62b248c8aaace'] = 'Facebook Messenger'; +$_MODULE['<{retailcrm}prestashop>index_4cecb21b44628b17c436739bf6301af2'] = 'SMS'; +$_MODULE['<{retailcrm}prestashop>index_2ca3885b024c5983c60a69c6af0ecd28'] = 'Ретаргетинг'; +$_MODULE['<{retailcrm}prestashop>index_9d4f613c288a9cf21d59cc45f1d3dc2c'] = 'Существует ли ознакомительный период?'; +$_MODULE['<{retailcrm}prestashop>index_4427ea757e4bf7ccea501cef064fdeaa'] = 'Да. Существует 14-дневный ознакомительный период в рамках которого Вы можете ознакомиться с возможностями retailCRM.'; +$_MODULE['<{retailcrm}prestashop>index_3b15dabe24b3ea13a55b08ca7abf1a94'] = 'Кто такой пользователь?'; +$_MODULE['<{retailcrm}prestashop>index_e4ed8cbeb5e181f15ad0192772ef76dd'] = 'Пользователь - это сотрудник, который имеет доступ к retailCRM в качестве представителя Вашего бизнеса или в качестве пользователя Вашего веб-сайта. Каждый пользователь имеет свой доступ к аккаунту retailCRM.'; +$_MODULE['<{retailcrm}prestashop>index_65991f2dd292e02d64d248906dfe0f40'] = 'Какие языки доступны в модуле?'; +$_MODULE['<{retailcrm}prestashop>index_fa224ef4c21784276945b35acdfe5605'] = 'Модуль retailCRM переведён на следующие языки:'; +$_MODULE['<{retailcrm}prestashop>index_cb5480c32e71778852b08ae1e8712775'] = 'Испанский'; +$_MODULE['<{retailcrm}prestashop>index_78463a384a5aa4fad5fa73e2f506ecfc'] = 'Английский'; +$_MODULE['<{retailcrm}prestashop>index_deba6920e70615401385fe1fb5a379ec'] = 'Русский'; +$_MODULE['<{retailcrm}prestashop>index_59064b34ae482528c8dbeb1b0214ee12'] = 'Как долго длится ознакомительный режим?'; +$_MODULE['<{retailcrm}prestashop>index_26a6451f37fce97782d596fe7373e834'] = 'Длительность пробного режима составляет 14 дней'; +$_MODULE['<{retailcrm}prestashop>index_d8ff508a2fce371d8c36bd2bedbaecf6'] = 'Оплата производится за пользователя или за аккаунт?'; +$_MODULE['<{retailcrm}prestashop>index_5e2c0f6429d1304965531a63cf35dfd7'] = 'Оплата осуществляется за каждого пользователя. Если в систему будет добавлен новый пользователь за него так же будет взыматься оплата. Каждый пользователь имеет доступ к функциям онлайн-чата и социальных сетей. Если Вам нужен дополнительный аккаунт обратитесь к команде retailCRM.'; +$_MODULE['<{retailcrm}prestashop>index_a833bd40df33cff491112eb9316fb050'] = 'Как я могу оплатить?'; +$_MODULE['<{retailcrm}prestashop>index_4889fefd090fe608a9b5403d02e2e97f'] = 'Оплатить можно следующими способами:'; +$_MODULE['<{retailcrm}prestashop>index_95428f32e5c696cf71baccb776bc5c15'] = 'Банковским переводом'; +$_MODULE['<{retailcrm}prestashop>index_e7f9e382dc50889098cbe56f2554c77b'] = 'Кредитной картой'; +$_MODULE['<{retailcrm}prestashop>index_7088f1d1d9c91d8b75e9882ffd78540c'] = 'Наши контакты'; +$_MODULE['<{retailcrm}prestashop>index_50f158e2507321f1a5b6f8fb9e350818'] = 'Пишите нам если у Вас есть вопросы'; +$_MODULE['<{retailcrm}prestashop>settings_c2cc7082a89c1ad6631a2f66af5f00c0'] = 'Соединение'; +$_MODULE['<{retailcrm}prestashop>settings_52a13123e134b8b72b6299bc14a36aad'] = 'Daemon Collector'; +$_MODULE['<{retailcrm}prestashop>settings_6bcde6286f8d1b76063ee52104a240cf'] = 'Брошенные корзины'; +$_MODULE['<{retailcrm}prestashop>settings_065ab3a28ca4f16f55f103adc7d0226f'] = 'Способы доставки'; +$_MODULE['<{retailcrm}prestashop>settings_33af8066d3c83110d4bd897f687cedd2'] = 'Статусы заказов'; +$_MODULE['<{retailcrm}prestashop>settings_bab959acc06bb03897b294fbb892be6b'] = 'Способы оплаты'; +$_MODULE['<{retailcrm}prestashop>settings_7a1920d61156abc05a60135aefe8bc67'] = 'По умолчанию'; +$_MODULE['<{retailcrm}prestashop>settings_20cacc01d0de8bc6e9c9846f477e886b'] = 'Выгрузка заказов'; +$_MODULE['<{retailcrm}prestashop>settings_71098155ccc0a0d6e0b501fbee37e7a9'] = 'Онлайн-консультант'; +$_MODULE['<{retailcrm}prestashop>settings_061b368c43f85d3fe2c7ccc842883a40'] = 'Настройка соединения'; +$_MODULE['<{retailcrm}prestashop>settings_86ee1fa7924a67e54ac66e11c214e674'] = 'retailCRM URL'; +$_MODULE['<{retailcrm}prestashop>settings_656a6828d7ef1bb791e42087c4b5ee6e'] = 'API-ключ'; +$_MODULE['<{retailcrm}prestashop>settings_f8d7c52aa84f358caedb96fda86809da'] = 'Включить поддержку корпоративных клиентов'; +$_MODULE['<{retailcrm}prestashop>settings_4d3d769b812b6faa6b76e1a8abaece2d'] = 'Активно'; +$_MODULE['<{retailcrm}prestashop>settings_f75d8fa5c89351544d372cf90528ccf2'] = 'Ключ сайта'; +$_MODULE['<{retailcrm}prestashop>settings_917afe348e09163269225a89a825e634'] = 'Синхронизация корзин покупателей'; +$_MODULE['<{retailcrm}prestashop>settings_d8e002d770b6f98af7b7ae9a0e5acfe9'] = 'Создавать заказы для брошенных корзин покупателей'; +$_MODULE['<{retailcrm}prestashop>settings_35b5a9139a54caeb925556ceb2c38086'] = 'Статус заказа для брошенных корзин покупателей'; +$_MODULE['<{retailcrm}prestashop>settings_9b9cf9f8778f69b4c6cf37e66f886be8'] = 'Выберите статус'; +$_MODULE['<{retailcrm}prestashop>settings_a0d135501a738c3c98de385dc28cda61'] = 'Выгружать брошенные корзины'; +$_MODULE['<{retailcrm}prestashop>settings_27096e1243f98e1b3300f57ff1c76456'] = 'Выберите задержку'; +$_MODULE['<{retailcrm}prestashop>settings_5b385947acf10ac0c5521161ce96aaa7'] = 'Выберите доставку'; +$_MODULE['<{retailcrm}prestashop>settings_7dcc1208fa03381346955c6732d9ea85'] = 'Выберите тип'; +$_MODULE['<{retailcrm}prestashop>settings_acfa058ec9e6e4745eddc0cae3f0f881'] = 'ID заказов'; +$_MODULE['<{retailcrm}prestashop>settings_91412465ea9169dfd901dd5e7c96dd99'] = 'Выгрузить'; +$_MODULE['<{retailcrm}prestashop>settings_c9cc8cce247e49bae79f15173ce97354'] = 'Сохранить'; +$_MODULE['<{retailcrm}prestashop>settings_4f18e3f1c9941a6ec5a38bc716c521b4'] = 'Код для вставки на сайт'; +$_MODULE['<{retailcrm}prestashop>retailcrm_065ab3a28ca4f16f55f103adc7d0226f'] = 'Способы доставки'; +$_MODULE['<{retailcrm}prestashop>retailcrm_33af8066d3c83110d4bd897f687cedd2'] = 'Статусы заказов'; +$_MODULE['<{retailcrm}prestashop>retailcrm_bab959acc06bb03897b294fbb892be6b'] = 'Способы оплаты'; +$_MODULE['<{retailcrm}prestashop>retailcrm_7a1920d61156abc05a60135aefe8bc67'] = 'По умолчанию'; +$_MODULE['<{retailcrm}prestashop>retailcrm_6310f29293c902c64db619c29179d99a'] = 'Способ доставки'; +$_MODULE['<{retailcrm}prestashop>retailcrm_4dbcb38bbbff5d4a402f2575c57a35e6'] = 'Тип оплаты'; +$_MODULE['<{retailcrm}prestashop>retailcrm_cc18dd262eff97c4dd4b56f750896adb'] = 'Статус по умолчанию'; +$_MODULE['<{retailcrm}prestashop>retailcrm_a33b96f0ce0f1227132f1cb3cf1c9e88'] = 'Статус заказа при пакетной выгрузке '; +$_MODULE['<{retailcrm}prestashop>retailcrm_630f6dc397fe74e52d5189e2c80f282b'] = 'Вернуться к списку'; +$_MODULE['<{retailcrm}prestashop>retailcrm_c2784d9473fed62ae67fe8597011166a'] = 'Ручная выгрузка заказов'; +$_MODULE['<{retailcrm}prestashop>retailcrm_acfa058ec9e6e4745eddc0cae3f0f881'] = 'Идентификаторы заказов'; +$_MODULE['<{retailcrm}prestashop>retailcrm_91412465ea9169dfd901dd5e7c96dd99'] = 'Выгрузить'; +$_MODULE['<{retailcrm}prestashop>retailcrm_b9b2d9f66d0112f3aae7dbdbd4e22a43'] = 'Некорректный или пустой адрес CRM'; +$_MODULE['<{retailcrm}prestashop>retailcrm_942010ef43f3fec28741f62a0d9ff29c'] = 'Некорректный или пустой ключ CRM'; +$_MODULE['<{retailcrm}prestashop>retailcrm_1bd340aeb42a5ee0318784c2cffed8a9'] = 'Выбранная версия API недоступна'; +$_MODULE['<{retailcrm}prestashop>retailcrm_39e90036af004a005ccbccbe9a9c19c2'] = 'Статусы заказов не должны повторяться в матрице соответствий статусов'; +$_MODULE['<{retailcrm}prestashop>settings_8ffa3281a35a0d80fef2cac0fa680523'] = 'Включить выгрузку истории'; +$_MODULE['<{retailcrm}prestashop>settings_80e47b10a89a3f22e0def96577ee8b25'] = 'Получать остатки из retailCRM'; +$_MODULE['<{retailcrm}prestashop>settings_7d320ac32b103449d18b51f47e7b1329'] = 'Активировать только при включенной опции \"Корпоративные клиенты\" в retailCRM'; diff --git a/retailcrm/upgrade/index.php b/retailcrm/upgrade/index.php new file mode 100644 index 0000000..6a1c957 --- /dev/null +++ b/retailcrm/upgrade/index.php @@ -0,0 +1,8 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Upgrade module to version 3.0.1 + * + * @param \RetailCRM $module + * + * @return bool + */ +function upgrade_module_3_0_1($module) +{ + $result = true; + + $apiVersion = 'RETAILCRM_API_VERSION'; + $lastRun = 'RETAILCRM_LAST_RUN'; + $syncCarts = 'RETAILCRM_API_SYNCHRONIZED_CART_DELAY'; + + // Suppress warning. DB creation below shouldn't be changed in next versions. + if ('retailcrm' != $module->name) { + return false; + } + + // API v4 is deprecated, so API version flag is removed for now. + if (Configuration::hasKey($apiVersion)) { + $result = Configuration::deleteByName($apiVersion); + } + + // Fixes consequences of old fixed bug in JobManager + if (Configuration::hasKey($lastRun)) { + $result = $result && Configuration::deleteByName($lastRun); + } + + // Immediate cart synchronization is not safe anymore (causes data inconsistency) + if (Configuration::hasKey($syncCarts) && Configuration::get($syncCarts) == "0") { + $result = $result && Configuration::set($syncCarts, "900"); + } + + return $result && Db::getInstance()->execute( + 'CREATE TABLE IF NOT EXISTS `'._DB_PREFIX_.'retailcrm_abandonedcarts` ( + `id_cart` INT UNSIGNED UNIQUE NOT NULL, + `last_uploaded` DATETIME, + FOREIGN KEY (id_cart) REFERENCES '._DB_PREFIX_.'cart (id_cart) + ON DELETE CASCADE + ON UPDATE CASCADE + ) DEFAULT CHARSET=utf8;' + ); +} diff --git a/retailcrm/upgrade/upgrade-3.0.2.php b/retailcrm/upgrade/upgrade-3.0.2.php new file mode 100644 index 0000000..b4fc20f --- /dev/null +++ b/retailcrm/upgrade/upgrade-3.0.2.php @@ -0,0 +1,57 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Upgrade module to version 3.0.2 + * + * @param \RetailCRM $module + * + * @return bool + */ +function upgrade_module_3_0_2($module) +{ + if ('retailcrm' != $module->name) { + return false; + } + + return $module->registerHook('actionCarrierUpdate'); +} diff --git a/retailcrm/views/css/fonts.min.css b/retailcrm/views/css/fonts.min.css new file mode 100644 index 0000000..864431c --- /dev/null +++ b/retailcrm/views/css/fonts.min.css @@ -0,0 +1 @@ +@font-face{font-family:'OpenSans';src:url('../fonts/OpenSans/opensans-regular.eot');src:url('../fonts/OpenSans/opensans-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/OpenSans/opensans-regular.woff2') format('woff2'),url('../fonts/OpenSans/opensans-regular.woff') format('woff'),url('../fonts/OpenSans/opensans-regular.ttf') format('truetype'),url('../fonts/OpenSans/opensans-regular.svg#open_sansregular') format('svg');font-weight:normal;font-style:normal}@font-face{font-family:'OpenSans';src:url('../fonts/OpenSansBold/opensans-bold.eot');src:url('../fonts/OpenSansBold/opensans-bold.eot?#iefix') format('embedded-opentype'),url('../fonts/OpenSansBold/opensans-bold.woff2') format('woff2'),url('../fonts/OpenSansBold/opensans-bold.woff') format('woff'),url('../fonts/OpenSansBold/opensans-bold.ttf') format('truetype'),url('../fonts/OpenSansBold/opensans-bold.svg#open_sansbold') format('svg');font-weight:600;font-style:normal} \ No newline at end of file diff --git a/retailcrm/views/css/index.php b/retailcrm/views/css/index.php new file mode 100644 index 0000000..6a1c957 --- /dev/null +++ b/retailcrm/views/css/index.php @@ -0,0 +1,8 @@ + .optWrapper { + top: @selectHeight !important; + } + + & > .optWrapper { + border-radius: @optionsBorderRadius; + + & > .options { + li label { + font-weight: normal !important; + } + + li.opt { + float: left !important; + width: 100% !important; + font-size: @fontSize; + font-weight: normal !important; + + label { + width: 100% !important; + text-align: left !important; + } + } + } + } + + & > .CaptionCont { + border: @border !important; + box-shadow: none!important; + border-radius: @borderRadius; + line-height: normal; + padding: 0 @innerPadding !important; + color: @textColor !important; + font-size: @fontSize !important; + height: @selectHeight; + + & > span { + line-height: @selectHeight; + } + } +} \ No newline at end of file diff --git a/retailcrm/views/css/retailcrm-upload.min.css b/retailcrm/views/css/retailcrm-upload.min.css new file mode 100644 index 0000000..402a996 --- /dev/null +++ b/retailcrm/views/css/retailcrm-upload.min.css @@ -0,0 +1 @@ +#retailcrm-loading-fade{display:flex;flex-direction:row;align-items:center;justify-content:center;background:#000;position:fixed;left:0;right:0;bottom:0;top:0;z-index:9999;opacity:.5;filter:alpha(opacity=50)}#retailcrm-loader{width:50px;height:50px;border:10px solid white;animation:retailcrm-loader 2s linear infinite;border-top:10px solid #0c0c0c;border-radius:50%}@keyframes retailcrm-loader{from{transform:rotate(0deg)}to{transform:rotate(360deg)}} \ No newline at end of file diff --git a/retailcrm/views/css/styles.min.css b/retailcrm/views/css/styles.min.css new file mode 100644 index 0000000..0de6948 --- /dev/null +++ b/retailcrm/views/css/styles.min.css @@ -0,0 +1 @@ +@font-face{font-family:'OpenSans';src:url('../fonts/OpenSans/opensans-regular.eot');src:url('../fonts/OpenSans/opensans-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/OpenSans/opensans-regular.woff2') format('woff2'), url('../fonts/OpenSans/opensans-regular.woff') format('woff'), url('../fonts/OpenSans/opensans-regular.ttf') format('truetype'), url('../fonts/OpenSans/opensans-regular.svg#open_sansregular') format('svg');font-weight:normal;font-style:normal}@font-face{font-family:'OpenSans';src:url('../fonts/OpenSansBold/opensans-bold.eot');src:url('../fonts/OpenSansBold/opensans-bold.eot?#iefix') format('embedded-opentype'), url('../fonts/OpenSansBold/opensans-bold.woff2') format('woff2'), url('../fonts/OpenSansBold/opensans-bold.woff') format('woff'), url('../fonts/OpenSansBold/opensans-bold.ttf') format('truetype'), url('../fonts/OpenSansBold/opensans-bold.svg#open_sansbold') format('svg');font-weight:600;font-style:normal}body,html{margin:0;padding:0;height:100%}.hidden{visibility:hidden}.retail-wrap{font-family:OpenSans, Arial, sans-serif;padding:0 15px;height:100%}.retail-wrap *,.retail-wrap *::after,.retail-wrap *::before{box-sizing:border-box}.retail-container{margin:0 auto;width:100%;max-width:950px}.retail-title{margin:60px 0 0;font-weight:400;text-align:center;font-size:28px;line-height:38px}.retail-title_content{text-align:left;margin-top:40px}.retail-txt{color:#7A7A7A;font-size:18px;line-height:26px}.retail-descript{margin-top:45px;text-align:center}.retail-tab__enabled{display:block}.retail-tab__disabled{display:none !important}.retail-video{margin:57px auto 0;max-width:442px;position:relative}.retail-video-trigger{position:absolute;top:0;bottom:0;right:0;left:0;width:100%;height:100%;cursor:pointer}.retail-video iframe{pointer-events:none}.retail-video__btn{position:absolute;left:0;right:0;top:0;bottom:0;margin:auto;width:100px;height:100px;cursor:pointer;opacity:0.4;transition:0.25s ease}.retail-video__btn svg{width:100%}.retail-video__btn:hover{opacity:0.6}.retail-btns{margin:56px auto 0;display:flex;justify-content:space-between;max-width:815px;transition:0.05s ease}.retail-btns__separate{padding:0 20px;display:flex;align-items:center;color:#7A7A7A;font-size:16px}.retail-btns_hide{opacity:0}.retail-form{margin-top:60px}.retail-form__title{font-size:16px;font-weight:600;line-height:24px;margin-bottom:22px}.retail-form__label{width:100% !important;text-align:left !important;margin:15px 12px;font-size:15px}.retail-form__row{margin-top:15px}.retail-form__row_submit{margin-top:23px}.retail-form__message-warning{padding:13px 18px;margin:1px 13px;border-radius:8px;border:1px solid #fcf3b5;font-size:1rem;box-shadow:0 0 6px 0 #fdd0d0}.retail-form__checkbox{display:flex;flex-direction:row;align-items:center;padding:4px 12px}.retail-form__checkbox input[type=checkbox]{width:24px;height:24px}.retail-form__checkbox label{width:auto;margin-left:8px;font-size:16px}.retail-form__area{display:inline-block !important;vertical-align:top;width:430px !important;height:60px !important;border:1px solid rgba(122, 122, 122, 0.15) !important;box-shadow:none !important;border-radius:58px !important;padding:0 28px !important;line-height:normal;color:#7A7A7A !important;font-size:16px !important;appearance:none}.retail-form__area:focus{color:#363A41}.retail-form__area:focus::-webkit-input-placeholder{color:#363A41}.retail-form__area:focus::-moz-placeholder{color:#363A41}.retail-form__area:focus:-moz-placeholder{color:#363A41}.retail-form__area:focus:-ms-input-placeholder{color:#363A41}.retail-form__area_txt{padding:20px 28px !important;line-height:24px !important;height:487px !important;border-radius:20px !important;resize:none !important;font-family:OpenSans, Arial, sans-serif !important}.retail-form input:focus,.retail-form textarea:focus{outline:none !important}.retail-form_main{margin-top:34px;max-width:900px;width:100%}.retail-form_main .retail-form__area{width:100% !important}.retail-tabs{margin-top:60px}.retail-tabs__btn{display:inline-block;vertical-align:top;padding:19px 30px;font-size:16px;font-weight:600;line-height:22px;color:#7A7A7A;text-align:center;min-width:152px;text-decoration:none !important;position:relative;transition:0.25s ease}.retail-tabs__btn:hover{color:#363A41}.retail-tabs__btn::after{content:"";height:3px;width:100%;position:absolute;bottom:-1px;left:0;right:0;opacity:0;visibility:hidden;background:#ef5e67;transition:0.25s ease}.retail-tabs__btn_active{color:#363A41}.retail-tabs__btn_active::after{opacity:1;visibility:visible}.retail-tabs__head{display:flex;justify-content:space-between;border-bottom:1px solid #DFDFDF}.retail-tabs__body{padding-top:18px}.retail-tabs__body p{margin-top:23px;margin-bottom:0;color:#7A7A7A;font-size:16px;line-height:24px}.retail-tabs__item{display:none}.retail-list{margin:0;padding:0;list-style:none}.retail-list__item{padding-left:2px;position:relative;color:#7A7A7A;font-size:16px;line-height:24px}.retail-list__item::before{content:"-";display:inline-block;vertical-align:top;color:#7A7A7A;font-size:16px;line-height:24px;margin-right:3px}.retail-tile{display:flex;flex-wrap:wrap}.retail-tile__col{width:48%;padding-right:35px}.retail-tile__col:nth-child(1){width:52%}.retail-tile__col_contacts{padding-right:0}.retail-tile__row{display:flex;justify-content:center;margin-bottom:30px}.retail-tile__row:nth-last-child(1){margin-bottom:0}.retail-tile__item{margin-top:34px}.retail-tile__item:nth-child(1){margin-top:20px}.retail-tile__title{color:#363A41;font-size:16px;font-weight:600;line-height:24px}.retail-tile__descript{color:#7A7A7A;font-size:16px;line-height:24px;margin-top:10px}.retail-tile__link{color:#ef5e67;font-size:16px;font-weight:600;line-height:24px;transition:0.25s ease}.retail-tile__link:hover{color:#ff4c4c}.retail-popup{position:absolute;width:90%;height:90%;background:white;left:0;right:0;top:0;bottom:0;margin:auto;transform:translate3d(0, -1000%, 0) scale(0.1);transition:0.25s ease}.retail-popup__close{position:absolute;top:-30px;right:-30px;width:30px;height:30px;cursor:pointer}.retail-popup__close::after,.retail-popup__close::before{content:"";position:absolute;left:0;right:0;top:0;bottom:0;margin:auto;height:30px;width:2px;background:white}.retail-popup__close::before{transform:rotate(45deg)}.retail-popup__close::after{transform:rotate(-45deg)}.retail-popup.open{transform:translate3d(0, 0, 0) scale(1)}.retail-popup-wrap{position:fixed;left:0;right:0;top:0;bottom:0;background:rgba(0, 0, 0, 0.6);z-index:1000;display:none}.retail-column{display:flex;max-width:1389px;height:100%}.retail-column__aside{width:253px;background:#EAEBEC;min-height:300px}.retail-column__content{flex:1 0 auto;padding:0 58px;min-height:300px}.retail-menu{padding:8px 20px 8px 15px}.retail-menu__btn{display:inline-flex;align-items:center;justify-content:center;vertical-align:top;width:100%;height:60px;background:rgba(122, 122, 122, 0.1) !important;font-weight:bold;font-size:16px;color:#363A41 !important;text-decoration:none !important;padding:0 30px;margin-top:20px;border-radius:5px;text-align:center;transition:0.25s ease}.retail-menu__btn span{display:block}.retail-menu__btn:hover{background:rgba(122, 122, 122, 0.15) !important}.retail-menu__btn:nth-child(1){margin-top:0}.retail-menu__btn_active{color:white !important;background:#ef5e67 !important}.retail-menu__btn_active:hover{background:#ff4c4c !important}.retail-menu__btn_big{font-size:18px}.retail-full-height{height:100%}.retail .btn{display:inline-block;vertical-align:top;background:rgba(122, 122, 122, 0.1);border-radius:58px;height:60px;line-height:60px;padding:0 30px;font-size:18px;font-weight:600;text-align:center;color:#ef5e67;text-decoration:none;cursor:pointer;appearance:none;border:none;box-shadow:none;transition:0.25s ease}.retail .btn:hover{background:rgba(122, 122, 122, 0.15)}.retail .btn:active{background:rgba(122, 122, 122, 0.25)}.retail .btn_max{min-width:356px}.retail .btn_invert{background:#ef5e67;color:white}.retail .btn_invert:hover{background:#ff4c4c}.retail .btn_invert:active{background:#ff1919}.retail .btn_whatsapp{background:#33D16B;color:white;padding:0 62px}.retail .btn_whatsapp:hover{background:#22CA5D}.retail .btn_submit{min-width:218px}.retail .toggle-box{display:none} \ No newline at end of file diff --git a/retailcrm/views/css/sumoselect-custom.min.css b/retailcrm/views/css/sumoselect-custom.min.css new file mode 100644 index 0000000..6b1a47b --- /dev/null +++ b/retailcrm/views/css/sumoselect-custom.min.css @@ -0,0 +1 @@ +.SumoSelect{width:100%}.SumoSelect.open>.optWrapper{top:60px!important}.SumoSelect>.optWrapper{border-radius:10px}.SumoSelect>.optWrapper>.options li label{font-weight:normal!important}.SumoSelect>.optWrapper>.options li.opt{float:left!important;width:100%!important;font-size:16px;font-weight:normal!important}.SumoSelect>.optWrapper>.options li.opt label{width:100%!important;text-align:left!important}.SumoSelect>.CaptionCont{border:1px solid rgba(122,122,122,0.15)!important;box-shadow:none!important;border-radius:58px;line-height:normal;padding:0 28px!important;color:#7a7a7a!important;font-size:16px!important;height:60px}.SumoSelect>.CaptionCont>span{line-height:60px} \ No newline at end of file diff --git a/retailcrm/views/css/vendor/index.php b/retailcrm/views/css/vendor/index.php new file mode 100644 index 0000000..6a1c957 --- /dev/null +++ b/retailcrm/views/css/vendor/index.php @@ -0,0 +1,8 @@ +.search>label,.SumoSelect.open>.search>span{visibility:hidden}.SelectClass,.SumoUnder{right:0;height:100%;width:100%;border:none;box-sizing:border-box;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";filter:alpha(opacity=0);-moz-opacity:0;-khtml-opacity:0;opacity:0}.SelectClass{z-index:1}.SumoSelect .select-all>label,.SumoSelect>.CaptionCont,.SumoSelect>.optWrapper>.options li.opt label{user-select:none;-o-user-select:none;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none}.SumoSelect{display:inline-block;position:relative;outline:0}.SumoSelect.open>.CaptionCont,.SumoSelect:focus>.CaptionCont,.SumoSelect:hover>.CaptionCont{box-shadow:0 0 2px #7799D0;border-color:#7799D0}.SumoSelect>.CaptionCont{position:relative;border:1px solid #A4A4A4;min-height:14px;background-color:#fff;border-radius:2px;margin:0}.SumoSelect>.CaptionCont>span{display:block;padding-right:30px;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;cursor:default}.SumoSelect>.CaptionCont>span.placeholder{color:#ccc;font-style:italic}.SumoSelect>.CaptionCont>label{position:absolute;top:0;right:0;bottom:0;width:30px}.SumoSelect>.CaptionCont>label>i{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAANCAYAAABy6+R8AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wMdBhAJ/fwnjwAAAGFJREFUKM9jYBh+gBFKuzEwMKQwMDB8xaOWlYGB4T4DA0MrsuapDAwM//HgNwwMDDbYTJuGQ8MHBgYGJ1xOYGNgYJiBpuEpAwODHSF/siDZ+ISBgcGClEDqZ2Bg8B6CkQsAPRga0cpRtDEAAAAASUVORK5CYII=);background-position:center center;width:16px;height:16px;display:block;position:absolute;top:0;left:0;right:0;bottom:0;margin:auto;background-repeat:no-repeat;opacity:.8}.SumoSelect>.optWrapper{display:none;z-index:1000;top:30px;width:100%;position:absolute;left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:#fff;border:1px solid #ddd;box-shadow:2px 3px 3px rgba(0,0,0,.11);border-radius:3px;overflow:hidden}.SumoSelect.open>.optWrapper{top:35px;display:block}.SumoSelect.open>.optWrapper.up{top:auto;bottom:100%;margin-bottom:5px}.SumoSelect>.optWrapper ul{list-style:none;display:block;padding:0;margin:0;overflow:auto}.SumoSelect>.optWrapper>.options{border-radius:2px;position:relative;max-height:250px}.SumoSelect>.optWrapper>.options li.group.disabled>label{opacity:.5}.SumoSelect>.optWrapper>.options li ul li.opt{padding-left:22px}.SumoSelect>.optWrapper.multiple>.options li ul li.opt{padding-left:50px}.SumoSelect>.optWrapper.isFloating>.options{max-height:100%;box-shadow:0 0 100px #595959}.SumoSelect>.optWrapper>.options li.opt{padding:6px;position:relative;border-bottom:1px solid #f5f5f5}.SumoSelect>.optWrapper>.options>li.opt:first-child{border-radius:2px 2px 0 0}.SumoSelect>.optWrapper>.options>li.opt:last-child{border-radius:0 0 2px 2px;border-bottom:none}.SumoSelect>.optWrapper>.options li.opt:hover{background-color:#E4E4E4}.SumoSelect>.optWrapper>.options li.opt.sel{background-color:#a1c0e4;border-bottom:1px solid #a1c0e4}.SumoSelect>.optWrapper>.options li label{text-overflow:ellipsis;white-space:nowrap;overflow:hidden;display:block;cursor:pointer}.SumoSelect>.optWrapper>.options li span{display:none}.SumoSelect>.optWrapper>.options li.group>label{cursor:default;padding:8px 6px;font-weight:700}.SumoSelect>.optWrapper.isFloating{position:fixed;top:0;left:0;right:0;width:90%;bottom:0;margin:auto;max-height:90%}.SumoSelect>.optWrapper>.options li.opt.disabled{background-color:inherit;pointer-events:none}.SumoSelect>.optWrapper>.options li.opt.disabled *{-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";filter:alpha(opacity=50);-moz-opacity:.5;-khtml-opacity:.5;opacity:.5}.SumoSelect>.optWrapper.multiple>.options li.opt{padding-left:35px;cursor:pointer}.SumoSelect .select-all>span,.SumoSelect>.optWrapper.multiple>.options li.opt span{position:absolute;display:block;width:30px;top:0;bottom:0;margin-left:-35px}.SumoSelect .select-all>span i,.SumoSelect>.optWrapper.multiple>.options li.opt span i{position:absolute;margin:auto;left:0;right:0;top:0;bottom:0;width:14px;height:14px;border:1px solid #AEAEAE;border-radius:2px;box-shadow:inset 0 1px 3px rgba(0,0,0,.15);background-color:#fff}.SumoSelect>.optWrapper>.MultiControls{display:none;border-top:1px solid #ddd;background-color:#fff;box-shadow:0 0 2px rgba(0,0,0,.13);border-radius:0 0 3px 3px}.SumoSelect>.optWrapper.multiple.isFloating>.MultiControls{display:block;margin-top:5px;position:absolute;bottom:0;width:100%}.SumoSelect>.optWrapper.multiple.okCancelInMulti>.MultiControls{display:block}.SumoSelect>.optWrapper.multiple.okCancelInMulti>.MultiControls>p{padding:6px}.SumoSelect>.optWrapper.multiple>.MultiControls>p{display:inline-block;cursor:pointer;padding:12px;width:50%;box-sizing:border-box;text-align:center}.SumoSelect>.optWrapper.multiple>.MultiControls>p:hover{background-color:#f1f1f1}.SumoSelect>.optWrapper.multiple>.MultiControls>p.btnOk{border-right:1px solid #DBDBDB;border-radius:0 0 0 3px}.SumoSelect>.optWrapper.multiple>.MultiControls>p.btnCancel{border-radius:0 0 3px}.SumoSelect>.optWrapper.isFloating>.options li.opt{padding:12px 6px}.SumoSelect>.optWrapper.multiple.isFloating>.options li.opt{padding-left:35px}.SumoSelect>.optWrapper.multiple.isFloating{padding-bottom:43px}.SumoSelect .select-all.partial>span i,.SumoSelect .select-all.selected>span i,.SumoSelect>.optWrapper.multiple>.options li.opt.selected span i{background-color:#11a911;box-shadow:none;border-color:transparent;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAYAAAD+Bd/7AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNXG14zYAAABMSURBVAiZfc0xDkAAFIPhd2Kr1WRjcAExuIgzGUTIZ/AkImjSofnbNBAfHvzAHjOKNzhiQ42IDFXCDivaaxAJd0xYshT3QqBxqnxeHvhunpu23xnmAAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center center}.SumoSelect.disabled{opacity:.7;cursor:not-allowed}.SumoSelect.disabled>.CaptionCont{border-color:#ccc;box-shadow:none}.SumoSelect .select-all{border-radius:3px 3px 0 0;position:relative;border-bottom:1px solid #ddd;background-color:#fff;padding:8px 0 3px 35px;height:20px;cursor:pointer}.SumoSelect .select-all>label,.SumoSelect .select-all>span i{cursor:pointer}.SumoSelect .select-all.partial>span i{background-color:#ccc}.SumoSelect>.optWrapper>.options li.optGroup{padding-left:5px;text-decoration:underline}/*# sourceMappingURL=sumoselect.min.css.map */ \ No newline at end of file diff --git a/retailcrm/views/fonts/OpenSans/index.php b/retailcrm/views/fonts/OpenSans/index.php new file mode 100644 index 0000000..6a1c957 --- /dev/null +++ b/retailcrm/views/fonts/OpenSans/index.php @@ -0,0 +1,8 @@ +v-St6h!A_4|wQA89W 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 LU__l3N*eTcf>x{? literal 0 HcmV?d00001 diff --git a/retailcrm/views/fonts/OpenSans/opensans-regular.svg b/retailcrm/views/fonts/OpenSans/opensans-regular.svg new file mode 100644 index 0000000..8dfe7b5 --- /dev/null +++ b/retailcrm/views/fonts/OpenSans/opensans-regular.svg @@ -0,0 +1,7651 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/retailcrm/views/fonts/OpenSans/opensans-regular.ttf b/retailcrm/views/fonts/OpenSans/opensans-regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a91fb8a5daff72660336829db32b412447260440 GIT binary patch literal 95152 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 LKt!DyN*wriIs2t5 literal 0 HcmV?d00001 diff --git a/retailcrm/views/fonts/OpenSans/opensans-regular.woff b/retailcrm/views/fonts/OpenSans/opensans-regular.woff new file mode 100644 index 0000000000000000000000000000000000000000..616db051d26e988be73c69b5b5593282dd771256 GIT binary patch literal 47896 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 Gvws8CL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/retailcrm/views/fonts/OpenSansBold/opensans-bold.ttf b/retailcrm/views/fonts/OpenSansBold/opensans-bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..96333f910ab3fa308405759fa16f57f8a34a13d4 GIT binary patch literal 97116 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

c_y^& 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 QHAM{<^vK8$aPRkj0n+#Xxc~qF literal 0 HcmV?d00001 diff --git a/retailcrm/views/fonts/OpenSansBold/opensans-bold.woff b/retailcrm/views/fonts/OpenSansBold/opensans-bold.woff new file mode 100644 index 0000000000000000000000000000000000000000..37ed43a7093bfc2d9a7f2eecc809522e72274554 GIT binary patch literal 48168 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

l7>k}zK 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 literal 0 HcmV?d00001 diff --git a/retailcrm/views/fonts/OpenSansBold/opensans-bold.woff2 b/retailcrm/views/fonts/OpenSansBold/opensans-bold.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..438048214fd51939dd648c96e92d69f1d93b4a45 GIT binary patch literal 35328 zcmV(>K-j-`Pew8T0RR910Ez$r6951J0ef5k0Ev?T0U4SA00000000000000000000 z0000#Mn+Uk92y=QzeF5R24Db(MhGwogk=#33WV%bgrge)HUcCAjbsakbN~b(1&Im= znL!MJ2tRjaVrB+g;!$x0-EF6WN_PUZs`9V>sY}>Rymvc@@7@#Bs*b?0aR6_ys%HQH z|DT*p##nblw*dgCrdsv&l0r#I$>4^F6yeBPoJv(V97V9^)LE$vv>fDBl@}FP3tpy+ zkvHtsrNth?X2ODKg261UhasnA#Sd1#Sox{+R#ACnh3Q1r4j43`skm#nYxt`hWt{kIf9&+Kn-UE- zNA`-06DsYKWj+07HC7v($eLFYlPVyaF z`6-VP>a{s%A$7ytv??H!2(2>1ux*t}w>c^sK=1P71+J(_qQ z(rMI4!z8w3j5kia$VFWpcHQwK;=wGTxcPMZ3L8kkXFP8Ffdumv{u*c671w#!?7jY) z?wVp7;NKss-#fJ?)sn5TG%iWD*1uPS#123>*B?JZ50DSwS~zEcKk1B^@)HVxgyc`$ zT{?J+;j^4aD1^j_KL}M9p;=Aoq4rSQ-NlGE{%lZi%%xnBFj*`v1cwxe7gkKWRj-21 zx_jcJ7@z{fu}=tC{PjQ(IFl->RmP%pQ3C=}X zo^x^eHKW!=ugXR1T>Wsltbe6Z=+S6`x+iSNclm|wtJY6)8JhtkY(h+!%V?Pa8z&uG zR6c!6Iv9iKDMUaA2@MhXYwpeNZUI2ymdLBT*Zi4$PPUBuRW=2*b*4rI3c&K_ls~8L zNL5`a_p+z7Im2XU&X#TA-Tec?KS&w?WeZeeRp}JvMkx;Pc#FkSO6_ZDlw3+#vw7Jv zC0oY8^WV}}s_Q-T3$v2WGBD`^%fPMx5@ zP^F9gf`W}q01bdqb1Clx5KpJwd?7R~S^wwcX<4VAE8iA?WTy&PxD-frDqMxFTR5g{ zf$$IlZ?DNEGn>491>`%2`dbd2KwADU;R@p9{ zS_V-;Vg>nieg?;S!!L>~9L$7*e~R$3`#jX>}A^OXMIbXyuDqQO7?F3MX=$I?a# z5;7)<()R0qWKQ}^9M!hUEo+BWR7OBhRv=`)2lUqe_x=|7a<+19`KlV?YY~`0WMnyP z6Gvyld*-D@>p+0WWJ3V zn7nt&)(HI6+gyQ$-^{vlD1NJ~Rmh-~;s15$<7QI+#asnm7~ALoaPp9o4wDY6z=Ex^L~xy0m`O1??!=b#kY}=|3~)ZITCP^$ncxQk}WkoVT|* zXn@|jiP%)kh$y_%8$uPvNShOf zifkTJeCgy66wRDu3cW-|q@CPuH`nTM5sCwj^`w~>6zxhmV3^QKj}k-tY(q%9)yd9M z#-qD?75ka8a!Li8j8xx@5S?8{ve&kpSi1r31f1V)lN=HQW-&c!a+vPjN${;kR==W= z;!+*c2BV@qg@d7o#GzqeVpci2(qlFJh?plwq?w{io+wAbUcV8TsxB&JFYQi=Nq>w=YXIu!Y8WMrbw8l5S&~%;eH# z`*4-bI#@7XkhfW4ll-9s91>ckh)uM-_}xoF8d>B^E0Z;xJWAD9+u-wUKHLT zf{nQF_9T#f*coP-OXZ``SO4tPR0v7jTG7pA7M^~B$52uHf@j_ zeh(yri@%b%jlLs0$b}s&HM)Uq7cXtlHf%}Z=>RgVmt&zVFQiyyoSwikQ)RUa{(Pf0 z;LwmB0^GiVDOyX~1rb8;tHD=qH2hk&X$4x5OFU!p^q*Bx4qOfN;D(3B6M`qw-o&TZ zmyj%SNdbRQ3AlA2Q=oM%pWS7SgWM)h=NknSQbH+Zx|~#$l~6@jLrte?s9vs*D|(op zTM#80HRMRl(N5R!ESN+QWh=mx(ws(tD`?!bhr&ZxP6Th|1HOtBAqQcMb2>$_$bhZ0 zA%|S@$ftlpN+_kwmtUl!@0?V1p7S*$P3h6C1cu)9Z84JKZ{TS+Oi1R~6`?5u4J zA%qY@2o++vJ(QqGQ_~@px7KCs8|x8--2?(Y-7fx@QP|bq=)bcjd`)9^$tog!@+@?c?on5!FV7fyD_sTp z73CeW+7JJ(u?F|xK6yYMl1J8K@`S7#Psua#T%FNg&X->qui=eKyYkL&=-p&nTS6D5`V~RZE?2Ld~s&7imZw6yYk?=?@AuE*b}odqK7|<3K_IRqc&PkCS0k8AV3T z&)0zB#pAP=e^-HkfDb8%L82OGg@uLcz`~j4w>zr?p#P?>id$-x-GxVN9iAt=N_g;X z*n!=ceTIE?zpISiyPqd!d+408mone0R18R_n<= zNt#R{SIs%AqId!%`pY$oxD<|nZ{8s$VHHsNb~9Ia3Vd%Qa{YBt%}w(#)4u{*(wni< zg?R5Rt$+;Bt;E*@F#P3F7*mrH$P%vMJ6KRl;G}v4#Pb^$0tWunE?%1;Q*Tu}u98PE z^e?&`RD!mGbwPkGrG506R;)q@aDQvJ9bti|<4 zzzRGm3rq@jSVd7WMchPM&;=77pH--UQ{_amupmoGM%LF_9Vm!OOJyVK0w831s`@CG zSLsr+Sb_TPM`cyq%@kvuf8PGpu+5Hq&KeVt2oF9rsiCK)FzRk|83ap-15K@9nBCSZ zebLrFMV38R#2xu8Hi0Q3B{6{^;w+#VGsKS-mWYxl2Z0Bn=q%(7kG1lJdO7ksDAuOV z?+yKx)*6~s8KqKfC8p}AEYm8QDr%fsYCh?JkX5cG9JG2P1(!aofu{u-n+K_2BvHT9 zI&wB2`nqzLJS8W>g^RFxGYdO2A>>4}H!e8`v=GbXZ2>O0hl>DRhy=*So!o&bxHMhD zLwqodt9G@*h{=uS-TD>)$x(;kVik4=Q>&x_Mv#NU_~QVvHG!*$j0c!gr{yfd=_p3z z@S-D~1IIBYHF{25+;s|)zKQC@yj7feFgK6Fxox$k>#Whb?|Q%*_LI}%tuYoBD{?3B zLAlHBN(e!OU_!d~$~vnGiLuqZx4t?Y+iYjzZn{YDPRvV;AJ6z1e~Dum3h+O1^>QEu zlE8>h5-jVg!H%k|T-bjWDR7#ScDe{TF=RL))6HtMtz?g*^;G4|B^KdU%}uArH0(hz zA}WMs*;SLZl_OrNten}pC^<1L9Q>#}kwUc55GOl1jUm+NdxdPA6$~|lo#7)=!Baqr zR?!i&Zqbt+`$W8`ZSX280IRVclsSain^ZeF=ravSLvj;eg_oCuD*lUqDBhVZ!^kVN zZjjRFFS&EJ_U99tLdL}tY4Q)U%)Vu7znQf7Kzfb0hb}ymg?f6X)@Q0w=G!gaRx4GG zI2P7NmA$b{82`i^!$=9MRok-WR+#NTt=;|=QY5w;;fz-4)P&QZ=m_qO_n3MD0_6Loqu zNH>CVN;Ub5Z|l)f<_c)GYKhiTPGZ$`kS(4|q>{8`E@hG0LO3O*+)fSCt+7LO7F2WG z96h;?LJpM4b&l|Ies{cH-}XW&s)jvj{1bTqfx07#&8`Xra@? z-VqFFk`GvH6l(D_)}OQPjN1Vs1G_Ig;0PxWxWNOB`oeLDA>|B(IIc+ijg*Hx1p*W) z7OYhEP)$n>(_AZpHreZIS84+tbkJ?Z1BEA`AwHKN|$YBS3n#=>h_b(uP1ke}&`O8+h z5V$n>xAYHR!H*xgcOVM^V7m4sKtKVYzd{Q}0D$a4pFah`7-9!#WnodD3kFzXnN`+U zZ!6Jmhb_8g#hQnndhVU~KH2rfcZdGwKtiG!cmfkUZj4*wPK~JlcdGIXrKmt9YLGw+ z)`hjIb*uke02c`CmRV_ybpgivAy7cd`^K+ZRzS*c;qfFm8Zl2mVjkgv8FdHY5sFdv zzz|S(q<{7Q|NZZO|MTy^{rQi-`^~R^`Lmz?@CV=h*80X*zw(98f9|uN`1nWvBd_#c z{VQwPDSO-+oKUM;g#sDIn%51TDe>!lOujBZUI0c^?$MS{QjYQdD-vxbK{1>bmU6!h z%Rz(*Wn9w@)3P1c^Mf#olQheVGGn7eGsc&ZZ&vK)$4l7mHA%7*snVp&kSR;H9J%u3 zD^SRvwvw$%O{`{LehV$HZLO`g+S>aab<);lS~uOPeEtPiHoXA-4K%R&SEN&w7c`$yXo+OM_yxb%$CJB zB8vIKj?PHGak~A9Y!8dU%amvzb}`$wS_^ITrTkPcokBUM{8g<}Mza$M3r-z0F2FU| zu^B5%S*F-qL3rJAxp4jKilWp+yZT-pt#S9X&`;_IfRkp@f7+AbxrG3bCB&bF!0t}jg%_9hQwyGnF7_|z? zAiOu||JqG@J|rbf!w$2afj2zKFl+Y+(B-(5!`nXPeI8If=a^b%k9$J>lp>_dpYD@s;j(0xiB4vjs|~N1ex7;QARZGjbaU za<_YK6NGReg>44xmbLMzWyLp?-d$M?ARr+BFE;=nI{~20`3MIf>OGM22L5sq%)S3u zg@aFn#ig|gWU4IFiKg0Q&2tobZ$xo0p|x;LsGXP@+Q#&S;hgAUPT4rqb(?jtre%gh z%!1LH)^3G(LH5APM5p7oCDqzK==>6V_H z3rf51C6L(#uAK{n`;Q>E3p@ihZefPMLA>!_@Gg28!q6L#ovuY_kf@h@`ht6idh$%N zw4A*k-nf6C+J#ImdToY^hV+Uo3kzP9C_I>~{c4j)xJw*!hExb7I#9QpsjRLW{ zKg9_Zm||*?0xZ#`vdjfjduc4qSg1|f1I=I)O&Tz)+l_tOso3L_{nOs9SYrbw1X2%9 z?movL(~8|lk;;qaiorVwqh8J&@<)soDyH4)gA8A{)VZi49;XB$m0g1ID_4l(OV2mX zUqt3ZUcj-VqWh7V2^ZvQ_~3~r2yVg+j1MRKU5LL!rqBK^h{-VgU0Je!dBShFYQ?|z zFY$Q%_4=O)H<+zeA6<1IlN$HvrT0HQe~{ zd2jY`-w$i`QNGG`^sS|h>o|`{&HnW89e!lsEP!089N3j-i9d`#Q5cG4Ahr@y*(w91enxgGj0Njg=Kk`AoT=q>QqX<|Vg06KwVTH#4 z#KxbjG^jESyD^o~;ggizszCq>ehEBVCw?b#rk?@s!wmH1KK|(wDZRla%__xc+6eSU zbqG04wWUS9m)2FBjIOPqgldKv)s(u@_b_0zZKH5t9c4X>uK(Jo$TaU+cAjL>hiv|n zxNCc>f$ixQ{(mWR547r_Z41*aXgFVhBS5 z*v4BN#vl_kXZBm1jH7)^{#(#sXpCEuSs&5ai^)k8GnR-2zU3*(qmkV^X1s=PF=wRk zE%u8F>zuYPLCP%+fN6npmFS7n_4m0ts|j>NQKw>9Uh`?k78y!#^z=xJ#F{TEf`=6x zLM0YM90$k|nP9G=!5^U=EZ9OO^t4#3NU8h&lQrAu8oDxXR z)>oJgw-|-FBs4dmonk)Wzf}9A1}DR^reDG=2TwW{x#z}}6NyqX!8N4vK+5nzT-Blo z=R#8l4B#9?$rz4D`=kMzvZ3MZOT@ue`jA;QGS2-Z%0#)!hpQx@27{qGcugim*beU( z4qQ-WwrIP--7FCiMwgm&NetSU>M73j&!$!eez7!JoQjD>k*x^hB4Ank%=?Mswd{og2O_b53|ndB#HWi z#2MvaWg?uqzXzxW67)8u24BIUI#a`z1VANl*f8uMlIhz!LxOU0ULqaADE_e;h~WUk zyp8f+upX|kkXUB$6@#lkFoN~Jq%UzeND}!`+JOn~_8=)ZtyFWi1v*W!9aCgru*H)U z!_m^544y~VcW^Vavf&tV@ExQTYfOg3+?vIq0umMX6AHiMcG@qmB;T3D&&koT zsEuhl7-~?qgG*STW?MZQ_ZDBt=$1vJr?IBOAu#Vo4EfAR%W`fEu!U6aCs^T#&8vX2 z0Nf_6?O+AXlnb>r&Ald=Y4$T0u7yLWyC z@H>&GY`cS1_AuI5wh*!BJHV_}+TLj~(z1uo?&Qt$ckWtJbe6VK}^V;Y8qxIMx#_ zv+B6$%JzLxRf=EY9VTD&=I@;wn1)n1ir&%BV7Mju-eo&xqHSXr5l4 z+0!&!z~~^+Ba9m7qs_o2)~YKB#R>_J7=?z?Qk1V)pyu733d=CxbtEX{Rh+2ojtxs< zaMgtHGH}WWo6BLCQAjiYDQ(9c0 z`!C-A2&IB*gyLwDM@sb+;sX~G0)DxMB*Tz!EkKIN73Yx6FVh4By<7*9mU;-)jZ~(Q z7r4TMFQvOS4MEj-c_62t+*qDojwL_ght-jRcjHeGqS%$3N>gA*&@m-dEIZ)mXw{c9 z3gha1Vi*nO%nIYkETE?`aa}UA*CjNvek(j_8qlnrRp{D3i+}z$t^S{;3T_QfVq(mG zDwGd$J44%wk$JH%K$Rb0jtv%!qvw*klWw~m?@+xF&pWd0wgqBSg7|sB6?-&wDw~lg zGwL=SJO^0S|Tx%rYr0t(ls5Z6gQJmH{ z2SSYle$zC^hQ1eJRKoOOh&&KYUyZ1tgsg)SvtEf;XcQ?p@-cH^A9?)3~#3{pp0bi%- zKnlP1P~aW~+7+WdiJY10ik+7={Qi<15;hDlfYqy^CNV#3DZ1HeGR>$AHB~oma{#@^ z#}GeaZgNfI&Xbpu5@eBcT~_Kz&&2?nOEP|yD5eX*GB&fo!OfvY5SF9av(6ms3hH(w zlO+BgHVz#VDF12RrcmJYnkU`BH*u8^(||$}yH!`92S(I&gMYdNX?=8{vT@GC69LEG zG-<;fIJ8zTrt^~O#{fW$1_~a9&&AxlDw|8T|z_8gSPtP4g;*kp4eP_dG{mq z=-a1Dpfmk5XEm>`4<&-3!tVX9zi>L1bKrEdC;=uu5o`_#J7Me&#zx#Yn+CFZU_TPW zwv6~F=kK9A7~$Zi%Ie(RFs)Szmn!_C`6_g~38*?LgHLjzGL$f~w0-d7& z7#})@sqSB7S2)tKo??#gCo6#>Np!&4kyAhVw{&2&as0VzLQ9*ndbHjDgBD%Fm=gnR zhz$DE`Vg+N+X+hwes#8zVj_%Wx#;WG$}XFDy<$vOO5u9RQ(9r57cKq5kDO#CS{Q0Y zO-0Yn7uVHghOkCubG=!QD5S1Joq`pLJK^;T7Cy=*w`|>e8snr79PRHEOFOPE>@u=8 z)f68AFE=A+bgija`A$?&8A%><0-$)yauj{J$%zP;4odzd3Lm9s4qp@W?{D=KNii8noPUh)Uz;A7}6o^tajhwmZZ1K`1$p#M9^)QmpXG>h<(woIK?KOsQhB@ zb$nTa^Qr@4%h=yQFghwgPxfdA_Oi#(Hu3ij#4&&Ik1TiT z$Ghke#Qz5E^cb*+qXU-&Ul!Hcv0P@8^Bly3gi z-|pqDQ*ugS`hfd)4lm~9V_>uRr@yT}+P({ouE@R!4x^d{-JRtZ>AeN84fvA0T)^+e z%Bak!vj2&CnU=E575WcWs_y-_d!s$~-c&OiWuFupOOMXaohWLqN1!PN-OE>1wjspW&|pAZCz!qytj zv%)zSW8{*;X&aYJ_kPk>SO4T z=Q+{5kI@y6+k-3r&mQ{JqBJyVvMubS2t31 z09bUQY91Gk} z5`_IeghISuEXrbj2XiWaTK5TX^qLs8rZ|5&Pc zWvT}Vf{_|ddN`nx-sTBSK&lM8%;D=mnU@aIK9!H$gvF2pce20 z@aW(=thHFoT~22DxSH52iU3x{UeL>!I#eWtuQ#X~ft+duqwyHhH3%`3>LUu~*NRK< ze+0;?7xZmnirfn{--XR9-n+uuXjt+O4Q(`B{~Cgio2-QHMow$Mh29j{(i}EJ4y^;z z8?QM1CGbaEdOXW)tOb5Tw9_W-@tbj`$=a1h2*AIE8uYF*pDV9?N)G?gOid~y61dOy zH3vTe*dI;||Hnqi{9l~3Z8~hgt zfgfA!JA|}nJvw7C1?01v*Y1d|l->LWn!@vu^bQ<@co0sq3)s#{gJOdvXT|MYF=&Tg ziByoDGtyb<1or{(CZ&(F^L%S`Yp`p4=g*5AgG|%lzlXu51{pk{9Mg%({m5o+Iz;MC z?DM^Fgiu~~?Ai%$4`1igfhRvq&s}^uSesrp-%OU5a7{6rvQsb40(gZOXt0tJt`vSb zP<-X^jFcM|md&v7bZ>DA^35dEB7Kdi0d{b-4bmB7pH(s&QjK83e`=-n{9)y_nw5j^ zCzeVs{5sLsydcm0aPsiPQ1cyL-ct*;J->>l>Xv>S)U3#JABbuFjtSR^2alwrgsc4V zmVT-8_E?K$DPlYAxsHfm=(B?2XT=3^tLHt-^R?r3;8)+4*)qGyme926Ig~wAtE+a5 zgw-S`CDT90q}-nt{Eh?t1Lh><9LDqf1F%h#gaSUq#RP3bgn~as#|C~v@z&cvN;?AZ zOu})%a!?Zn^SGy26SQ#{O|A0m(TONq3r!wR33DB(pT0D3X+$^+bo4kbsl7WiS3lO8 z9PCT*J&Prwy|GrjK6=Y=Mssa=a(IvLh4|gX{?p``?z?XF#{O2-4;E`A-+`w(%et;M zsDno&C;Dse!#*kOrKlM_))603Y#P}gF*Y&|`W*brj(=N?PlI5FWrSJ@;B?o_Y4RUl zXu@+O*cpz953*p0oRZdx|AWKb)$fM9vIjosn6bE?xGY!UpJZ{rvr9ptm-q&SLi(Q0 zzxclXi5L2*eZL`J*_4`|*%2HXS;&qK$zae5S@GN{BOuvXwfe6pHJ=d`bW&E~^%L%y zxY+UNR_^O-_W=9a6WBv#s1CpCD@h#Lr(64?%Zj&2=|K_6i6I2%gp_Q@2Xq+1218@O z=!V!JdzVlzoL4YD1*E>b4S{a8e{ITBSsID%OU!o?`sFPV(6ZjwwRb4s1Tc6*uFT?$Um$w=Kbhww9hj+d%bj5|9tq2WrHFdYK;r9q8RK<8j zcsK=GIR-hC8n4p%#5eMCAf<&-em*&Ie1qpXW&D!o`f=)BPf5H-qH7s6I=5$Z`N8yk z0j?*cJ1S06QXeWX4xSgA&K3Rmns@q^Zr2&h6$vdZnW%q883xxL`txM=)&ytXAM_8* z=C2lfuIG?d-qHXNTxQrRVKcZ$s^r|Ir-HIhJkek$tK@iI0I-vb7BVB(3o?4bppbH< zjJ~QRl~=!Z{@==F(ZeP|*M4xa6R`e18E!=#;b~7cx=4u0wTF+Gp??TkT}b@!Rq*!5 z_)||YpmC3v_Jn*FW-(Z1R<(ZH?jwBpEAAM#?0MLi4-1SJ3 zwf}G5$sz4|NPR{4o4XX|&$C|VZ+;Ql*qc4PNDmcW3Wue6o}0)7MR*w9w3+!Z4R1OSRsug zoy>o^_B@eaOe1R~Fzsb*L@t5AD2Ry;FT&$jG^nWW(?6$^clQ#fFa1Krx>5Hb5*zP! zw0=X2_`uAXs)U%>wD6oNr(aGba57*7A5>Z;fR>J&g7#g|uc!FrMPAiXo~8RvXbm~Q zqzh)qsH>nyMpXMX(x`#XDs_&x;l9imELPvxDZsYcxyY_4*{eBx7X0Ev(j9n$N$7rx z@kvb&av;G;H-Kn)%KbLIX};!$!n>nSiNJae5D%>4AUIa%d57RReI>1}Cri)tXTitX zkAD-*yCi3!VnM+IB1O%ZQW%rleh{ktrl>a)sVq53w1rB;1jdz2o=cu1>+Oj-3VEAp zrW@pu?i&c6AFKaC6fMx91e11|q%ZvMH3wMx8t=`{{|o*vD{XvSm?K#UnG4}=J=lF6 z;;mzbT9{TV-iTsC!6|uXAec;?D?6z(FRy&))rSnC(r-+qAMTr}Nk|aCEz@q%E?Qios zzSyx}y@!0)d0_oR#T-0iwrjQhA>Sr{}K3PbzCUC z^Wnm4m^q{?u`?<)rUcZk%86^qVkQg{s9LfzroVQ1h4$*#KzTK_`C;R_bi*Al?Fp>{ zVPjqEra`WR#0)w;HX{;P$Excg5U*YuUOkgxgndO-z&Ro@Hm-b|d@LBtpJyU0tAwTS z&c)x%!QID2$4`^llQgxv_`*0BDqh?&WMJ$O$T?hk6*B+Mz^V7jg=?+N^RACgKmBOu z?YBRMj_sVL;Ot|Klh61h;K1LoRAQtr$t#k2tGjo-fc67hO>ilg0uAeUxv+fy#yYI( zD7S6iPVE@JLjS6`og*yFL4YnM~KWkkb?A&k+? zVl^r94^-G^=}o@0@#t8v<9vQZfQ7F-T}=vYw2wN~q6U~gQrsVMhr9uH$T#lF?Cj%p z(<&DJm|sHN*&!J0v!m3=fd%1&u)&z($B0RDH}5U3=g+_IFPk$BE(CRHYWfq4{n)>a_TJ!mXebT9hnqbF$k{1^5VQBV`Gu4mr zMT0>>+J%oY?d3XYNL4Nr%_MhEsZjYNnA=mSQ^coC8lNu>hx&>H3kC>)`Ot=kEk%3! z5*jp&Z*lT2ui#v&+ZH6lDqPL~N$As1|7f*LE9$50?QAU??p}%t z?>Y?apFij23H=)5NH`Xv8Y-o4!Qr{~{-e{4pF@z3MoQN{XXp75`RtmnXCAx>0eZNg z^m#75Qx*KEHp;J$a(4*@zCE~}dCJ-^pBi`LE)W5 zD$gZ-S9j-7;G3Sy=J=#IJBUw)chTppd;;A%fsi<3-V^7i^`05i?4GOap`42dfoIv1 z-!o!boYG|}8u=E%7MV_FDGA(Ws|WvTNp@+P38qB3F6~;q!E;yso>KSNnJ&IU2g;{m zU`JE8V7PxS_}-ld21KcV;P7 z3!NQu^6l-NavkmI(OYp(&pv;Cu-MRob?-?)M$jYc3S^d%1&Wnd(=7{czxrKAP?Jl# z@ia|}YO?5{cwR2?#*{RgYG1Wz} zLt=hieqhm#L6S5(yzL6p(J<=v7m{_e^X`(tHeVlfm-b^g4sd=yi(f1%N^iMMR03%1kJ_V-PW4VBZOQPjfufp`q?8%?i8>P^63B! z5sPo60mql)k_3g0JFrm~a=f}DO&6zv(Q;STP%c<#fE+*Gk#dwwrFG!~XzpfAp8o}O zIBvPEMM_d)#tAgL_3uOrAIOia3k9|G;|K|mv_&m}ZhcdZrnbJt6FB*hJpRj?WQ>R8 zG|BM+;ZVkO<~8}|a0!r8;-HCgrt+l;9>>Jz_P;eQf!u;p4(Xu+Awl7@Egqb1>}c{g zL~yW>XFgl|QG12KBmj1p-2r%6d^iusyy%5oW|fS=s}e%vGdHUcAyB*`QC|NMx*Inx zxsIWxQX`F_G+i5j7<$8t*q!R@v0L*l_;OZ`cSV3+1v*+cz8%Wd2Gw1I!ewaN!3t zj}PR_oIl#zaPXEr%RtN2?Slp!Z0V&aj61a8kA!}^O&@9BJ0h7w z0?p5+x`*yeb#8DS1vOS`mp6EHn<>PTqj8>J;V20-VcestT&}1(>2F;)T zA|0j@K~({R8CFbga|ZcT8vHelOMFb<3I9;`mHpwqAJemtTy*DZT2$H6#h-@?Hq{m* zd0t%-P58;MjzzR5=CbNORzmJF1#rEp2T)()kyq-blZZy4W7)HXgNe$Hxacg(G$A`Z_Y_h0jg) z)|K{k*6Buqo%(zk(`!%L^!st(`~CrNP!v5XmDsM5rc;Bs6b8K6?6zj$+S?_MkoXOb zWvVDAqWzIFdi#)24zLr(ZoFf0vNz(5RSv^6sn$=qqO6>e4jdbjHy!p1!MB z+Cr~b1ZV}<=4Mb*RdF+wvB8!^MBq!URXs7Xwle9y&kuTjox|)NbpGz>?ERetIh)Uh z72K`9sqjSLd8*g`YrZCY;z#tH_`=$X=3@oERH*jql1Ga_vJ{C|LCOpjKFuzrNbH4TBr% zoa|674()086V4yEuth||GqAIoUMiFaX$G&f%Gds!ExY{E5>l#~k{QMjqArZ{fr^6dyZp{r(ME$Q4=CX;O22XOUqV=z2p(|5keCje zY)KrB24l%8r%@A5c4kIdw$VDTK3Bw;F7mmmG-%}O$BvA)(G(N^!SCrsFML~H`l%Od zazo5H@f@~u#8Cebj0RijU$IAAr`I;xFPbTBRMuwW$YbD-j&8=TBn#H{V7bbXiHx{QKni}O4)C?3d-VI*_ttw=c_D=(!^=!Z zpV446?6LcAcRkc*3~1p0 zXK4uQ3EZPsU6NK59zu5>?aCko#3f-rUhw^t|8R5Vk4X=`H!*%S?db{WOt@c*bj1J+ zrlSqw`jXqfPTk|9!PxbK5o>mA>gn70Z7iNI(kCNsuu>`{hstWL`NpEgfqLAyM*#%GsC^ksE6r)s&eB< z$*G0m_}&eouyRenzPP}I9aNO3Axk^-)p(1?w%ts9r25yMeKdZvz}oBJAv(6bFS=Ug zNJrB1{2D=>fFW3j)++%kmG`dDp%-VX(=hy3P6?95_bW_beNj@Hh$w~tb0iO#)Lo&H zjTXI{sek9oMk}q|Aw1Lm6Lr!AJ%*5urqPd_4g6KKa;v_+{WHxs;_ZW~L{y?tF(Xra zf8JDo%h9H&I2%Mi+8?&|6x2!ePDh{COBqFDJZ#IJN2lnzwNwGLG>7ZCDxm_od?G~a z+8BxKX!2CF?K?i^selt|<_U|ctIJ{?nFEF%!fu9dhA+2Pxr^JGbZtzM)U2b&`qFHq#@5qG&Z?nkjU)X!R?mx{V*r_goYnjN z^lRFQ8lZ6PCAD#X`=TT$Re^Bssjs9Orgaq*3&eb zx^Ufm-T20?jT9j@FLa9nNjAVLZ*`iUIz`O}O%IQ;@#i#nx~@0_X!vlXSzT=?Lh?r52(m~P^{5& z#d7d#hp^1Z)nR*wI*BBjCAy0EGPE-?a)mgYKbHS2GbBmybQRYz^ab)FV zHM$we6kwp1EXKm|({;W%inX&8wz?7str>ki{<>Hjy$g9D~e6ny>}u7oShwRfpFuP1a8&TqFIfoHh~7;#4Y z`x>j4(@O3T;ggU<@BrLa)`p5PWj-lB<)P-|_3zYQ$?EbOnsKB!W-tvt zuWCIrb?fsvC)x!7Z7shZVdOBhvUB{km$f`r1}tV^r~`c}|5TMvT_wsbxb2+|#m6U? z`a5Q>ip{*^dM(pCIo5h+`n<0}5o2q#aB^%ilOCTelzjc(tWbPR0y88&!EAEFhbF|t ztMtxFltXji{e8TMerij)2Pwrx>#G*b$lg^qRjLp~45!)OPK34d;y{9N`s@rp(<;x% zC=*`{T7IWpTSebh+*#NHkxT+4dT5e{D(KL`68UyF%e#xQu)|?JcX$0(;!U)b(h`jQ zD#an~B;U{t|*TTyHu`@?ZNZy8+<%%AI=ZA@1olB`prsk7&SziW)dc~ z#n^i(vL28$upm>hp)_vku((W>4l6z)?WMC}hPvh-XP7MUc#9QH3Tdi`W{@oKqCEQ4MzE`K4g79NogtLiegF}dr zABfOwd&O^_h+pSL+JdL!-9tPW)3HJA1EN1jaej}koU+B|j~;dPCS(}K?r7Q%mtY}w z$+7TAt9Z>cgF;Q5@GO*50%z(d$Qrfx%MsvMBpT;U!32&~!tuev{@TFGrI zes5q~``+Dk6x6&(8cUmwIgwXvuV{6#sTW5{7quOPp8MWqTi5^CR+Llc(dRkOj%NTa zK+(U&4+D916jJ`1zE)+lhN=6cm#iZr-aJGymNrqI%|nuRv{GJBzkXnaVyiyh&%FeZ zMfRrp^f^imNjk-WLKv`sF`G@8mL|~v#@V5bQl4VJ6wS0Xl0)%F zHU^a$o2T3W+jL1&x?K_=2ke-#VaR7V%M~q8}7Ov)!q13TJk?(NL zROpzn%yyfwR_buikn32a$alGBD0GZl=DLqtsR*DBQ)8F;14Ckyk#IC}*`H`)Y3gbq zf?A&w#uaL$8N_Q^MaIJIQs}VB{thE0H)(gpBE;0N!oxAF4;hQbBH{Ax*Br!EoRI>g zES||yBZY~N@M2%cLzI<@xWhGfBb{3y3L`u`ASsN8ipN|;;iMeGzMZCwNF5*!ntQe= zN?GFzh#VRps+Z>_7n}?ZG${a<3hiq@j~b#)4-Eoz6w5P<)ta{v4WHaUSaE zazz~nmI8^nx-BX>(9NES=2cwEP-& zQex(7@7B3n?#u?yiRnh*f$C9N6QW7~NdGjXF;Ks%c&Pe1{5SwA{>JH7uve@btm`&W zYl(Pq@zZPbSd2*`kG2$G>zCOSpVZ+ik@T=Blv+BH&>TldDfEY&%z|yx_MmX$Ib!J3 z$r>L&8<^3W8eML0>!3y^DjJsEok0kd!p7LDN>gr?Pa^~oHIr6tGzFNr)Dg?V$or^_ zaq=jHS$kCLgYfa;KDS-A2KxnAS8ik4VeLNQ<(F1kZInR6b(6l6Mk{mWI)`n|apjWk zGSFP3qKb33N9H#df5P1b1>@RiwO$6lxC?lmIjkt-K0nUldR16gooLFtcuWzNlG)w_ zv(|f8SpHyiO1}s*-Upq)IQ1AHNkb5hn%G=Te4Fp zPbuw0G^F?N`=FnIAF#q_kLEL5?bWv$E__A^xh=ka%I@o-eu+s+g7HS6h(w^uK%etBF& z|B(>?$_e8v`Q=H<`Q!9()f0%2BKUQa42vQBqWgugj|mpG8ON!55>T_>ddDQcI=%Cg zv5XJzx^ObCCq{N$7pIVm?dclnIyriwE6sNrYc{E8y?-AqoAX!Ive-ki2$wqp{eU0r*iy|B6PDoEwPuyb!G~cx`QQrra|i)>$gkL&0&-F3U#& zZr{;ab}M~zWa8Y_v-4x@xnLdCN--Q4EUNNU^5e6wLV};`--Gh%YfGXo>d_5$uv(Y3 zi@8leVqe^{Z_RPVr?JCn?4-t_aQ)B|}_0ey4Pw>@W;kGHR)lzb>`XRi7z! z?#jd#Fkk@%1Lb5{$EpdPXX62$DPYlDZ*>D28FLjUBI{D4W7 z{{I~^DJHItyYfu`N~rMFy}e?ShOC>OVnazD_Avx{0lT02ZG^|dF)P=PoOo^idXrY!0#g-u)*$4QrMzlR$b0{555z=6A2%BFHbCR=lZ{7aj>1R6 zEHgl4z;*)_9F&2}8MRvcFTMMGS9p9}$d1^`PE5$n8OFzVN%@ZUgAT`D{1^LNe?Mxy zQ1erMJ2#zsi36XzjslMa4o4(-HdrLF*cK-3Mg5^XhREK|lZAeITIffiq z?65pMe(rk5>;&W#k-Am?0hHHJTO4{pGYYojr+-5NEXuF_W7>)}*cz||YWE&KfcJAW6otILhH zY@iyvH4Qt{Rh(0PGyg`~dVMzTld^HUNww!_G|dJDPHT-D&{oDCAshFhJrq!ATF z96F^bd?XNedkwf`5pwXCtTb?>0y$d`W1p%V?ZMH>>wOF#nX*Wdm0jtzJS6P`Z*fa3 zn5`NpebEgp)mx3Y!~!ZK6^?W>u$K-~u-Zq4t&ta(wh+D9)y>Ds6XiS9m{{LjGn~Dc z*fNMbeP0M+d@Eo}+9CBCU_E56y2q0K3^DVBXO0lHk22a^b=OhaO1EVn!sfGNwZ;5i z{Wr$Eefv~hdqr78NAtKa>upL z0J{H(s)V+0ThfAjZ`7wh1+}|>ZY1viU%+bb9uq|fV|ZUszyHOiP4WjqRRTCCG%`NH zlv>=CQ(Q+E_DZPefuqHRP7nQMwDWAWT0L|v2Ou{5Y&O$Gq zlQRacYeDS7zBWlUlDw&M4QYihr?H49dr)hUm*rMo03KtZ%>KvSWWrj8T)<{oFT83slcQJluyo15!Gah3U!Jx!gVJ@w+DU#%* ztd^$Eb<^Pe{@eF|{+DNax4TRK|Nny^AJS;|&&PKIt{4H5^MmhZ5h*2N*(14F&-;VJ zMQNTTEKaLXBdr5`i4jNU#O9H)AhBKQ@wfD1#lI+3P)A){UR{6unc(Lj*Gnl(%ubdr z66MHZPNA1c=9DFlpah9|*#u2a{$wBb=)?_ZOJ`I}KPh?D&;j$j ziF}#}t8=F1NnJtW0|;fge!re~2*Io-J1Z-;rRv49rOJd`U>evx*55o*$X@cs?_`{K zJkv8YX_-6=Xn*`T-)f+ z>o=VoHyn;?a4ZFBWgQ!{H$%Db-XZq-ky!#QiOcnSl>ia;0;e9{G*rO6*;tEjZw(f2 z?>LelZr4ZRSf?=D?nUd_+g8#S?&7-tf9%ta3d_Z8hblyEv4-O~urQw7NCzl9RJ8B~ z0J0bj0(o-FLS)Bnz))u!fF*#o1Ol`XjoOn2K5%lyh*c}ed(I}3;LRn%X(L34hyXQp zgPYkJiBtpqXS8b&Fib;KS%Z`k8#az9chfy3;5ctc_3@5aH})R}VyhHt|#v(XDh4e)Cj( zSRCLf$IIt0nql$_L)YvBIU|IzQFp+uyMgkcqgMc3i8fD9T}3XJ&EK36R|v#d=#Wxm z#d_pGf*6!vhFqhYlPe3*Sgou5$o+rz}{DHdX7^rGmz7+p;|VfXK6& zC*sVpRG%I|`iDLyhODW9nKi85nr6Tep*;_q45NvzdnJif*!@b?Nyos3W}i+UtKQ?s zN!{2(k2?|o&!kryAzQ`N+hM?AsAvZg`!cdW)&+DrXc1LlmTSblz)E1fj){N*YZn{U z&+V>MM(Z(@n|gOUFG0WX(CLy(M>d6og5fp1^3Z`VwZjgsIP&3k=jA?xEydfRHgECP z;r8XcFmpI;te$q+SE_o(0R~)=;zb!2MVq@T3O7%Klp>5L9gsSlMFTU$a|e?42x4`Z z*(t{+8B28wfx4F{G}20*D+{qh*Xfr(0|`3W0<~=7wWup=L7LLLzX&b+HWlYN#_|SP z5HXnSJ8;e&3VY_bJ*>Gl-5dJ4nvL!75VNLl4@-ckZXjEnU%rs9@kuys7f-dpBGR8O z2A=k-&f9Kk?{txq{Nl;V^rZ8e^R^oejLP}p^>;49H_k4e{O8~$uRc^<$JZ#|W#Aod zDl7Tx>(ASMt?h68YCSCO-+$Wtu~V&L`0)<+`+q=e;Tt7W^X+&+-B%qE#K7#W{Kw^V)J@aOQm0_oRf?8zN_cD&Cya}XHM!yT?tOSlkvRcb;g4lU+mu%;an9!oVCVIR~{8bHyoyj)y8tLTWQDv(WD34K#S4_h2z zPGOoTf>jvtl97HlcJRUL3>ypM0_ef0AqS+=t zAr~XnCWx&JgB}3#9z%0H1kMSY_%UcG$#N^Nzzhbc`OFYCvDm)NlDkgRLiLSqORf?_|H$(r3pu`f6s!!mI*LV&IAbf-Mh z@MA<)q}*$ODG04^CX$D_v4$&AP;v*p%5wY0Ml`EPzA_GNYl;F&fjY%GCg!*e8MYs} zY*g^PX65Z{=f(7&pnpp5I6)s7+jr+YzWrh1e~bN~gF<8nbnN>-{>#DHD|y%;t{LBb z`@`q&iYFkCs;EZk)~MSv_=)d-|IY&$j}C{W6QA_|mSg+D_ttux(>J3=(#NP~fY)9A z2C9eir`s;==F8Q`UvG2rH+pod_1)_vjHeR8(>xMc3vH-aI9_h6tce6wKnoqA;6^)b z^e6~{5!zA&?+IaL$wVfML&4G_lA6=&BBT0PH+ULZqJ|N33XcYf)NvD7kXT1GC_LoD zJdHa|JVl;V29)SpCraX-ASs&$4Ih|HAEV3#vZ)($%d?xnN}_q-N?aqxP!uSjTQDTg?H(D&t{vK;%JIo9RP6D*6g-L)(cpY##+ zFv2Q;j2vzY6yZj9N{B|sEHAe>mT9{~Fo1=yh5=GNuqJ}i6cMJNDwOJX*BVuAfqhAH zTaU87K05`n16P<3X=Nn!BBGrRb&b28TuSl%R}}SB8@|9XKytQRN5#xHJ1^zH6|xZl zB$7MSW=PHzVPiL8U(W8ku?@jpA3-Rwfnil?bK>+8KbGs^aVD!1vUV024oBrEv*Spq z`vK$7r5Cs8bV-W6gehdLTBKY{4GfI{60I^IJo4~v0wY-2ZW@U=ZS@FqX;cy`nMe+j zqe2jX!ncRHIyPuQ{%C?RNm8PRIJGaFPaoTI_|vbCbZy=n`~pQj04yWm`n~20?(cj^ zNkFI);iGMS)3BB~UK2<8(&8w;WUR{AJ;UG4qaPISlYa9Iq&?oe`cURjMV1=m?a!u1 z)er%82a=pK-OngLZocvlioDsc>aeOaZ^+#2Aft&iNP8!P8zBXQ(g46JJQi9Y(RZ{) z?wHBd<0~M>k3e2s72*o5qEv`B={6l@w&qwQsDPyb2v!2v>CEYFp2gS$L8y)=x2q!t zz?C&~f*kMf?h>^RY8#s&@DGd7^9}u8%LC04Jsh3gIVv*wF#=WG;UQ@prbUbaJ)ZEe z15_9hTIRY4UNGX|a%n&tTbPdVW%5R49f#U_Wh6NsL;`IMUSh1Mjbf9Uz&=|oc;9#R zT6LNd8fn}skCIC2jo{BHR;*4rGrNpxJTo2rb2s5IEFR?3V(kR=Jpzj{1pZvYov=$%S4Q>VSRF}M zd>>hC+JfT%kY`DiTgaoSlRVg13v>4wC0BiSX69V-vZ zS6i&2A5b|Krz4qw5*Pt%Vie276jHX`$F_u1+Fu<=VYVutRQ~~m!qCSOPti*7NTo7u z&(DqDA{Ku!vJwD^wn91xEusBoHP=&^hF5^Fg8o`Z)68e<`5JRUIsPZHfB)kj;2<$S z4mFAP@&CsM`*-H=yK!+pbVE2;-S6bLfBhQ``Kt7Isw8UJxbffX1jJF<^x;rDMLThp z(BfVfs-R2x+QwM5#ZWvNp?T6U~N>7*)n0qR`dR zL4eG*NiB<#4?<{7HHD*adSI+M7S7t|=(K_)vs`IwiL6zStPj8?ruiG0B-Uo5eAMgz zQo*)1Xd4({> zib+*j3{+Mc)FQ%XArkUhw~Cb$We}8`+e`mq-3RlsETyGzd2PC9>%v;!BUJB8J&p+o zfF^wS(Hc#&2B~)RQg3yIcFKzMio8s_NYbM!h8MVw7KESvpq_t(X{O zhM;r3ZS?le4tN;orV#=I2X%WJZ;homHm%4*gRCeVs7)JtdD6!!(}) z)>24CeS?93`Vy1Sz56}@kucpi-8h+4dsX+A%ffsuoXJ^e9B}^3U2+RlG5cT(5Z09v z$q?h!Bc`uy`R3>TTGw0`3lAUPyaN&M`B}P35f0Lh@)1deN3ApROMU>P`@Zw5nx34$ zxc|Ie>Q5P7J>UUN_peTktv;h;t9v6--g?TVtL-T}aY% zveG=r&j@(^;;X&F2H4Lro)G3vi1}p<}w>Z zK071#2U73t>@^0WlQ@o+Pe`y}S$4vi>9h|=pIY?8K=Lh>+`{N;`srpGO-MP4Ofyh zJ3=U4gUB`9H@91Eq;EdkY{EFUh7J~r$f!xmsF`C)tkI@&Cuy@Ej$qy&$fsNV)HjXP zaglF9;6*)ad~Z}Qjw{kAk2maM+~9O*6QDNJ=tKKM1%=~MI6h^9=on^yvVvVhESJIo zAi=jG_t+R)s*KPS+$xywTssECFo%u-g0?I7s9aA1q_@Ep1fDr*8t{CiB@k*jN6hD! z;nsWQyagd}2$4W7@_^(iskm%sAhgsE)5r~|Bk&{zX22*x|0HEu%^|&KB`x!Hij%$$ zTHwIS{rp9PgPLaudJkQsh3V}INnh5@cwkND#q94*!$j3tQVX`?s}UN~7?`HSi$`S{ zPh1Zd=JEha9wfd&#DwX4^zDTd$}5?I!{c14KN#2Qk>DHPHa4le9N_BHYSiNd8g}cw zM16q^7+eGpi!1IghZ0U0n#&^`%K6n_+eZec1DBSG}=O8#>_ z9@KN1{;p*@1qxr*%GiAcxExHxStZw6z{L1;{)<^dCSMBbcQ2U@9hPPGW1=P4wk{q< z2)aYAV65^-K;OaD9hX3nU z5rTP&`Axu3Po-LFiuqc-)z8F{w6l9CH9?B=;=4M>RHW0ZEP^$ru9b(|TBy|{VFT1n zo%;1R^Ow_pDsBq@*oI6{&)rJ%!OKtm4pf?zvm#e$3Nn2vaVEjM877UMdsWASDKHB@Js~YjwJ;NA3pLC{ zSi0RuUZkv6j$o>9u6ilbd$Zz&&<1O5X?|)j5t7^{5~Q)&YBpH;+-~@pl5bG=$a7AS z%e|hqkbwmm>M#|>pyv%ebj_WC(9$GpMs7+SfhQ@L28J1|~^LTL>3RK4v~6GI@9< zR;tR6UBQ+}G|Ogp5;<}`4dqY;>Br<+|%Mt>t|He95&IC#_B;{0@pld1~yAd^CSc$1U+O`!TbBx96Ms zMd530Q}p`G!=3nEX{YQ7zm%`xd%ozehxlGG_mz^1mirT^90iVE#BE<>o|pbO3%%+T z4d0wy27en}RP&#k4ZkLHerss)Q!u5-#3faDf<_)5qp3mn_tr3~+U}<3-hf-gR@U+qwybkOoY;wI|PPhjz2JbggBz}-yC6)B64t(MGA znPpGHe7=Rd@|7`?)6ZyOG462YZzu0AgcJG?t3}dvEvVu;mBzO%We{GKIG$L+LWq&v zbd(tvRg*0}Lj*f6JVdz*IPwfIrS+=JU~An~<;PZn4M9dqzGfTIM4^XEcYFnaVL5gg8O8(ZPc3guCB_0+h$LammgI zad!AIW^R}{;h+2f#PFj8{YmVtwUFC3WbRn~5ld)k+Ii;M=)AmTDOEFO$Y*T03r~b5 zXW=7dNcC=Y8ZOSp<@HdpbGJD2wDhZjC)%x@_;Ap@8Ca3C7iFZGn@(hs8!;b0OS80* z6KMBD#AU4<>yjIVldtTLB7-C%monzW0*0zvP;6Z*W7SO3AV{m47Hg1y$qcTZC^VkO z_UpDfgYPjCz9HQMSpMF{w6Bd?@JJ~b0u)g^{eMXDg}bBY3iVjHTri?f*~pj!Q`OER zLkbW;jpKL^LJScKhF7s+F#jnvB5a^2tm8=YX&_Ho1 zupXrhgg_kz;r(2Tz@)=)az>x#r0zqj`*4RtVC(`3Ze7SuCIM+Zy31mS3HIf*N*}7H z;eq6-J7l_@=Aw|^Lv$*BU)whuLZ9TYGG(nxk!t9f$;eC(u(A5e z_5u1>!_%HM*OaOpwT)2VyTJpu?P6-_Y;P2H#v~WIEU_^5p4?LG2=HGwA^TYe-GqrX z_53p%Xa=5Ud&hkaxdVnRiBu;U`(iDM`o-&JGBwUA`y+XL5ZA};y|JFzI^q=IE(&3@YEY zX150z0#o!Y@F>+#CB(@+)Ji-wWco)~5GmC)qo1|om$_`Rna_TD_ z*q)%En;6G8f@A3Z&|4pKH$`%3rAPqtVGZ!~i&K@WHJfhh6SD^OXSvYSvJR6Lbpy%a_c!_w}@*oah;->=EIw|@L*z| z&p?*b$uk`?r6Oa9mqgb;zzalGCixW;gNiA}=MA#C9s0cS4ZO=H-hCqadiqdlRe1-t zL~0)}{O;+CptI#bHRJB5Ss&;aE{QjTa(zmB zSI&VFFFp_)j)RF%)cRg>S+>fWlR^vJ7&^Omn|%1F~toTiF<#cUKYg z8Y3mSV6&Bw9VHS(X#LUXg<|eJe-vA!tZc4C?zpM(x>u)CcBq(S6)~Awk)nBDPc(HQ zA?=*g(Y{8AV30xS>OwIzl@;b|AA%IK7;v^TcwS#G1AlJl5q2J)XIe*>kg65M(dc0Ppe4T82u<+Iq?vKGww7D z8Z)U-6`$8b+gBky7XQzAA>_XKn8|6i$lqZi#f1lHV2==$p~)k zsX{9-Rz-+`mU~)AY1xDfwN+s8ns*GKR7m%kgj_$4fypJ8iY~APwiq54m;gX|(SmyFOn49E8ipFK5s7hQcZxFe@`zvq$B@!l-mE=v+*TEg;eW zba}zC;E?oIM|hh{2?jx&keAmR=In^0rgE-b8DBVLqkvf*7lzz$P=?&NS4KHFc1kFR zM8iQHji^&sE;zc4;u9C8S+YT6n}cPm-zgUhA|~(IBwcAIbowS?902w0Imc^me9}h; zE1WuY&CX|_Js{~e{n60+KsYqTU$QenOW5hq#2_Xhv5NvwUT%Alb%q&fg!@XU;_%@% zhjYvdN3%@+rbg6KOUq4ZG!Aen#=ihgg|hC=LYHcqa*CVgPzS zm4hfRWgqe?AQ23mofddmmz6QqD+COeFexadX#I8>5!~68PKG!=4^bVt4^WA{yw6f? zClT>8BqA2(A@TZ}*_Gm^YO~)jIAUzmoMHIF6z`5(Je(F~d}>X05vVBTAl>ndOuH~pi^xqNN4rvl zRA1yWjl7@SZdPZfily0vi<2-rHrq3fn9)RPjCE8b8p?P($y=lAF(3zl&dsqvNS5d6 z88h&gn4pe*H=$_1geb;$budI>Ta(N$C4=fG3d5;1TfN*R_HHhE+-R$*tD!X%&WOs|OW8+~%akP@vrQGBPnY4@y>+ZT{OsN0x{`?7E zMr#>W!?-)1tj^E7&rVi~xe`R}bx2Gw2HK%42w$})Fa6ebFG|+eJGmOg6lqvY zNT2)H$E&)~XLHDYPIp_wfM@Q zk!8zPMq$n4hyOd5oXi0eWg4ZAR~5jv%C`Y7wnRV|WNcS4z#&>;(NGs(gB@#z-wO8< z$YSYO1Jf2hm|n7M@i>=!qDpn8n^DGW2^IfO9^XH`v=h##L*HGE(X+e;v0%{=5HK7pfKyBg)Be@%=1y1Sm-`GCWi@by=lR3$0nnf+<+LW z#J)H5sE!=McJY0HF!KeZna+|G_OsJRV5FPi_-L%gkiNOUykX}p5cNhwNCk3ZKVtO+ zUkeYhYMce?#tyTyYxLFO{0&@pmARh{f2bq71D4^A-^qvifd1VhFMTI;o@+cDQ6y3E z7gCQdO5cdBw&4y5)cwGBNY<~Eli>$6TwQONu|st9Vs5>=JDy&Xe!9#gwInT;f=Iba zh2@_g!qYfFy!R~8#~@?tAv-eI*aO~qC+vOnwY&LiGLbE7m_IHz%53f%jEGYZ?E)A1 zs4peC$6bGraFuscoOnIwTM}N1^R1_UjK|qj6ljE{kZQm#~oBVD;2UDE~t|F6QSz;;YO~Y)jc@vNH zKXZ8}(~k8tM1)?l{rNo^r*i2t%wg$zTiRDNB^i zt@{X?I&B4oCr&p1egN~x zY+byginN**7SkoJC?r1oLZ?t%KOiiBoAUXgUlwqA|?LP zxCcqHoc4Mbvw+FI^tS+#nQ2{u?Lbq}iqIum;l-+b<4hGYXHKNWnlqmg^In5V%_qVO zvq)osTTTY*GJh5&UXN};q@v7F9?uYJDiAv8u~D!pz1ns(Hmg^SI$EuWqva6W`cc(L zN&Su#aVPL13fGwDy3wUcp z2nHh1qs6{@P%M-fF7P)s&nHh&OyLQHhht^~iRVJ(RVabbO!hTCQLUe@2e$Up4R?Ah z{kRt?&`tNmR6?U9@H-MiJAZ1UXEy@Aey@CMwZ@4jc{IAL6v^Yko$hiqhk6e9vw5_$ zjKnGCG@5Tg|B=2FKR8*!`$81_xpzJhAJy(xdkEvnt9Kv74nYTd5ZLWf*miILld373 zGe$Wo6V1j-1|phA4%h8eolRS8Yr=uno`MQCkKqadX07DPNiVt#5#)*30wrnz!h9?f zCCWh|9v>0ncTKXp+d5 zmz7}ci3y`yiGXSq2H6NSG#@QNn62*BiiSxGNc9I_y`ceZ`@l76MzG%1#wWcQ2eZmH z?a;j*o**|P7s9sxRn#uPeldu_Ra*fMW)$Bl(CKnOfEk$gz4*%REDYss@z#T~&QUfqBoFP+ZWiFWMmdvW8e>OJ84GU(~i-6P}%lcZ{Zf9;u zDge|w#5GW3QE6#f!WbcX(~Y1JUSc7>+5>_v*@=}jS@7v-D!*vsvi1(L7ol=dH)V&e zk}qX9OgsXW4f2YV9z!O;=gw`-x3Y7aT;lf27H`zlm0i7T)WTrCX!!PrUfxXW&xR{} zUX06+Uw2*Q^i`Xon^k5+FQ{bAD588_A`7r~)+e;bG3Xk6U3o@(FWwK(vjN#+uEMH( z&HH<85%-%sB;(r1<@-il@4xlar3et+mO+)qSTO=Sas?>;f;9HWJq)>mh`tlZHzQFM z+}nRF2tgTu13LyK_gDy4q>GhbKb{UU{qp!onRyk-769ec$ydjy3Y;>ZS^9W z0C;|qIXk{IgcR1HYatsTI43$H_#6R!tfwRGs&QAs9>Ed#fmCe+m5rcD5id*#=I#Y) zz1Jmlfn5?#8-Odu!>)XViPm?Mg5+AMD-2qbz{0YzC1MnB`y6F|8C_rt>;h=wZVpU9 zHzD&TL<;P>G#fK0do`Z*-Y|e>P-6=-!x|fmO;oY5WfKcZ!@!rPsX!v!*a05;h%YGx zQ?O;cA&L=ZMuw(o78rA&Tk zbm+1D1(S9k?%ARRzAn7Eck^hB@FMmiISe;@VBR0QBX;r2Vbzycy_Xo})xFg3DW)@z zNZq{G1O0k`4X-h!8kerXx_kk9DG#xA6xX=RosFMtugdE*WC?}^6Zw})jICuNYrKo$ z8wMjHk5eyU$mYQ4-_iOO8h`VAS=a4HJ*eDXR_P>Yr&V@?=>>A-ij=)8>tgByjxuzIp}z?HliF}kQd^Y zH{(<%rsc@8nsBf}z(-iL^ib1~Dme~jHUD=xFg?gq^P8)D!ec)}bgD`dDawMt)C~+J z17!_#$gs35dMI_@iN?c5+Tg3e_vFfoi>!wwL@YNjtCvO%VNDTBlI>-DpfV+jnwDyz z6L~Qu60M_0O4xTftAl|j$17oKtIG4E_83*|zMFWmNEF`>BX)V#WPbAZxdy6ZZyKFsMqINni8YPbh6n}2Jbeo3Nb<03@D1Fz`!sUb&viS^c8CR}EWdjii#Kzt9d5 zszFVb0F2?uXtIS;I1d=ao~DPTL^iLHqiBRQKWs*84sF({7;zQcW_PmmRnA<7Lcm(W zMCM(B4^E$77`S*y3X%mACT&uzU|l(C#wH3;wALp8$qASp`)9s8gNEypo)gX=1wIso z=XbEn`F@BqX_)t4y=p{~SR3GFA1;OAvx=W!Y#j3ZVA{fhz*0;HnC0u(A+>m}tqH2C zeoCTsr1zu;%RA_4wb#eyq$aPwV``3u$3=n76v}JpDm!5oXC3Zc;!^32mHE%W5mRBs zkmIVWit$)_k(Wf7@Ad!{!f_`{HhFfOOq0>(ZKGV@_36?-QC5>#!G@=WTLnMGG9^Q; zPExkene2w&hb9a)y&2H0j#KwVS5JbKdj`%JG`O1`x0}B)5+}2Qow_AczIgi%#H+q@ zwCv6x7I&1mFesgm@u7EI+j{J0)YufAb|kgFCnc=au(~6I$QNRw#2BRosP9hK zuF_5&+bPJgX_&Kz|9IANCdM(AR8B4&Z%S@44)noZDwu8E{*ufw4COdN=~pto1c(Aa zr27-8cCfTPpv!0#lui9HH<6KiYBuVXopU0`k=Ja_<%1Y48sS`nZw|VL!kJ=XnS}jWafuzgzR0ht$r7S3$s~-sM;PLqp?2Om?fG+Ut?2E z#!r))P%|qTp~`zkDCTf42awC`f`5+Yn(+fmz+yBSfD*uOW|+=_WMn`qT&Ydd%-7S*#CIitA} zHYfyL2;&H|W8%bR>r87iXr$mij26FcjZb*DCQe|AYSxXMj%5Y{ONw2bl+8Wx)Xf;) zr{Ea$7Gf@j;SU5yOT9*PonTrQqI5mW$8zi_hJmp-X_F1TX?#*!BI!Ck?+dgwv4Kbz z$4Xtd}&qmx#NDsA$m4Aq#JL3-jiHRF_L??T6gq#hC)RLn%hs6 z>dyMWF-=vXFLJ5}c}|VGvXVuuRoH52+&O3n0fK#CWGhg}nuG!zh>QM@Eg>+pTJN+XEGq zb|?6|Y0-FHKCfq%>IvPKom~I3il)*4_Q%s>RG7XQ_zu>C)}$FbHMO-MdV0&SMX*M= z@;T_uv{%<+#h0QVuVK%8%ua^5!{K!9`qv^VFHb&d-Y#YLUP8VT6K5jxaqz`G(Y>Rz z-T&e{(vKfh@zye)LbKTV*?@)xNt5=2dp*P9`coBq!u>igTuW+)?SjZ;Tpz$WJ6L)Y z3$F-HzGwL$`DSx-pzkvP6yBuK4E9F(bbYRYuF2;nV-;`49Ovz}n}PYftz1>lI>a*u z>l{JdO4{dr@K%Phl1bq~AFl3v0csu`-!x@ezTz`ADu-q2EJ_?(BqwqwF-HPH^n(P5 zLPrymcxqEk6uWNJb81F{$)e&_R$28hdZa<6x1>64vGI+R=gpH9X1{Z0_Hk39vvI`y z7tc-EafskTkMFBs3J0VL`rTX!aX#yzV&;eCXryP@z>Wj@h=WDlCLLiNrRP~e$(HfL ztoxvfZ8nK_LC?U#rx9acKxvQ@#f-EVxE=1anGZ5UxkD$4W5Eula1W{rCay8`JXe_3 zFz1~|9-g`bw})1EKP1O{ZNhyu=8iSiwwW-OkiXIhyzOvGpMxzjwugFY-Se=#gb(nL zenwz!s9b%j8(#LUXX5O=aC58H)TK#pWG=at zU1dJ>?y>)PS+@7Z$wPj`nI+r~_mcUVePQUm0BPXztVw(VdJ2-Z?o)S|!JDo(?5DSV zT?(AyPx9vH#c(f=t71N(ekflbi7EOv!E1Vk49?8<@j$Pc@7bH7k8z1`#d~;9=iB-6 z>Cg;(Ah;|Bg1;HtdWhaZl3nAz_c$Zg#_sxi=qQoaWCxkxJ3T`Nv0D|nUePlV=Rp0e z;|_3x4tp;m68B{WqoGr#8$N~f4AQBoV;7JZxe`_qjgpB}h}_n|TJolb#Yo|2IEqGr zgYLYsG9?A>YK|FVrZDFfXBaQCG@igjpBHM-~q! z?8E;G7>t>#5IQuzcutfkY0~1^xZ{Ivj^&WA5Y2c&UQ}Z$hL=#7QFV#9LfTd zPQ)BpS&zl8o^ZI0Qr!WYi~BSTFLXbp-@ok}xl56>yO_CBk}T#YuwZOK5@*s@qD04GJ1mgI;zm{D zIOXC!w)LWvZt=GzTEgg0dbz!sg6eGe1TS z?&A5h1r_gX5hla5YBRq3psU)ea_Gw3vV)MR^IE*!1Jo8u_7ID zn%gte&9cA=`YkhQz31y#r@Qk0<2A|DGY%)Et@#JRka}0H}Svux`<@6 zf~n$03kAUldER{6CPdlN9yXiL77^WCQxVtCjxu~DLlSARWRa&mv99Impk=e#k3Cp! z{*rAAgyo5y+BD;+BIp=-mMNAW1NwgN{E!o<$Hhtg_dyB1YDu)~ZCYzzxu=XButv1I zqo@TWBQus*%!NbiPg#{%VEsF8t~9&J$cP;Iyol4OVtK6ILEu9q zCPRjqd|!rczY*B8mHr`F@dN+~_|xBd)u5Z3&EFGt08eA<757saA3(;RDg^w7!c_#| z$N#tX)Rk+@G4Vun43M2;2kLy2@dhgY`%2j6qN3IpUA5Y8xXcAW%b{xcm@H>(rdNQ> zIvK=mqfYv8qt|g9)KKyWQ!m&B*Bhl1pU;XRH@k#rB zhStJ5pt^SYw%N`;4@;dz3iqE=RlwaOQEeQLKb3yYVrmPkh%6@2(#H(vz5`klGfpi| zOSRwXrL7%AkLd{d^69)fT{G9nt5EI=(9^HD@eQNf3SUeBFA&gT7;HtG2>m0`WIG0+ z1p<^tU!WK02lj(J0U!X+(KZ1>`7;;mOngdWRnrs2l_gO#&{n zcKCgJ%^AXmAADn za0g2Q6N}{*6sbbRlE%RrdIWIT!S$TIzMJaoH~7F1aA%$R2t%?;HVAsJKyUKxglWB} zV9;kCNAcAA+?I!oNirHxi>z8sTkMB(n_B|Ms)1F4WDapL+50g78wR{%tgW%diWgpg zNu0*91wl&~7E2Vifv8+#CVEMB@-x8jc=;2@@p|5LbzUP`)A7_Qp$ym(dwn{X&h<(} zrP^9&e3`Xb>0I6~IlCLqIocSHLNQ4{ zRU~G8%76mAs{^oM93{2Fmk;TRSJ|uB(ZT)!p8P`luJ}lUdEy zNL5X$Rf*B;@}N3Z1g)r4vB*)af>t()rk;yBX1rZAfm#TcX1`@)x-@F$mrs48-wv5u@N-AdMm$$B__rb6X8iMbAA?ivp)A{Z#m!caP5K|-3 zOiTytT`FUPXraYI6+^`gnhS~9-=9iSPO|FloS73# zctVx;5+gULR6%J(bSA9zaob%^j4s7&H7RIDEp!vQ)}Er6Po}vhBQbh^Y9Li1x6DFH zsaURjO|{yFX!vM5I%@hB!5W@{Olvk(gAz6s6`!zuu}rcQaXWJHOGc8dB|R0_B+7;ud5gIADrw2LJ@imh#pEnSYnvrW|&~CRpG%HZgf;Gdgh)~9)T;(S#Z-%4v(pz8s4RF`2U~TNGfb@Tc`KXc{i7+!)Yb-3StZi)V>>V7PoLvAAf)NzM z36i22mg5Cck`-0c4b!q6*Ykrgijy?Ui?XVlw(Ey+nwNFkkMp{p_xtnm3kr*hOG?Yi zD=Mq1YgWH$??m0@!)a)2I@x@x<#g+rvu)?vJ370%d)B{b&_6J^3PwfRA08PUyR)2t zZ>gyZ(-$wzT)uMk8VJD%is1xF(G1J+f+)#~s_BMl*^cY^K^VnJn&m}V)lJ*=!#K^$ zy6wk#-Ou}#8*SX8AEsqHuFvzY{e-dqe0t9UAs9h1oJMCbS!_=KHr`}Y&2yS)p`-zB z|8W0^5w#{{Ay7Q<#(9GhcFY>AaH zlXI{VCWWPiftP2cl0WYLvKHFFI0}-=@wuMvnZ0aa90f_`_*_q?H2?qr000000Aq|X z#u#IaF~%6@oO8}O=bUrSIU*t=A|fIpq7Xs|A%qY@2q6R!5fKp)5fPD;Qc5W|6O`j~ zJ)M?HDW#O!5(Je}N-0Hq00000002pnBuSE_BuSDaNs?{bwr$%sV6C;*T5GMf)>><0 zj4{R-V~jDz)I8I#wZI0(QIJ%9X0Px5r$u;bQW%4t9P$3ahc&jI^Fx-Tc%1)rZsQ)F zp0%(w7xNt@KP9(FSnX~VoYE_mz>U&-W+#7hyGp>0fY0*#ho_ oGWl%PbjWln;nl}T|Ni^^xixS)2|G|m_-_wBj^0r~ocQB+0MtY-5C8xG literal 0 HcmV?d00001 diff --git a/retailcrm/views/fonts/index.php b/retailcrm/views/fonts/index.php new file mode 100644 index 0000000..6a1c957 --- /dev/null +++ b/retailcrm/views/fonts/index.php @@ -0,0 +1,8 @@ +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%0Do%n-aD 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 literal 0 HcmV?d00001 diff --git a/retailcrm/views/index.php b/retailcrm/views/index.php new file mode 100644 index 0000000..6a1c957 --- /dev/null +++ b/retailcrm/views/index.php @@ -0,0 +1,8 @@ + + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +(function () { + class RCRMCollector { + init = () => { + this.getCollectorConfig() + .then((config) => { + this.initCollector(); + + if (this.has(config, 'customerId')) { + this.executeCollector(config.siteKey, {customerId: config.customerId}); + } else { + this.executeCollector(config.siteKey, {}); + } + }) + .catch((err) => this.isNil(err) ? null : console.log(err)); + }; + + getCollectorConfig = () => { + return new Promise((resolve, reject) => { + fetch('/index.php?fc=module&module=retailcrm&controller=DaemonCollector') + .then((data) => data.json()) + .then((data) => { + if (this.has(data, 'siteKey') && data.siteKey.length > 0) { + resolve(data); + } else { + reject(); + } + }) + .catch((err) => { + reject(`Failed to init collector: ${err}`); + }); + }); + }; + + initCollector = () => { + (function(_,r,e,t,a,i,l){ + _['retailCRMObject']=a; + _[a]=_[a]||function(){ + (_[a].q=_[a].q||[]).push(arguments); + }; + _[a].l=1*new Date(); + l=r.getElementsByTagName(e)[0]; + i=r.createElement(e); + i.async=!0; + i.src=t; + l.parentNode.insertBefore(i,l) + })(window,document,'script','https://collector.retailcrm.pro/w.js','_rc'); + }; + + executeCollector = (siteKey, settings) => { + _rc('create', siteKey, settings); + _rc('send', 'pageView'); + }; + + isNil = (value) => { + return value == null; + }; + + has = (object, key) => { + return object != null && hasOwnProperty.call(object, key); + }; + } + + (new RCRMCollector()).init(); +})(); diff --git a/retailcrm/views/js/retailcrm-collector.min.js b/retailcrm/views/js/retailcrm-collector.min.js new file mode 100644 index 0000000..747f793 --- /dev/null +++ b/retailcrm/views/js/retailcrm-collector.min.js @@ -0,0 +1,36 @@ +function _classCallCheck(a,b){if(!(a instanceof b))throw new TypeError("Cannot call a class as a function")}function _defineProperty(a,b,c){return b in a?Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0}):a[b]=c,a}/** + * MIT License + * + * Copyright (c) 2020 DIGITAL RETAIL TECHNOLOGIES SL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to http://www.prestashop.com for more information. + * + * @author DIGITAL RETAIL TECHNOLOGIES SL + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */(function(){new function a(){var b=this;_classCallCheck(this,a),_defineProperty(this,"init",function(){b.getCollectorConfig().then(function(a){b.initCollector(),b.has(a,"customerId")?b.executeCollector(a.siteKey,{customerId:a.customerId}):b.executeCollector(a.siteKey,{})})["catch"](function(a){return b.isNil(a)?null:console.log(a)})}),_defineProperty(this,"getCollectorConfig",function(){return new Promise(function(a,c){fetch("/index.php?fc=module&module=retailcrm&controller=DaemonCollector").then(function(a){return a.json()}).then(function(d){b.has(d,"siteKey")&&0 + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +!function(t, r){ + let appendJs = function (src) { + var a = t.getElementsByTagName("head")[0]; + var c = t.createElement("script"); + c.type="text/javascript"; + c.src=src; + a.appendChild(c); + }; + + if (typeof Promise === 'undefined') { + appendJs("//cdn.jsdelivr.net/npm/es6-promise@4.2.8/dist/es6-promise.auto.min.js"); + } + + if (typeof fetch === 'undefined') { + appendJs("//cdn.jsdelivr.net/npm/whatwg-fetch@3.0.0/dist/fetch.umd.js"); + } +}(document); diff --git a/retailcrm/views/js/retailcrm-compat.min.js b/retailcrm/views/js/retailcrm-compat.min.js new file mode 100644 index 0000000..d9faa89 --- /dev/null +++ b/retailcrm/views/js/retailcrm-compat.min.js @@ -0,0 +1,36 @@ +/** + * MIT License + * + * Copyright (c) 2020 DIGITAL RETAIL TECHNOLOGIES SL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to http://www.prestashop.com for more information. + * + * @author DIGITAL RETAIL TECHNOLOGIES SL + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */!function(b){var a=function(d){var e=b.getElementsByTagName("head")[0],a=b.createElement("script");a.type="text/javascript",a.src=d,e.appendChild(a)};"undefined"==typeof Promise&&a("//cdn.jsdelivr.net/npm/es6-promise@4.2.8/dist/es6-promise.auto.min.js"),"undefined"==typeof fetch&&a("//cdn.jsdelivr.net/npm/whatwg-fetch@3.0.0/dist/fetch.umd.js")}(document); diff --git a/retailcrm/views/js/retailcrm-consultant.js b/retailcrm/views/js/retailcrm-consultant.js new file mode 100644 index 0000000..c320298 --- /dev/null +++ b/retailcrm/views/js/retailcrm-consultant.js @@ -0,0 +1,91 @@ +/** + * MIT License + * + * Copyright (c) 2020 DIGITAL RETAIL TECHNOLOGIES SL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to http://www.prestashop.com for more information. + * + * @author DIGITAL RETAIL TECHNOLOGIES SL + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +(function () { + class RCRMConsultant { + init = () => { + this.getRcct() + .then((rcct) => { + window._rcct = rcct; + this.initConsultant(); + }) + .catch((err) => this.isNil(err) ? null : console.log(err)); + }; + + getRcct = () => { + return new Promise((resolve, reject) => { + fetch('/index.php?fc=module&module=retailcrm&controller=Consultant') + .then((data) => data.json()) + .then((data) => { + if (this.has(data, 'rcct') && data.rcct.length > 0) { + resolve(data.rcct); + } else { + reject(); + } + }) + .catch((err) => { + reject(`Failed to init consultant: ${err}`); + }); + }); + }; + + initConsultant = () => { + !function(t){ + var a = t.getElementsByTagName("head")[0]; + var c = t.createElement("script"); + c.type="text/javascript"; + c.src="//c.retailcrm.tech/widget/loader.js"; + a.appendChild(c); + }(document); + }; + + executeCollector = (siteKey, settings) => { + _rc('create', siteKey, settings); + _rc('send', 'pageView'); + }; + + isNil = (value) => { + return value == null; + }; + + has = (object, key) => { + return object != null && hasOwnProperty.call(object, key); + }; + } + + (new RCRMConsultant()).init(); +})(); diff --git a/retailcrm/views/js/retailcrm-consultant.min.js b/retailcrm/views/js/retailcrm-consultant.min.js new file mode 100644 index 0000000..5354d46 --- /dev/null +++ b/retailcrm/views/js/retailcrm-consultant.min.js @@ -0,0 +1,36 @@ +function _classCallCheck(a,b){if(!(a instanceof b))throw new TypeError("Cannot call a class as a function")}function _defineProperty(a,b,c){return b in a?Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0}):a[b]=c,a}/** + * MIT License + * + * Copyright (c) 2020 DIGITAL RETAIL TECHNOLOGIES SL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to http://www.prestashop.com for more information. + * + * @author DIGITAL RETAIL TECHNOLOGIES SL + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */(function(){new function a(){var b=this;_classCallCheck(this,a),_defineProperty(this,"init",function(){b.getRcct().then(function(a){window._rcct=a,b.initConsultant()})["catch"](function(a){return b.isNil(a)?null:console.log(a)})}),_defineProperty(this,"getRcct",function(){return new Promise(function(a,c){fetch("/index.php?fc=module&module=retailcrm&controller=Consultant").then(function(a){return a.json()}).then(function(d){b.has(d,"rcct")&&0 + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +(function () { + class RCRMJobs { + init = () => { + this.executeJobs(); + }; + + executeJobs = () => { + let req = new XMLHttpRequest(); + req.open( + "GET", + '/index.php?fc=module&module=retailcrm&controller=Jobs', + true + ); + req.timeout = 0; + req.send(null); + }; + } + + (new RCRMJobs()).init(); +})(); diff --git a/retailcrm/views/js/retailcrm-jobs.min.js b/retailcrm/views/js/retailcrm-jobs.min.js new file mode 100644 index 0000000..5525942 --- /dev/null +++ b/retailcrm/views/js/retailcrm-jobs.min.js @@ -0,0 +1,36 @@ +function _classCallCheck(a,b){if(!(a instanceof b))throw new TypeError("Cannot call a class as a function")}function _defineProperty(a,b,c){return b in a?Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0}):a[b]=c,a}/** + * MIT License + * + * Copyright (c) 2020 DIGITAL RETAIL TECHNOLOGIES SL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to http://www.prestashop.com for more information. + * + * @author DIGITAL RETAIL TECHNOLOGIES SL + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */(function(){new function a(){var b=this;_classCallCheck(this,a),_defineProperty(this,"init",function(){b.executeJobs()}),_defineProperty(this,"executeJobs",function(){var a=new XMLHttpRequest;a.open("GET","/index.php?fc=module&module=retailcrm&controller=Jobs",!0),a.timeout=0,a.send(null)})}().init()})(); diff --git a/retailcrm/views/js/retailcrm-tabs.js b/retailcrm/views/js/retailcrm-tabs.js new file mode 100644 index 0000000..f980b96 --- /dev/null +++ b/retailcrm/views/js/retailcrm-tabs.js @@ -0,0 +1,240 @@ +/** + * MIT License + * + * Copyright (c) 2020 DIGITAL RETAIL TECHNOLOGIES SL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to http://www.prestashop.com for more information. + * + * @author DIGITAL RETAIL TECHNOLOGIES SL + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +class RCRMTabs { + currentTab = ''; + withHistory = false; + callbacks = {}; + + constructor( + tabSelector, + triggerSelector, + activeTabClass, + inactiveTabClass, + activeTriggerClass, + inactiveTriggerClass, + dataTabId, + formSubmitTrigger, + initialize = false, + withHistory = true + ) { + this.tabSelector = tabSelector; + this.triggerSelector = triggerSelector; + this.activeTabClass = activeTabClass; + this.inactiveTabClass = inactiveTabClass; + this.activeTriggerClass = activeTriggerClass; + this.inactiveTriggerClass = inactiveTriggerClass; + this.dataTabId = dataTabId; + this.withHistory = withHistory; + this.formSubmitTrigger = formSubmitTrigger; + + if (initialize) { + this.initializeTabs(); + } + }; + + tabsCallbacks = (list) => { + this.callbacks = list; + }; + + switchTab = (tab) => { + if (this.isTabExists(tab)) { + this.activateTab(this.getTab(tab)); + } + }; + + initializeTabs = () => { + let initialTabId = this.parseUri().rcrmtab || ''; + + $(this.triggerSelector).each((index, el) => { + let $el = $(el); + let $tab = this.getTab($el.data(this.dataTabId)); + + if (this.isTriggerActive($el)) { + if (!this.isTabActive($tab)) { + this.activateTab($tab); + } + } else { + if (this.isTabActive($tab)) { + this.deactivateTab($tab); + } else if (!$tab.hasClass(this.inactiveTabClass)) { + $tab.addClass(this.inactiveTabClass); + } + } + + $el.on('click', (e) => { + e.preventDefault(); + this.switchTab($el.data(this.dataTabId)); + }); + }); + + if (initialTabId !== this.currentTab && this.isTabExists(initialTabId)) { + this.deactivateTab(this.getTab(this.currentTab)); + this.activateTab(this.getTab(initialTabId)); + } + + document.querySelectorAll(this.formSubmitTrigger).forEach((form) => { + form.addEventListener("submit", (event) => { + let target = event.target; + this.storeTabInAction(target); + }); + }); + + if (this.withHistory) { + $(window).bind('popstate', (event) => { + let state = event.originalEvent.state; + + if (typeof state === "object" && + state !== null && + typeof state.rcrmtab === 'string' && + state.rcrmtab.length > 0 + ) { + this.switchTab(state.rcrmtab); + } + }); + } + }; + + storeTabInAction = (form) => { + if (form instanceof HTMLFormElement) { + let baseUri = location.href.replace(location.search, ''); + let parsedAction = this.parseUri(form.action.replace(baseUri, '')); + parsedAction.rcrmtab = this.currentTab; + form.action = baseUri + this.generateUri(parsedAction); + } + }; + + getTab = (id) => { + return $(document.getElementById(id)); + }; + + getTrigger = (id) => { + return $('[data-' + this.dataTabId + '="' + id + '"]'); + }; + + isTriggerActive = ($el) => { + return $el.hasClass(this.activeTriggerClass); + }; + + isTabActive = ($el) => { + return $el.hasClass(this.activeTabClass); + }; + + isTabExists = (tabId) => { + return this.getTrigger(tabId).length > 0 && this.getTab(tabId).length === 1; + }; + + activateTab = ($el) => { + this.deactivateTab(this.getTab(this.currentTab)); + + if (!$el.hasClass(this.activeTabClass)) { + let $trigger = this.getTrigger($el.prop('id')), + $currentTriggers = $('.' + this.activeTriggerClass); + + if (this.callbacks.hasOwnProperty($el.prop('id')) && this.callbacks[$el.prop('id')].hasOwnProperty('beforeActivate')) { + this.callbacks[$el.prop('id')].beforeActivate(); + } + + $currentTriggers.each((index, el) => { + $(el).removeClass(this.activeTriggerClass); + }); + + if (!$trigger.hasClass(this.activeTriggerClass)) { + $trigger.removeClass(this.inactiveTriggerClass); + $trigger.addClass(this.activeTriggerClass); + } + + $el.removeClass(this.inactiveTabClass); + $el.addClass(this.activeTabClass); + this.currentTab = $el.prop('id'); + + if (this.callbacks.hasOwnProperty($el.prop('id')) && this.callbacks[$el.prop('id')].hasOwnProperty('afterActivate')) { + this.callbacks[$el.prop('id')].afterActivate(); + } + + if (this.withHistory) { + let uri = this.parseUri(); + uri.rcrmtab = this.currentTab; + window.history.pushState({rcrmtab: this.currentTab}, this.currentTab, this.generateUri(uri)); + } + } + }; + + deactivateTab = ($el) => { + if ($el.length === 0) { + return; + } + + if ($el.hasClass(this.activeTabClass)) { + let $trigger = this.getTrigger($el.prop('id')); + + if (this.callbacks.hasOwnProperty($el.prop('id')) && this.callbacks[$el.prop('id')].hasOwnProperty('beforeDeactivate')) { + this.callbacks[$el.prop('id')].beforeDeactivate(); + } + + if ($trigger.hasClass(this.activeTriggerClass)) { + $trigger.removeClass(this.activeTriggerClass); + $trigger.addClass(this.inactiveTriggerClass); + } + + $el.removeClass(this.activeTabClass); + $el.addClass(this.inactiveTabClass); + + if (this.callbacks.hasOwnProperty($el.prop('id')) && this.callbacks[$el.prop('id')].hasOwnProperty('afterDeactivate')) { + this.callbacks[$el.prop('id')].afterDeactivate(); + } + } + }; + + parseUri = (parsableString) => { + return Object.fromEntries((parsableString || location.search) + .substr(1) + .split('&') + .map((item) => item.split('='))); + }; + + generateUri = (parsedUri) => { + if (parsedUri != null) { + return '?' + Object.entries(parsedUri) + .map((item) => item.join('=')) + .join('&') + .replace(/\n+/igm, '') + .trim() + } else { + throw new Error('Invalid URI data'); + } + }; +} \ No newline at end of file diff --git a/retailcrm/views/js/retailcrm-tabs.min.js b/retailcrm/views/js/retailcrm-tabs.min.js new file mode 100644 index 0000000..852cea1 --- /dev/null +++ b/retailcrm/views/js/retailcrm-tabs.min.js @@ -0,0 +1,36 @@ +function _typeof(a){return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a},_typeof(a)}function _classCallCheck(a,b){if(!(a instanceof b))throw new TypeError("Cannot call a class as a function")}function _defineProperty(a,b,c){return b in a?Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0}):a[b]=c,a}/** + * MIT License + * + * Copyright (c) 2020 DIGITAL RETAIL TECHNOLOGIES SL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to http://www.prestashop.com for more information. + * + * @author DIGITAL RETAIL TECHNOLOGIES SL + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */var RCRMTabs=function a(b,c,d,e,f,g,h,i){var j=this,k=!!(8 + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +$(function(){ + function RetailcrmUploadForm(tabController) { + this.idStorageKey = 'retailCRM_uploadOrdersIds'; + this.form = $('input[name=RETAILCRM_UPLOAD_ORDERS_ID]').closest('form').get(0); + + if (typeof this.form === 'undefined') { + return false; + } + + this.input = $(this.form).find('input[name="RETAILCRM_UPLOAD_ORDERS_ID"]').get(0); + this.submitButton = $(this.form).find('button[id="upload-orders-submit"]').get(0); + this.submitAction = this.submitAction.bind(this); + this.partitionId = this.partitionId.bind(this); + this.setLoading = this.setLoading.bind(this); + this.tabController = tabController; + + $(this.submitButton).click(this.submitAction); + + let idToShow = localStorage.getItem(this.idStorageKey); + + if (idToShow !== null) { + $(this.input).val(idToShow); + localStorage.removeItem(this.idStorageKey); + } + } + + RetailcrmUploadForm.prototype.submitAction = function (event) { + event.preventDefault(); + let idString = $(this.input).val(); + let ids = this.partitionId(idString.toString().replace(/\s+/g, '')); + + if (ids.length === 0) { + return false; + } + + this.setLoading(true); + localStorage.setItem(this.idStorageKey, idString); + this.tabController.storeTabInAction(this.form); + $(this.form).submit(); + }; + + RetailcrmUploadForm.prototype.setLoading = function (loading) { + var loaderId = 'retailcrm-loading-fade', + indicator = $('#' + loaderId); + + if (indicator.length === 0) { + $('body').append(` +

+
+
+ `.trim()); + + indicator = $('#' + loaderId); + } + + indicator.css('visibility', (loading ? 'visible' : 'hidden')); + }; + + RetailcrmUploadForm.prototype.partitionId = function (idList) { + if (idList === '') { + return []; + } + + let itemsList = idList.split(','); + let ids = itemsList.filter(item => item.toString().indexOf('-') === -1); + let ranges = itemsList.filter(item => item.toString().indexOf('-') !== -1); + let resultRanges = []; + + ranges.forEach(item => { + let rangeData = item.split('-'); + + resultRanges = [...resultRanges, ...[...Array(+rangeData[1] + 1) + .keys()].slice(+rangeData[0], +rangeData[1] + 1)]; + }); + + return [...ids, ...resultRanges].map(item => +item).sort((a, b) => a - b); + }; + + window.RetailcrmUploadForm = RetailcrmUploadForm; +}); diff --git a/retailcrm/views/js/retailcrm-upload.min.js b/retailcrm/views/js/retailcrm-upload.min.js new file mode 100644 index 0000000..ea7e23f --- /dev/null +++ b/retailcrm/views/js/retailcrm-upload.min.js @@ -0,0 +1,36 @@ +function _toConsumableArray(a){return _arrayWithoutHoles(a)||_iterableToArray(a)||_nonIterableSpread()}function _nonIterableSpread(){throw new TypeError("Invalid attempt to spread non-iterable instance")}function _iterableToArray(a){if(Symbol.iterator in Object(a)||"[object Arguments]"===Object.prototype.toString.call(a))return Array.from(a)}function _arrayWithoutHoles(a){if(Array.isArray(a)){for(var b=0,c=Array(a.length);b + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */$(function(){function a(a){if(this.idStorageKey="retailCRM_uploadOrdersIds",this.form=$("input[name=RETAILCRM_UPLOAD_ORDERS_ID]").closest("form").get(0),"undefined"==typeof this.form)return!1;this.input=$(this.form).find("input[name=\"RETAILCRM_UPLOAD_ORDERS_ID\"]").get(0),this.submitButton=$(this.form).find("button[id=\"upload-orders-submit\"]").get(0),this.submitAction=this.submitAction.bind(this),this.partitionId=this.partitionId.bind(this),this.setLoading=this.setLoading.bind(this),this.tabController=a,$(this.submitButton).click(this.submitAction);var b=localStorage.getItem(this.idStorageKey);null!==b&&($(this.input).val(b),localStorage.removeItem(this.idStorageKey))}a.prototype.submitAction=function(a){a.preventDefault();var b=$(this.input).val(),c=this.partitionId(b.toString().replace(/\s+/g,""));return 0!==c.length&&void(this.setLoading(!0),localStorage.setItem(this.idStorageKey,b),this.tabController.storeTabInAction(this.form),$(this.form).submit())},a.prototype.setLoading=function(a){var b=$("#retailcrm-loading-fade");0===b.length&&($("body").append("\n
\n
\n
\n ").trim()),b=$("#retailcrm-loading-fade")),b.css("visibility",a?"visible":"hidden")},a.prototype.partitionId=function(a){if(""===a)return[];var b=a.split(","),c=b.filter(function(a){return-1===a.toString().indexOf("-")}),d=b.filter(function(a){return-1!==a.toString().indexOf("-")}),e=[];return d.forEach(function(a){var b=a.split("-");e=[].concat(_toConsumableArray(e),_toConsumableArray(_toConsumableArray(Array(+b[1]+1).keys()).slice(+b[0],+b[1]+1)))}),[].concat(_toConsumableArray(c),_toConsumableArray(e)).map(function(a){return+a}).sort(function(c,a){return c-a})},window.RetailcrmUploadForm=a}); diff --git a/retailcrm/views/js/retailcrm.js b/retailcrm/views/js/retailcrm.js new file mode 100644 index 0000000..6e7a042 --- /dev/null +++ b/retailcrm/views/js/retailcrm.js @@ -0,0 +1,195 @@ +/** + * MIT License + * + * Copyright (c) 2020 DIGITAL RETAIL TECHNOLOGIES SL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to http://www.prestashop.com for more information. + * + * @author DIGITAL RETAIL TECHNOLOGIES SL + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ + +$(function(){ + var Main = { + init: function() { + this.player.init(); + this.tabs.init(); + this.uploadForm.init(this.settingsTabs.init()); + this.selects.init(); + this.popup.init(); + this.toggleBox(); + this.trimConsultant(); + this.showSettings(); + }, + selects: { + init: function () { + try { + $('.jq-select').SumoSelect(); + $('li.opt').each((_, el) => { + if ($(el).find('label').html().length === 0) { + let select = $(el).closest('ul').closest('div').parent().find('select'); + $(el).find('label').html(select.attr('placeholder')); + $(el).addClass('disabled'); + } + }); + } catch (e) { + console.warn('Cannot initialize select: ' + e.message); + } + } + }, + player: { + init: function () { + window.player = {}; + window.onYouTubeIframeAPIReady = function () { + window.player = new YT.Player('player', { + height: '100%', + width: '100%', + videoId: window.RCRMPROMO, + }); + } + var ytAPI = document.createElement('script'); + ytAPI.src = 'https://www.youtube.com/iframe_api'; + document.body.appendChild(ytAPI); + } + }, + settingsTabs: { + init: function () { + if (typeof RCRMTabs === 'undefined') { + return; + } + + let tabs = new RCRMTabs( + 'div[id^="rcrm_tab_"]', + '.retail-menu__btn', + 'retail-tab__enabled', + 'retail-tab__disabled', + 'retail-menu__btn_active', + 'retail-menu__btn_inactive', + 'tab-trigger', + '.rcrm-form-submit-trigger' + ); + + let mainSubmitHide = { + beforeActivate: function () { + $('#main-submit').hide(); + }, + afterDeactivate: function () { + $('#main-submit').show(); + } + }; + + tabs.tabsCallbacks({ + 'rcrm_tab_consultant': mainSubmitHide, + 'rcrm_tab_orders_upload': mainSubmitHide + }) + tabs.initializeTabs(); + + return tabs; + } + }, + uploadForm: { + init: function (tabController) { + if (!(typeof RetailcrmUploadForm === 'undefined')) { + new RetailcrmUploadForm(tabController); + } + } + }, + tabs: { + init: function () { + $('.retail-tabs__btn').on('click', this.swithTab); + }, + swithTab: function (e) { + e.preventDefault(); + + var id = $(this).attr('href'); + $('.retail-tabs__btn_active').removeClass('retail-tabs__btn_active'); + $(".retail-tabs__item_active").removeClass('retail-tabs__item_active') + .fadeOut(150, function () { + $(id).addClass("retail-tabs__item_active") + .fadeIn(150); + }); + $(this).addClass('retail-tabs__btn_active'); + } + }, + popup: { + init: function () { + var _this = this; + + $('[data-popup]').on('click', function (e) { + var id = $(this).data('popup'); + _this.open($(id)); + }); + $('.retail-popup-wrap').on('click', function (e) { + if (!$(e.target).hasClass('js-popup-close')) { + return; + } + var $popup = $(this).find('.retail-popup'); + _this.close($popup); + }); + }, + open: function (popup) { + if (!popup) { + return; + } + var $wrap = popup.closest('.retail-popup-wrap'); + + $wrap.fadeIn(200); + popup.addClass('open'); + player.playVideo(); + }, + close: function (popup) { + var $wrap = popup.closest('.retail-popup-wrap'); + popup.removeClass('open'); + $wrap.fadeOut(200); + player.stopVideo(); + } + }, + toggleBox: function () { + $('.toggle-btn').on('click', function (e) { + e.preventDefault(); + + var id = $(this).attr('href'); + var $box = $(id); + var $hideBox = $(this).closest('.retail-btns'); + + $hideBox.addClass('retail-btns_hide').slideUp(100); + $box.slideDown(100); + }) + }, + trimConsultant: function () { + let $consultantTextarea = $('#rcrm_tab_consultant textarea'); + $consultantTextarea.text($consultantTextarea.text().trim()); + }, + showSettings: function () { + $('.retail.retail-wrap.hidden').removeClass('hidden'); + } + }; + + Main.init(); +}); diff --git a/retailcrm/views/js/retailcrm.min.js b/retailcrm/views/js/retailcrm.min.js new file mode 100644 index 0000000..fd3995e --- /dev/null +++ b/retailcrm/views/js/retailcrm.min.js @@ -0,0 +1,36 @@ +/** + * MIT License + * + * Copyright (c) 2020 DIGITAL RETAIL TECHNOLOGIES SL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to http://www.prestashop.com for more information. + * + * @author DIGITAL RETAIL TECHNOLOGIES SL + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */$(function(){({init:function init(){this.player.init(),this.tabs.init(),this.uploadForm.init(this.settingsTabs.init()),this.selects.init(),this.popup.init(),this.toggleBox(),this.trimConsultant(),this.showSettings()},selects:{init:function init(){try{$(".jq-select").SumoSelect(),$("li.opt").each(function(a,b){if(0===$(b).find("label").html().length){var c=$(b).closest("ul").closest("div").parent().find("select");$(b).find("label").html(c.attr("placeholder")),$(b).addClass("disabled")}})}catch(a){console.warn("Cannot initialize select: "+a.message)}}},player:{init:function init(){window.player={},window.onYouTubeIframeAPIReady=function(){window.player=new YT.Player("player",{height:"100%",width:"100%",videoId:window.RCRMPROMO})};var a=document.createElement("script");a.src="https://www.youtube.com/iframe_api",document.body.appendChild(a)}},settingsTabs:{init:function init(){if("undefined"!=typeof RCRMTabs){var a=new RCRMTabs("div[id^=\"rcrm_tab_\"]",".retail-menu__btn","retail-tab__enabled","retail-tab__disabled","retail-menu__btn_active","retail-menu__btn_inactive","tab-trigger",".rcrm-form-submit-trigger"),b={beforeActivate:function beforeActivate(){$("#main-submit").hide()},afterDeactivate:function afterDeactivate(){$("#main-submit").show()}};return a.tabsCallbacks({rcrm_tab_consultant:b,rcrm_tab_orders_upload:b}),a.initializeTabs(),a}}},uploadForm:{init:function init(a){"undefined"==typeof RetailcrmUploadForm||new RetailcrmUploadForm(a)}},tabs:{init:function init(){$(".retail-tabs__btn").on("click",this.swithTab)},swithTab:function swithTab(a){a.preventDefault();var b=$(this).attr("href");$(".retail-tabs__btn_active").removeClass("retail-tabs__btn_active"),$(".retail-tabs__item_active").removeClass("retail-tabs__item_active").fadeOut(150,function(){$(b).addClass("retail-tabs__item_active").fadeIn(150)}),$(this).addClass("retail-tabs__btn_active")}},popup:{init:function init(){var a=this;$("[data-popup]").on("click",function(){var b=$(this).data("popup");a.open($(b))}),$(".retail-popup-wrap").on("click",function(b){if($(b.target).hasClass("js-popup-close")){var c=$(this).find(".retail-popup");a.close(c)}})},open:function open(a){if(a){var b=a.closest(".retail-popup-wrap");b.fadeIn(200),a.addClass("open"),player.playVideo()}},close:function close(a){var b=a.closest(".retail-popup-wrap");a.removeClass("open"),b.fadeOut(200),player.stopVideo()}},toggleBox:function toggleBox(){$(".toggle-btn").on("click",function(a){a.preventDefault();var b=$(this).attr("href"),c=$(b),d=$(this).closest(".retail-btns");d.addClass("retail-btns_hide").slideUp(100),c.slideDown(100)})},trimConsultant:function trimConsultant(){var a=$("#rcrm_tab_consultant textarea");a.text(a.text().trim())},showSettings:function showSettings(){$(".retail.retail-wrap.hidden").removeClass("hidden")}}).init()}); diff --git a/retailcrm/views/js/vendor/index.php b/retailcrm/views/js/vendor/index.php new file mode 100644 index 0000000..6a1c957 --- /dev/null +++ b/retailcrm/views/js/vendor/index.php @@ -0,0 +1,8 @@ ++~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&N(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ae(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ne(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ne(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n=void 0,r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n}else r&&(Q.set(this,i,k.event.trigger(k.extend(r.shift(),k.Event.prototype),r,this)),e.stopImmediatePropagation())}})):k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/\s*$/g;function Oe(e,t){return N(e,"table")&&N(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){k.fn[n]=function(e,t){return 0'),t.select=t.E.parent(),t.caption=e(""),t.CaptionCont=e('

').addClass("SelectBox").attr("style",t.E.attr("style")).prepend(t.caption),t.select.append(t.CaptionCont),t.is_multi||(l.okCancelInMulti=!1),t.E.attr("disabled")&&t.select.addClass("disabled").removeAttr("tabindex"),l.outputAsCSV&&t.is_multi&&t.E.attr("name")&&(t.select.append(e('').attr("name",t.E.attr("name")).val(t.getSelStr())),t.E.removeAttr("name")),t.isMobile()&&!l.forceCustomRendering?void t.setNativeMobile():(t.E.attr("name")&&t.select.addClass("sumo_"+t.E.attr("name")),t.E.addClass("SumoUnder").attr("tabindex","-1"),t.optDiv=e('
'),t.floatingList(),t.ul=e('
    '),t.optDiv.append(t.ul),l.selectAll&&t.SelAll(),l.search&&t.Search(),t.ul.append(t.prepItems(t.E.children())),t.is_multi&&t.multiSelelect(),t.select.append(t.optDiv),t.basicEvents(),void t.selAllState())},prepItems:function(t,l){var i=[],s=this;return e(t).each(function(t,n){n=e(n),i.push(n.is("optgroup")?e('
    • ").find("ul").append(s.prepItems(n.children(),n[0].disabled)).end():s.createLi(n,l))}),i},createLi:function(t,l){var i=this;return t.attr("value")||t.attr("value",t.val()),li=e('
    • "),li.data("opt",t),t.data("li",li),i.is_multi&&li.prepend(""),(t[0].disabled||l)&&(li=li.addClass("disabled")),i.onOptClick(li),t[0].selected&&li.addClass("selected"),t.attr("class")&&li.addClass(t.attr("class")),li},getSelStr:function(){return sopt=[],this.E.find("option:selected").each(function(){sopt.push(e(this).val())}),sopt.join(l.csvSepChar)},multiSelelect:function(){var t=this;t.optDiv.addClass("multiple"),t.okbtn=e('

      '+l.locale[0]+"

      ").click(function(){l.triggerChangeCombined&&(changed=!1,t.E.find("option:selected").length!=t.Pstate.length?changed=!0:t.E.find("option").each(function(e,l){l.selected&&t.Pstate.indexOf(e)<0&&(changed=!0)}),changed&&(t.callChange(),t.setText())),t.hideOpts()}),t.cancelBtn=e('

      '+l.locale[1]+"

      ").click(function(){t._cnbtn(),t.hideOpts()}),t.optDiv.append(e('
      ').append(t.okbtn).append(t.cancelBtn))},_cnbtn:function(){var e=this;e.E.find("option:selected").each(function(){this.selected=!1}),e.optDiv.find("li.selected").removeClass("selected");for(var t=0;t

      "),t.selAll.on("click",function(){t.selAll.toggleClass("selected"),t.optDiv.find("li.opt").not(".hidden").each(function(l,i){i=e(i),t.selAll.hasClass("selected")?i.hasClass("selected")||i.trigger("click"):i.hasClass("selected")&&i.trigger("click")})}),t.optDiv.prepend(t.selAll))},Search:function(){var t=this,i=t.CaptionCont.addClass("search"),s=e('

      ');t.ftxt=e('').on("click",function(e){e.stopPropagation()}),i.append(t.ftxt),t.optDiv.children("ul").after(s),t.ftxt.on("keyup.sumo",function(){var i=t.optDiv.find("ul.options li.opt").each(function(l,i){i=e(i),i.text().toLowerCase().indexOf(t.ftxt.val().toLowerCase())>-1?i.removeClass("hidden"):i.addClass("hidden")}).not(".hidden");s.html(l.noMatch.replace(/\{0\}/g,t.ftxt.val())).toggle(!i.length),t.selAllState()})},selAllState:function(){var t=this;if(l.selectAll){var i=0,s=0;t.optDiv.find("li.opt").not(".hidden").each(function(t,l){e(l).hasClass("selected")&&i++,e(l).hasClass("disabled")||s++}),i==s?t.selAll.removeClass("partial").addClass("selected"):0==i?t.selAll.removeClass("selected partial"):t.selAll.addClass("partial")}},showOpts:function(){var t=this;t.E.attr("disabled")||(t.is_opened=!0,t.select.addClass("open"),t.ftxt?t.ftxt.focus():t.select.focus(),e(document).on("click.sumo",function(e){if(!t.select.is(e.target)&&0===t.select.has(e.target).length){if(!t.is_opened)return;t.hideOpts(),l.okCancelInMulti&&t._cnbtn()}}),t.is_floating&&(H=t.optDiv.children("ul").outerHeight()+2,t.is_multi&&(H+=parseInt(t.optDiv.css("padding-bottom"))),t.optDiv.css("height",H),e("body").addClass("sumoStopScroll")),t.setPstate())},setPstate:function(){var e=this;e.is_multi&&(e.is_floating||l.okCancelInMulti)&&(e.Pstate=[],e.E.find("option").each(function(t,l){l.selected&&e.Pstate.push(t)}))},callChange:function(){this.E.trigger("change").trigger("click")},hideOpts:function(){var t=this;t.is_opened&&(t.is_opened=!1,t.select.removeClass("open").find("ul li.sel").removeClass("sel"),e(document).off("click.sumo"),t.select.focus(),e("body").removeClass("sumoStopScroll"),l.search&&(t.ftxt.val(""),t.optDiv.find("ul.options li").removeClass("hidden"),t.optDiv.find(".no-match").toggle(!1)))},setOnOpen:function(){var e=this,t=e.optDiv.find("li.opt:not(.hidden)").eq(l.search?0:e.E[0].selectedIndex);e.optDiv.find("li.sel").removeClass("sel"),t.addClass("sel"),e.showOpts()},nav:function(e){var t,l=this,i=l.ul.find("li.opt:not(.disabled, .hidden)"),s=l.ul.find("li.opt.sel:not(.hidden)"),n=i.index(s);if(l.is_opened&&s.length){if(e&&n>0)t=i.eq(n-1);else{if(!(!e&&n-1))return;t=i.eq(n+1)}s.removeClass("sel"),s=t.addClass("sel");var o=l.ul,a=o.scrollTop(),c=s.position().top+a;c>=a+o.height()-s.outerHeight()&&o.scrollTop(c-o.height()+s.outerHeight()),a>c&&o.scrollTop(c)}else l.setOnOpen()},basicEvents:function(){var t=this;t.CaptionCont.click(function(e){t.E.trigger("click"),t.is_opened?t.hideOpts():t.showOpts(),e.stopPropagation()}),t.select.on("keydown.sumo",function(e){switch(e.which){case 38:t.nav(!0);break;case 40:t.nav(!1);break;case 32:if(l.search&&t.ftxt.is(e.target))return;case 13:t.is_opened?t.optDiv.find("ul li.sel").trigger("click"):t.setOnOpen();break;case 9:case 27:return l.okCancelInMulti&&t._cnbtn(),void t.hideOpts();default:return}e.preventDefault()}),e(window).on("resize.sumo",function(){t.floatingList()})},onOptClick:function(t){var i=this;t.click(function(){var t=e(this);t.hasClass("disabled")||(txt="",i.is_multi?(t.toggleClass("selected"),t.data("opt")[0].selected=t.hasClass("selected"),i.selAllState()):(t.parent().find("li.selected").removeClass("selected"),t.toggleClass("selected"),t.data("opt")[0].selected=!0),i.is_multi&&l.triggerChangeCombined&&(i.is_floating||l.okCancelInMulti)||(i.setText(),i.callChange()),i.is_multi||i.hideOpts())})},setText:function(){var t=this;if(t.placeholder="",t.is_multi){for(sels=t.E.find(":selected").not(":disabled"),i=0;i=l.csvDispCount&&l.csvDispCount){sels.length==t.E.find("option").length&&l.captionFormatAllSelected?t.placeholder=l.captionFormatAllSelected.replace(/\{0\}/g,sels.length)+",":t.placeholder=l.captionFormat.replace(/\{0\}/g,sels.length)+",";break}t.placeholder+=e(sels[i]).text()+", "}t.placeholder=t.placeholder.replace(/,([^,]*)$/,"$1")}else t.placeholder=t.E.find(":selected").not(":disabled").text();return is_placeholder=!1,t.placeholder||(is_placeholder=!0,t.placeholder=t.E.attr("placeholder"),t.placeholder||(t.placeholder=t.E.find("option:disabled:selected").text())),t.placeholder=t.placeholder?l.prefix+" "+t.placeholder:l.placeholder,t.caption.html(t.placeholder),t.CaptionCont.attr("title",t.placeholder),csvField=t.select.find("input.HEMANT123"),csvField.length&&csvField.val(t.getSelStr()),is_placeholder?t.caption.addClass("placeholder"):t.caption.removeClass("placeholder"),t.placeholder},isMobile:function(){for(var e=navigator.userAgent||navigator.vendor||window.opera,t=0;t0)return l.nativeOnDevice[t];return!1},setNativeMobile:function(){var e=this;e.E.addClass("SelectClass"),e.mob=!0,e.E.change(function(){e.setText()})},floatingList:function(){var t=this;t.is_floating=e(window).width()<=l.floatWidth,t.optDiv.toggleClass("isFloating",t.is_floating),t.is_floating||t.optDiv.css("height",""),t.optDiv.toggleClass("okCancelInMulti",l.okCancelInMulti&&!t.is_floating)},vRange:function(e){var t=this;if(opts=t.E.find("option"),opts.length<=e||0>e)throw"index out of bounds";return t},toggSel:function(t,l){var i=this;"number"==typeof l?(i.vRange(l),opt=i.E.find("option")[l]):opt=i.E.find('option[value="'+l+'"]')[0]||0,opt&&!opt.disabled&&opt.selected!=t&&(opt.selected=t,i.mob||e(opt).data("li").toggleClass("selected",t),i.callChange(),i.setPstate(),i.setText(),i.selAllState())},toggDis:function(e,t){var l=this.vRange(t);l.E.find("option")[t].disabled=e,e&&(l.E.find("option")[t].selected=!1),l.mob||l.optDiv.find("ul.options li").eq(t).toggleClass("disabled",e).removeClass("selected"),l.setText()},toggSumo:function(e){var t=this;return t.enabled=e,t.select.toggleClass("disabled",e),e?(t.E.attr("disabled","disabled"),t.select.removeAttr("tabindex")):(t.E.removeAttr("disabled"),t.select.attr("tabindex","0")),t},toggSelAll:function(t){var l=this;l.E.find("option").each(function(){l.E.find("option")[e(this).index()].disabled||(l.E.find("option")[e(this).index()].selected=t,l.mob||l.optDiv.find("ul.options li").eq(e(this).index()).toggleClass("selected",t),l.setText())}),!l.mob&&l.selAll&&l.selAll.removeClass("partial").toggleClass("selected",t),l.callChange(),l.setPstate()},reload:function(){var t=this.unload();return e(t).SumoSelect(l)},unload:function(){var e=this;return e.select.before(e.E),e.E.show(),l.outputAsCSV&&e.is_multi&&e.select.find("input.HEMANT123").length&&e.E.attr("name",e.select.find("input.HEMANT123").attr("name")),e.select.remove(),delete t.sumo,t},add:function(l,i,s){if("undefined"==typeof l)throw"No value to add";var n=this;if(opts=n.E.find("option"),"number"==typeof i&&(s=i,i=l),"undefined"==typeof i&&(i=l),opt=e("").val(l).html(i),opts.length + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + *} + + + + +retailCRM +

      + {include file='./module_messages.tpl'} +
      +

      retailCRM

      +
      + {l s='retailCRM is a service for online stores that can prevent you from losing orders and increase the income at all stages of the funnel.' mod='retailcrm'} +
      +
      + +
      + + + +
      +
      + +
      +
      + +
      {l s='Connection Settings' mod='retailcrm'}
      +
      + +
      +
      + +
      +
      + +
      +
      +
      +
      + +
      +
      +

      + {l s='retailCRM is a service for online stores that can prevent you from losing orders and increase the income at all stages of the funnel.' mod='retailcrm'} +

      +

      + {l s='Stop losing leads:' mod='retailcrm'} +

      +
        +
      • {l s='LiveChat with active involvement will help you to get more orders from the website' mod='retailcrm'}
      • +
      • {l s='Chatbots and a single Inbox for Facebook Messengers and WhatsApp prevent you from losing hot leads, who are ready to buy' mod='retailcrm'}
      • +
      • {l s='Welcome chains warm up your leads and encourage them to make their first purchase' mod='retailcrm'}
      • +
      +

      + {l s='Bring the orders to payment:' mod='retailcrm'} +

      +
        +
      • {l s='Up-sales raise the average bill of your orders automatically' mod='retailcrm'}
      • +
      • {l s='The abandoned basket scripts increase the number of paid orders' mod='retailcrm'}
      • +
      +

      + {l s='Manage order fulfillment process:' mod='retailcrm'} +

      +
        +
      • {l s='CRM-system helps to receive orders, distribute them among employees, manage their statuses and fulfill them' mod='retailcrm'}
      • +
      • {l s='Notifications about the status of the order automatically inform the customer about every step of his order ' mod='retailcrm'}
      • +
      • {l s='SalesApp is an application for retail outlets that helps you to increase offline sales and builds a customer base in a single system' mod='retailcrm'}
      • +
      • {l s='Integration with the catalog helps to take into account the balances, prices and location of goods' mod='retailcrm'}
      • +
      +

      + {l s='Make your current customers stay with you:' mod='retailcrm'} +

      +
        +
      • {l s='CDP (Customer Data Platform) combines the data of your customers from different sources and builds a 360° profile' mod='retailcrm'}
      • +
      • {l s='Segments help to divide your base into small groups to make your communications more relevant.' mod='retailcrm'}
      • +
      • {l s='Email, SMS, WhatsApp and Facebook messenger newsletters increase the frequency of purchases in your customer base' mod='retailcrm'}
      • +
      • {l s='Script "Frequently used goods" helps to automatically remind you to replenish stocks' mod='retailcrm'}
      • +
      +

      + {l s='Make your customers come back:' mod='retailcrm'} +

      +
        +
      • {l s='CRM-remarketing helps to launch ads using retailCRM segments' mod='retailcrm'}
      • +
      • {l s='Abandoned viewing saves the goods that the client looked at the website and offers to pay for them' mod='retailcrm'}
      • +
      • {l s='Reactivation campaigns make lost customers come back to your store' mod='retailcrm'}
      • +
      +

      + {l s='retailCRM increases the effectiveness of all your marketing channels:' mod='retailcrm'} +

      +
        +
      • {l s='LiveChat' mod='retailcrm'}
      • +
      • {l s='Email' mod='retailcrm'}
      • +
      • {l s='Facebook Messenger' mod='retailcrm'}
      • +
      • {l s='SMS' mod='retailcrm'}
      • +
      • {l s='Retargeting' mod='retailcrm'}
      • +
      +
      +
      +
      +
      +
      +
      {l s='Is there a trial of the module?' mod='retailcrm'}
      +
      {l s='The module has a 14-day trial version in which you can work with the help of the retailCRM module.' mod='retailcrm'}
      +
      +
      +
      {l s='What is a user?' mod='retailcrm'}
      +
      {l s='A user is the person who will work with the retailCRM module as the representative of your business or your website. Each user can create a personal profile and have their own access to the tool panel.' mod='retailcrm'}
      +
      +
      +
      {l s='In what languages is the module available?' mod='retailcrm'}
      +
      {l s='The retailCRM module is available in the following languages:' mod='retailcrm'} +
        +
      • {l s='Spanish' mod='retailcrm'}
      • +
      • {l s='English' mod='retailcrm'}
      • +
      • {l s='Russian' mod='retailcrm'}
      • +
      +
      +
      +
      +
      +
      +
      {l s='How long is the trial?' mod='retailcrm'}
      +
      {l s='The duration of the trial version of the retailCRM module is 14 days.' mod='retailcrm'}
      +
      +
      +
      {l s='Is it paid per user or is it paid per account?' mod='retailcrm'}
      +
      {l s='Payment is made per user, if another user is added to the retailCRM system, payment by two users would be made. Each user has the right to an account (web-chat and social networks). In case a user needs to work with more than one account, it is necessary to contact the retailCRM team.' mod='retailcrm'}
      +
      +
      +
      {l s='How I can pay?' mod='retailcrm'}
      +
      + {l s='The methods to make the payment are:' mod='retailcrm'} +
        +
      • {l s='Wire transfer' mod='retailcrm'}
      • +
      • {l s='Credit card' mod='retailcrm'}
      • +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      {l s='Our contacts' mod='retailcrm'}
      +
      {l s='Write us in case of questions or doubts' mod='retailcrm'}
      +
      +
      + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +{**} +{**} + + diff --git a/retailcrm/views/templates/admin/module_messages.tpl b/retailcrm/views/templates/admin/module_messages.tpl new file mode 100644 index 0000000..39a5e41 --- /dev/null +++ b/retailcrm/views/templates/admin/module_messages.tpl @@ -0,0 +1,108 @@ +{** + * MIT License + * + * Copyright (c) 2020 DIGITAL RETAIL TECHNOLOGIES SL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to http://www.prestashop.com for more information. + * + * @author DIGITAL RETAIL TECHNOLOGIES SL + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + *} +{if isset($moduleErrors) && is_array($moduleErrors) && count($moduleErrors) > 0} +
      + {foreach from=$moduleErrors item=error} +
      + + {if is_array($error) && count($error) > 0} +
        + {foreach from=$error item=message} +
      • {$message|escape:'htmlall':'UTF-8'}
      • + {/foreach} +
      + {else} + {$error|escape:'htmlall':'UTF-8'} + {/if} +
      + {/foreach} +
      +{/if} +{if isset($moduleWarnings) && is_array($moduleWarnings) && count($moduleWarnings) > 0} +
      + {foreach from=$moduleWarnings item=warning} +
      + + {if is_array($warning) && count($warning) > 0} +
        + {foreach from=$warning item=message} +
      • {$message|escape:'htmlall':'UTF-8'}
      • + {/foreach} +
      + {else} + {$warning|escape:'htmlall':'UTF-8'} + {/if} +
      + {/foreach} +
      +{/if} +{if isset($moduleConfirmations) && is_array($moduleConfirmations) && count($moduleConfirmations) > 0} +
      + {foreach from=$moduleConfirmations item=confirm} +
      + + {if is_array($confirm) && count($confirm) > 0} +
        + {foreach from=$confirm item=message} +
      • {$message|escape:'htmlall':'UTF-8'}
      • + {/foreach} +
      + {else} + {$confirm|escape:'htmlall':'UTF-8'} + {/if} +
      + {/foreach} +
      +{/if} +{if isset($moduleInfos) && is_array($moduleInfos) && count($moduleInfos) > 0} +
      + {foreach from=$moduleInfos item=info} +
      + + {if is_array($info) && count($info) > 0} +
        + {foreach from=$info item=message} +
      • {$message|escape:'htmlall':'UTF-8'}
      • + {/foreach} +
      + {else} + {$info|escape:'htmlall':'UTF-8'} + {/if} +
      + {/foreach} +
      +{/if} diff --git a/retailcrm/views/templates/admin/settings.tpl b/retailcrm/views/templates/admin/settings.tpl new file mode 100644 index 0000000..097b97f --- /dev/null +++ b/retailcrm/views/templates/admin/settings.tpl @@ -0,0 +1,229 @@ +{** + * MIT License + * + * Copyright (c) 2020 DIGITAL RETAIL TECHNOLOGIES SL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to http://www.prestashop.com for more information. + * + * @author DIGITAL RETAIL TECHNOLOGIES SL + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + *} + + + + + + + +retailCRM + +{**} + + + + + diff --git a/retailcrm/views/templates/index.php b/retailcrm/views/templates/index.php new file mode 100644 index 0000000..6a1c957 --- /dev/null +++ b/retailcrm/views/templates/index.php @@ -0,0 +1,8 @@ +id = self::ID; - - $collector = new RetailcrmDaemonCollector( - $customer, - self::KEY - ); - - $js = $collector->buildScript()->getJs(); - - $this->assertContains('customerId', $js); - $this->assertContains('assertContains('', $js); - } - - public function testBuildJsWithoutCustomer() - { - $customer = new Customer; - - $collector = new RetailcrmDaemonCollector( - $customer, - self::KEY - ); - - $js = $collector->buildScript()->getJs(); - - $this->assertNotContains('customerId', $js); - $this->assertContains('assertContains('', $js); - } -} diff --git a/tests/RetailcrmHistoryTest.php b/tests/RetailcrmHistoryTest.php index 5caf989..e23d351 100644 --- a/tests/RetailcrmHistoryTest.php +++ b/tests/RetailcrmHistoryTest.php @@ -32,12 +32,7 @@ class RetailcrmHistoryTest extends RetailcrmTestCase $this->setConfig(); } - /** - * @param $api_version - * - * @dataProvider dataProvider - */ - public function testOrderCreate($api_version) + public function testOrderCreate() { $this->apiMock->expects($this->any()) ->method('ordersHistory') @@ -45,7 +40,7 @@ class RetailcrmHistoryTest extends RetailcrmTestCase new RetailcrmApiResponse( '200', json_encode( - $this->getHistoryDataNewOrder($api_version) + $this->getHistoryDataNewOrder() ) ) ); @@ -56,14 +51,13 @@ class RetailcrmHistoryTest extends RetailcrmTestCase '200', json_encode( array( - 'order' => $this->getApiOrder($api_version) + 'order' => $this->getApiOrder() ) ) ) ); RetailcrmHistory::$default_lang = (int)Configuration::get('PS_LANG_DEFAULT'); - RetailcrmHistory::$apiVersion = $api_version; RetailcrmHistory::$api = $this->apiMock; $oldLastId = RetailcrmTestHelper::getMaxOrderId(); @@ -94,25 +88,12 @@ class RetailcrmHistoryTest extends RetailcrmTestCase ); RetailcrmHistory::$default_lang = (int)Configuration::get('PS_LANG_DEFAULT'); - RetailcrmHistory::$apiVersion = 5; RetailcrmHistory::$api = $this->apiMock; RetailcrmHistory::ordersHistory(); } - public function dataProvider() - { - return array( - array( - 'api_version' => '4' - ), - array( - 'api_version' => '5' - ) - ); - } - - private function getHistoryDataNewOrder($api_version) + private function getHistoryDataNewOrder() { return array( 'success' => true, @@ -130,13 +111,19 @@ class RetailcrmHistoryTest extends RetailcrmTestCase 'newValue' => array( 'code' => 'new' ), - 'order' => $this->getApiOrder($api_version) + 'order' => $this->getApiOrder() ) + ), + "pagination" => array( + "limit" => 20, + "totalCount" => 1, + "currentPage" => 1, + "totalPageCount" => 1 ) ); } - private function getApiOrder($api_version) + private function getApiOrder() { $order = array( 'slug' => 1, @@ -160,6 +147,7 @@ class RetailcrmHistoryTest extends RetailcrmTestCase 'customer' => array( 'segments' => array(), 'id' => 1, + 'type' => 'customer', 'firstName' => 'Test', 'lastName' => 'Test', 'email' => 'email@test.ru', @@ -240,15 +228,11 @@ class RetailcrmHistoryTest extends RetailcrmTestCase 'uploadedToExternalStoreSystem' => false ); - if ($api_version == 5) { - $order['payments'][] = array( - 'id' => 97, - 'type' => 'cheque', - 'amount' => 200 - ); - } else { - $order['paymentType'] = 'cheque'; - } + $order['payments'][] = array( + 'id' => 97, + 'type' => 'cheque', + 'amount' => 200 + ); return $order; } @@ -257,6 +241,12 @@ class RetailcrmHistoryTest extends RetailcrmTestCase { return array( 'success' => true, + "pagination" => array( + "limit" => 20, + "totalCount" => 1, + "currentPage" => 1, + "totalPageCount" => 1 + ), 'history' => array( array( 'id' => 654, diff --git a/tests/RetailcrmInventoriesTest.php b/tests/RetailcrmInventoriesTest.php index 8e720ee..a890a73 100644 --- a/tests/RetailcrmInventoriesTest.php +++ b/tests/RetailcrmInventoriesTest.php @@ -30,12 +30,11 @@ class RetailcrmInventoriesTest extends RetailcrmTestCase } /** - * @param $apiVersion * @param $response * * @dataProvider dataProviderLoadStocks */ - public function testLoadStocks($apiVersion, $response) + public function testLoadStocks($response) { if ($response['success'] == true) { $this->apiMock->expects($this->any()) @@ -83,19 +82,9 @@ class RetailcrmInventoriesTest extends RetailcrmTestCase return array( array( - 'api_version' => 4, - 'response' => $response['true'], - ), - array( - 'api_version' => 5, 'response' => $response['true'] ), array( - 'api_version' => 4, - 'response' => $response['false'] - ), - array( - 'api_version' => 5, 'response' => $response['false'] ) ); diff --git a/tests/RetailcrmTest.php b/tests/RetailcrmTest.php index d285b48..5bdcbd0 100644 --- a/tests/RetailcrmTest.php +++ b/tests/RetailcrmTest.php @@ -58,12 +58,10 @@ class RetailCRMTest extends RetailcrmTestCase /** * @param $newOrder - * @param $apiVersion * @dataProvider dataProvider */ - public function testHookActionOrderStatusPostUpdate($newOrder, $apiVersion) + public function testHookActionOrderStatusPostUpdate($newOrder) { - $this->retailcrmModule->apiVersion = $apiVersion; $order = new Order(1); $customer = new Customer($order->id_customer); $cart = $this->createMock('Cart'); @@ -128,20 +126,10 @@ class RetailCRMTest extends RetailcrmTestCase { return array( array( - 'newOrder' => true, - 'apiVersion' => 4 + 'newOrder' => true ), array( - 'newOrder' => false, - 'apiVersion' => 4 - ), - array( - 'newOrder' => true, - 'apiVersion' => 5 - ), - array( - 'newOrder' => false, - 'apiVersion' => 5 + 'newOrder' => false ) ); }