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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 0000000..a91fb8a Binary files /dev/null and b/retailcrm/views/fonts/OpenSans/opensans-regular.ttf differ diff --git a/retailcrm/views/fonts/OpenSans/opensans-regular.woff b/retailcrm/views/fonts/OpenSans/opensans-regular.woff new file mode 100644 index 0000000..616db05 Binary files /dev/null and b/retailcrm/views/fonts/OpenSans/opensans-regular.woff differ diff --git a/retailcrm/views/fonts/OpenSans/opensans-regular.woff2 b/retailcrm/views/fonts/OpenSans/opensans-regular.woff2 new file mode 100644 index 0000000..91e1e27 Binary files /dev/null and b/retailcrm/views/fonts/OpenSans/opensans-regular.woff2 differ diff --git a/retailcrm/views/fonts/OpenSansBold/index.php b/retailcrm/views/fonts/OpenSansBold/index.php new file mode 100644 index 0000000..6a1c957 --- /dev/null +++ b/retailcrm/views/fonts/OpenSansBold/index.php @@ -0,0 +1,8 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 0000000..96333f9 Binary files /dev/null and b/retailcrm/views/fonts/OpenSansBold/opensans-bold.ttf differ diff --git a/retailcrm/views/fonts/OpenSansBold/opensans-bold.woff b/retailcrm/views/fonts/OpenSansBold/opensans-bold.woff new file mode 100644 index 0000000..37ed43a Binary files /dev/null and b/retailcrm/views/fonts/OpenSansBold/opensans-bold.woff differ diff --git a/retailcrm/views/fonts/OpenSansBold/opensans-bold.woff2 b/retailcrm/views/fonts/OpenSansBold/opensans-bold.woff2 new file mode 100644 index 0000000..4380482 Binary files /dev/null and b/retailcrm/views/fonts/OpenSansBold/opensans-bold.woff2 differ 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 @@ + + * @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 ) ); }