diff --git a/README.md b/README.md index 15ba4a0..db6a944 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,14 @@ -opencart-module -=============== +Opecart module +============== + +Opencart module for interaction with [IntaroCRM](http://www.intarocrm.com) through [REST API](http://docs.intarocrm.ru/rest-api/). + +Module allows: + +* Send to IntaroCRM new orders +* Configure relations between dictionaries of IntaroCRM and Opencart (statuses, payments, delivery types and etc) +* Generate [ICML](http://docs.intarocrm.ru/index.php?n=Пользователи.ФорматICML) (IntaroCRM Markup Language) for catalog loading by IntaroCRM + +* [Install](doc/Install.md) +* [Changelog](doc/Changelog.md) +* [TODO](doc/TODO.md) diff --git a/admin/controller/module/intarocrm.php b/admin/controller/module/intarocrm.php new file mode 100644 index 0000000..2c04773 --- /dev/null +++ b/admin/controller/module/intarocrm.php @@ -0,0 +1,564 @@ +load->model('setting/setting'); + $this->model_setting_setting->editSetting('intarocrm', array('intarocrm_status'=>1)); + } + + public function uninstall() { + $this->load->model('setting/setting'); + $this->model_setting_setting->editSetting('intarocrm', array('intarocrm_status'=>0)); + } + + public function index() { + + $this->log = new Monolog\Logger('opencart-module'); + $this->log->pushHandler(new Monolog\Handler\StreamHandler(__DIR__ . '/../../../system/logs/intarocrm_module.log', Monolog\Logger::INFO)); + + $this->load->model('setting/setting'); + $this->load->model('setting/extension'); + $this->load->language('module/intarocrm'); + $this->document->setTitle($this->language->get('heading_title')); + $this->document->addStyle('/admin/view/stylesheet/intarocrm.css'); + + if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) { + $this->model_setting_setting->editSetting('intarocrm', $this->request->post); + $this->session->data['success'] = $this->language->get('text_success'); + $this->redirect($this->url->link('extension/module', 'token=' . $this->session->data['token'], 'SSL')); + } + + $text_strings = array( + 'heading_title', + 'text_enabled', + 'text_disabled', + 'button_save', + 'button_cancel', + 'text_notice', + 'intarocrm_url', + 'intarocrm_apikey', + 'intarocrm_base_settings', + 'intarocrm_dict_settings', + 'intarocrm_dict_delivery', + 'intarocrm_dict_status', + 'intarocrm_dict_payment', + ); + + foreach ($text_strings as $text) { + $this->data[$text] = $this->language->get($text); + } + + $this->data['intarocrm_errors'] = array(); + $this->data['saved_settings'] = $this->model_setting_setting->getSetting('intarocrm'); + + if ($this->data['saved_settings']['intarocrm_url'] != '' && $this->data['saved_settings']['intarocrm_apikey'] != '') { + + $this->intarocrm = new \IntaroCrm\RestApi( + $this->data['saved_settings']['intarocrm_url'], + $this->data['saved_settings']['intarocrm_apikey'] + ); + + /* + * Delivery + */ + + try { + $this->deliveryTypes = $this->intarocrm->deliveryTypesList(); + } + catch (ApiException $e) + { + $this->data['intarocrm_error'][] = $e->getMessage(); + $this->log->addError('['.$this->config->get('store_name').'] RestApi::deliveryTypesList::Api:' . $e->getMessage()); + } + catch (CurlException $e) + { + $this->data['intarocrm_error'][] = $e->getMessage(); + $this->log->addError('['.$this->config->get('store_name').'] RestApi::deliveryTypesList::Curl:' . $e->getMessage()); + } + + $this->data['delivery'] = array( + 'opencart' => $this->getOpercartDeliveryMethods(), + 'intarocrm' => $this->deliveryTypes + ); + + /* + * Statuses + */ + try { + $this->statuses = $this->intarocrm->orderStatusesList(); + } + catch (ApiException $e) + { + $this->data['intarocrm_error'][] = $e->getMessage(); + $this->log->addError('['.$this->config->get('store_name').'] RestApi::orderStatusesList::Api:' . $e->getMessage()); + } + catch (CurlException $e) + { + $this->data['intarocrm_error'][] = $e->getMessage(); + $this->log->addError('['.$this->config->get('store_name').'] RestApi::orderStatusesList::Curl:' . $e->getMessage()); + } + + $this->data['statuses'] = array( + 'opencart' => $this->getOpercartOrderStatuses(), + 'intarocrm' => $this->statuses + ); + + /* + * Payment + */ + + try { + $this->payments = $this->intarocrm->paymentTypesList(); + } + catch (ApiException $e) + { + $this->data['intarocrm_error'][] = $e->getMessage(); + $this->log->addError('['.$this->config->get('store_name').'] RestApi::paymentTypesList::Api:' . $e->getMessage()); + } + catch (CurlException $e) + { + $this->data['intarocrm_error'][] = $e->getMessage(); + $this->log->addError('['.$this->config->get('store_name').'] RestApi::paymentTypesList::Curl:' . $e->getMessage()); + } + + $this->data['payments'] = array( + 'opencart' => $this->getOpercartPaymentTypes(), + 'intarocrm' => $this->payments + ); + + } + + $config_data = array( + 'intarocrm_status' + ); + + foreach ($config_data as $conf) { + if (isset($this->request->post[$conf])) { + $this->data[$conf] = $this->request->post[$conf]; + } else { + $this->data[$conf] = $this->config->get($conf); + } + } + + if (isset($this->error['warning'])) { + $this->data['error_warning'] = $this->error['warning']; + } else { + $this->data['error_warning'] = ''; + } + + $this->data['breadcrumbs'] = array(); + + $this->data['breadcrumbs'][] = array( + 'text' => $this->language->get('text_home'), + 'href' => $this->url->link('common/home', 'token=' . $this->session->data['token'], 'SSL'), + 'separator' => false + ); + + $this->data['breadcrumbs'][] = array( + 'text' => $this->language->get('text_module'), + 'href' => $this->url->link('extension/module', 'token=' . $this->session->data['token'], 'SSL'), + 'separator' => ' :: ' + ); + + $this->data['breadcrumbs'][] = array( + 'text' => $this->language->get('heading_title'), + 'href' => $this->url->link('module/intarocrm', 'token=' . $this->session->data['token'], 'SSL'), + 'separator' => ' :: ' + ); + + $this->data['action'] = $this->url->link('module/intarocrm', 'token=' . $this->session->data['token'], 'SSL'); + + $this->data['cancel'] = $this->url->link('extension/module', 'token=' . $this->session->data['token'], 'SSL'); + + + $this->data['modules'] = array(); + + if (isset($this->request->post['intarocrm_module'])) { + $this->data['modules'] = $this->request->post['intarocrm_module']; + } elseif ($this->config->get('intarocrm_module')) { + $this->data['modules'] = $this->config->get('intarocrm_module'); + } + + $this->load->model('design/layout'); + + $this->data['layouts'] = $this->model_design_layout->getLayouts(); + + $this->template = 'module/intarocrm.tpl'; + $this->children = array( + 'common/header', + 'common/footer', + ); + + $this->response->setOutput($this->render()); + } + + public function order_history() + { + $this->log = new Monolog\Logger('opencart-module'); + $this->log->pushHandler(new Monolog\Handler\StreamHandler(__DIR__ . '/../../../system/logs/intarocrm_module.log', Monolog\Logger::INFO)); + + $this->load->model('setting/setting'); + $this->load->model('setting/store'); + $this->load->model('sale/order'); + $this->load->model('sale/customer'); + + + + $settings = $this->model_setting_setting->getSetting('intarocrm'); + $settings['domain'] = parse_url(HTTP_SERVER, PHP_URL_HOST); + + if (isset($settings['intarocrm_url']) && $settings['intarocrm_url'] != '' && isset($settings['intarocrm_apikey']) && $settings['intarocrm_apikey'] != '') { + include_once __DIR__ . '/../../../system/library/intarocrm/apihelper.php'; + $crm = new ApiHelper($settings); + $orders = $crm->orderHistory(); + $ordersIdsFix = array(); + $customersIdsFix = array(); + $subtotalSettings = $this->model_setting_setting->getSetting('sub_total'); + $totalSettings = $this->model_setting_setting->getSetting('total'); + $shippingSettings = $this->model_setting_setting->getSetting('shipping'); + + foreach ($orders as $order) + { + if (!isset($order['deleted']) || !$order['deleted']) { + + $data = array(); + + $customer_id = (isset($order['customer']['externalId'])) + ? $order['customer']['externalId'] + : '' + ; + + if ($customer_id == '') { + $cData = array( + 'customer_group_id' => '1', + 'firstname' => $order['customer']['firstName'], + 'lastname' => (isset($order['customer']['lastName'])) ? $order['customer']['lastName'] : ' ', + 'email' => $order['customer']['email'], + 'telephone' => (isset($order['customer']['phones'][0]['number'])) ? $order['customer']['phones'][0]['number'] : ' ', + 'newsletter' => 0, + 'password' => 'tmppass', + 'status' => 1, + 'address' => array( + 'firstname' => $order['customer']['firstName'], + 'lastname' => (isset($order['customer']['lastName'])) ? $order['customer']['lastName'] : ' ', + 'address_1' => $order['customer']['address']['text'], + 'city' => $order['customer']['address']['city'], + 'postcode' => $order['customer']['address']['index'] + ), + ); + + $this->model_sale_customer->addCustomer($cData); + + if (isset($order['customer']['email']) && $order['customer']['email'] != '') { + $tryToFind = $this->model_sale_customer->getCustomerByEmail($order['customer']['email']); + $customer_id = $tryToFind['customer_id']; + } else { + $last = $this->model_sale_customer->getCustomers($data = array('order' => 'DESC', 'limit' => 1)); + $customer_id = $last[0]['customer_id']; + } + + $customersIdsFix[] = array('id' => $order['customer']['id'], 'externalId' => (int) $customer_id); + } + + $delivery = array_flip($settings['intarocrm_delivery']); + $payment = array_flip($settings['intarocrm_payment']); + $status = array_flip($settings['intarocrm_status']); + + $ocPayment = $this->getOpercartPaymentTypes(); + $ocDelivery = $this->getOpercartDeliveryMethods(); + + $data['store_id'] = ($this->config->get('config_store_id') == null) ? 0 : $this->config->get('config_store_id'); + $data['customer'] = $order['customer']['firstName']; + $data['customer_id'] = $customer_id; + $data['firstname'] = $order['customer']['firstName']; + $data['lastname'] = (isset($order['customer']['lastName'])) ? $order['customer']['lastName'] : ' '; + $data['email'] = $order['customer']['email']; + $data['telephone'] = (isset($order['customer']['phones'][0]['number'])) ? $order['customer']['phones'][0]['number'] : ' '; + $data['comment'] = $order['customerComment']; + + $data['payment_address'] = '0'; + $data['payment_firstname'] = $order['firstName']; + $data['payment_lastname'] = (isset($order['lastName'])) ? $order['lastName'] : ' '; + $data['payment_address_1'] = $order['customer']['address']['text']; + $data['payment_city'] = $order['customer']['address']['city']; + $data['payment_postcode'] = $order['customer']['address']['index']; + + /* + * TODO: add country & zone id detection + */ + //$data['payment_country_id'] = '176'; + //$data['payment_zone_id'] = '2778'; + //$data['shipping_country_id'] = '176'; + //$data['shipping_zone_id'] = '2778'; + + $data['shipping_address'] = '0'; + $data['shipping_firstname'] = $order['customer']['firstName']; + $data['shipping_lastname'] = (isset($order['customer']['lastName'])) ? $order['customer']['lastName'] : ' '; + $data['shipping_address_1'] = $order['delivery']['address']['text']; + $data['shipping_city'] = $order['delivery']['address']['city']; + $data['shipping_postcode'] = $order['delivery']['address']['index']; + + $data['shipping'] = $delivery[$order['delivery']['code']]; + $data['shipping_method'] = $ocDelivery[$data['shipping']]; + $data['shipping_code'] = $delivery[$order['delivery']['code']]; + $data['payment'] = $payment[$order['paymentType']]; + $data['payment_method'] = $ocPayment[$data['payment']]; + $data['payment_code'] = $payment[$order['paymentType']]; + $data['order_status_id'] = $status[$order['status']]; + + $data['order_product'] = array(); + + foreach($order['items'] as $item) { + $data['order_product'][] = array( + 'product_id' => $item['offer']['externalId'], + 'name' => $item['offer']['name'], + 'quantity' => $item['quantity'], + 'price' => $item['initialPrice'], + 'total' => $item['initialPrice'] * $item['quantity'], + ); + } + + $deliveryCost = isset($order['delivery']['cost']) ? $order['delivery']['cost'] : 0; + + $data['order_total'] = array( + array( + 'order_total_id' => '', + 'code' => 'sub_total', + 'value' => $order['summ'], + 'sort_order' => $subtotalSettings['sub_total_sort_order'] + ), + array( + 'order_total_id' => '', + 'code' => 'shipping', + 'value' => $deliveryCost, + 'sort_order' => $shippingSettings['shipping_sort_order'] + ), + array( + 'order_total_id' => '', + 'code' => 'total', + 'value' => isset($order['totalSumm']) ? $order['totalSumm'] : $order['summ'] + $deliveryCost, + 'sort_order' => $totalSettings['total_sort_order'] + ) + ); + + if (isset($order['externalId'])) { + /* + * opercart developers believe that to remove all + * products from the order during the editing is a good idea... + * + * so we have to get all the goods before orders are breaks + * + */ + $items = $crm->getOrderItems($order['externalId']); + $data['order_product'] = array(); + + foreach($items as $item) { + $data['order_product'][] = array( + 'product_id' => $item['offer']['externalId'], + 'name' => $item['offer']['name'], + 'quantity' => $item['quantity'], + 'price' => $item['initialPrice'], + 'total' => $item['initialPrice'] * $item['quantity'], + ); + } + + $this->model_sale_order->editOrder($order['externalId'], $data); + } else { + $this->model_sale_order->addOrder($data); + $last = $this->model_sale_order->getOrders($data = array('order' => 'DESC', 'limit' => 1)); + $ordersIdsFix[] = array('id' => $order['id'], 'externalId' => (int) $last[0]['order_id']); + } + } + } + + if (!empty($customersIdsFix)) { + $crm->customerFixExternalIds($customersIdsFix); + } + + if (!empty($ordersIdsFix)) { + $crm->orderFixExternalIds($ordersIdsFix); + } + + } else { + $this->log->addNotice('['.$this->config->get('store_name').'] RestApi::orderHistory: you need to configure Intarocrm module first.'); + } + } + + public function export() { + file_put_contents(__DIR__ . '/../../../download/intarocrm.xml', $this->xml()); + } + + private function validate() { + if (!$this->user->hasPermission('modify', 'module/intarocrm')) { + $this->error['warning'] = $this->language->get('error_permission'); + } + + if (!$this->error) { + return TRUE; + } else { + return FALSE; + } + } + + protected function getOpercartDeliveryMethods() + { + $deliveryMethods = array(); + $files = glob(DIR_APPLICATION . 'controller/shipping/*.php'); + + if ($files) { + foreach ($files as $file) { + $extension = basename($file, '.php'); + + $this->load->language('shipping/' . $extension); + + if ($this->config->get($extension . '_status')) { + $deliveryMethods[$extension.'.'.$extension] = strip_tags($this->language->get('heading_title')); + } + } + } + + return $deliveryMethods; + } + + protected function getOpercartOrderStatuses() + { + $this->load->model('localisation/order_status'); + return $this->model_localisation_order_status->getOrderStatuses(array()); + } + + protected function getOpercartPaymentTypes() + { + $paymentTypes = array(); + $files = glob(DIR_APPLICATION . 'controller/payment/*.php'); + + if ($files) { + foreach ($files as $file) { + $extension = basename($file, '.php'); + + $this->load->language('payment/' . $extension); + + if ($this->config->get($extension . '_status')) { + $paymentTypes[$extension] = strip_tags($this->language->get('heading_title')); + } + } + } + + return $paymentTypes; + } + + private function xml() + { + $this->dd = new DOMDocument(); + $this->dd->loadXML(' + + + '.$this->config->get('config_name').' + + + + + '); + + $this->eCategories = $this->dd->getElementsByTagName('categories')->item(0); + $this->eOffers = $this->dd->getElementsByTagName('offers')->item(0); + + $this->addCategories(); + $this->addOffers(); + return $this->dd->saveXML(); + } + + private function addCategories() + { + $this->load->model('catalog/category'); + + foreach ($this->model_catalog_category->getCategories(array()) as $category) { + $e = $this->eCategories->appendChild($this->dd->createElement('category', $category['name'])); + $e->setAttribute('id',$category['category_id']); + } + + } + + private function addOffers() + { + $this->load->model('catalog/product'); + $this->load->model('catalog/manufacturer'); + $this->load->model('tool/image'); + + $offerManufacturers = array(); + + $manufacturers = $this->model_catalog_manufacturer->getManufacturers(array()); + + foreach ($manufacturers as $manufacturer) { + $offerManufacturers[$manufacturer['manufacturer_id']] = $manufacturer['name']; + } + + foreach ($this->model_catalog_product->getProducts(array()) as $offer) { + + $e = $this->eOffers->appendChild($this->dd->createElement('offer')); + $e->setAttribute('id', $offer['product_id']); + $e->setAttribute('productId', $offer['product_id']); + $e->setAttribute('quantity', $offer['quantity']); + $e->setAttribute('available', $offer['status'] ? 'true' : 'false'); + + /* + * DIRTY HACK, NEED TO REFACTOR + */ + + $sql = "SELECT * FROM `" . DB_PREFIX . "product_to_category` WHERE `product_id` = " .$offer['product_id']. ";"; + $result = $this->db->query($sql); + foreach ($result->rows as $row) { + $e->appendChild($this->dd->createElement('categoryId', $row['category_id'])); + } + + $e->appendChild($this->dd->createElement('name'))->appendChild($this->dd->createTextNode($offer['name'])); + $e->appendChild($this->dd->createElement('productName'))->appendChild($this->dd->createTextNode($offer['name'])); + if ($offer['manufacturer_id'] != 0) { + $e->appendChild($this->dd->createElement('vendor'))->appendChild($this->dd->createTextNode($offerManufacturers[$offer['manufacturer_id']])); + } + $e->appendChild($this->dd->createElement('price', $offer['price'])); + + if ($offer['image']) { + $e->appendChild( + $this->dd->createElement( + 'picture', + $this->model_tool_image->resize($offer['image'], $this->config->get('config_image_product_width'), $this->config->get('config_image_product_height')) + ) + ); + } + + $e->appendChild($this->dd->createElement('url'))->appendChild( + $this->dd->createTextNode( + $this->url->link('product/product&product_id=' . $offer['product_id']) + ) + ); + + if ($offer['sku'] != '') { + $sku = $this->dd->createElement('param'); + $sku->setAttribute('name', 'article'); + $sku->appendChild($this->dd->createTextNode($offer['sku'])); + $e->appendChild($sku); + } + + if ($offer['weight'] != '') { + $weight = $this->dd->createElement('param'); + $weight->setAttribute('name', 'weight'); + $weightValue = (isset($offer['weight_class'])) ? $offer['weight'] . ' ' . $offer['weight_class'] : $offer['weight']; + $weight->appendChild($this->dd->createTextNode($weightValue)); + $e->appendChild($weight); + } + + if ($offer['length'] != '' && $offer['width'] != '' && $offer['height'] != '') { + $size = $this->dd->createElement('param'); + $size->setAttribute('name', 'size'); + $size->appendChild($this->dd->createTextNode($offer['length'] .'x'. $offer['width'] .'x'. $offer['height'])); + $e->appendChild($size); + } + } + } +} +?> \ No newline at end of file diff --git a/admin/language/english/module/intarocrm.php b/admin/language/english/module/intarocrm.php new file mode 100644 index 0000000..93a17e5 --- /dev/null +++ b/admin/language/english/module/intarocrm.php @@ -0,0 +1,22 @@ + \ No newline at end of file diff --git a/admin/language/russian/module/intarocrm.php b/admin/language/russian/module/intarocrm.php new file mode 100644 index 0000000..1f53831 --- /dev/null +++ b/admin/language/russian/module/intarocrm.php @@ -0,0 +1,22 @@ + \ No newline at end of file diff --git a/admin/view/stylesheet/intarocrm.css b/admin/view/stylesheet/intarocrm.css new file mode 100644 index 0000000..8bf0ceb --- /dev/null +++ b/admin/view/stylesheet/intarocrm.css @@ -0,0 +1,2 @@ +.intarocrm_unit {margin-bottom: 10px;} +.intarocrm_unit input {width: 30%;} \ No newline at end of file diff --git a/admin/view/template/module/intarocrm.tpl b/admin/view/template/module/intarocrm.tpl new file mode 100644 index 0000000..6154a66 --- /dev/null +++ b/admin/view/template/module/intarocrm.tpl @@ -0,0 +1,100 @@ + + +
+ + +
+ + +
+ + /admin/settings#t-main +
+ + +
+
+

+
+
+
+
+ + +

+
+
+ +
+
+
+ +
+ + + + + +
+ + +

+ +

+ $value): ?> +
+ + +
+ + +

+ + +
+ + +
+ + +

+ $value): ?> +
+ + +
+ + + + + +
+
+
+ +
+ + + \ No newline at end of file diff --git a/cli/cli_dispatch.php b/cli/cli_dispatch.php new file mode 100644 index 0000000..8baa5e4 --- /dev/null +++ b/cli/cli_dispatch.php @@ -0,0 +1,141 @@ +write("ERROR: cli $cli_action call attempted by non-cli."); + http_response_code(400); + exit; +} + +// Ensure $cli_action is set +if (!isset($cli_action)) { + echo 'ERROR: $cli_action must be set in calling script.'; + $log->write('ERROR: $cli_action must be set in calling script.'); + http_response_code(400); + exit; +} + +// Version +define('VERSION', '1.5.6'); + +// Configuration (note we're using the admin config) +require_once(__DIR__ . '/../admin/config.php'); + +// Configuration check +if (!defined('DIR_APPLICATION')) { + echo "ERROR: cli $cli_action call missing configuration."; + $log->write("ERROR: cli $cli_action call missing configuration."); + http_response_code(400); + exit; +} + +// Startup +require_once(DIR_SYSTEM . 'startup.php'); + +// Application Classes +require_once(DIR_SYSTEM . 'library/currency.php'); +require_once(DIR_SYSTEM . 'library/user.php'); +require_once(DIR_SYSTEM . 'library/weight.php'); +require_once(DIR_SYSTEM . 'library/length.php'); + +// Registry +$registry = new Registry(); + +// Loader +$loader = new Loader($registry); +$registry->set('load', $loader); + +// Config +$config = new Config(); +$registry->set('config', $config); + +// Database +$db = new DB(DB_DRIVER, DB_HOSTNAME, DB_USERNAME, DB_PASSWORD, DB_DATABASE); +$registry->set('db', $db); + +// Settings +$query = $db->query("SELECT * FROM " . DB_PREFIX . "setting WHERE store_id = '0'"); + +foreach ($query->rows as $setting) { + if (!$setting['serialized']) { + $config->set($setting['key'], $setting['value']); + } else { + $config->set($setting['key'], unserialize($setting['value'])); + } +} + +// Url +$url = new Url(HTTP_SERVER, HTTPS_SERVER); +$registry->set('url', $url); + +// Log +$log = new Log($config->get('config_error_filename')); +$registry->set('log', $log); + +function error_handler($errno, $errstr, $errfile, $errline) { + global $log, $config; + + switch ($errno) { + case E_NOTICE: + case E_USER_NOTICE: + $error = 'Notice'; + break; + case E_WARNING: + case E_USER_WARNING: + $error = 'Warning'; + break; + case E_ERROR: + case E_USER_ERROR: + $error = 'Fatal Error'; + break; + default: + $error = 'Unknown'; + break; + } + + if ($config->get('config_error_display')) { + echo "\n".'PHP ' . $error . ': ' . $errstr . ' in ' . $errfile . ' on line ' . $errline."\n"; + } + + if ($config->get('config_error_log')) { + $log->write('PHP ' . $error . ': ' . $errstr . ' in ' . $errfile . ' on line ' . $errline); + } + + return true; +} +set_error_handler('error_handler'); +$request = new Request(); +$registry->set('request', $request); +$response = new Response(); +$response->addHeader('Content-Type: text/html; charset=utf-8'); +$registry->set('response', $response); +$cache = new Cache(); +$registry->set('cache', $cache); +$session = new Session(); +$registry->set('session', $session); +$languages = array(); + +$query = $db->query("SELECT * FROM " . DB_PREFIX . "language"); +foreach ($query->rows as $result) { + $languages[$result['code']] = $result; +} +$config->set('config_language_id', $languages[$config->get('config_admin_language')]['language_id']); +$language = new Language($languages[$config->get('config_admin_language')]['directory']); +$language->load($languages[$config->get('config_admin_language')]['filename']); +$registry->set('language', $language); + +$document = new Document(); +$registry->set('document', $document); + +$registry->set('currency', new Currency($registry)); +$registry->set('weight', new Weight($registry)); +$registry->set('length', new Length($registry)); +$registry->set('user', new User($registry)); + +$controller = new Front($registry); +$action = new Action($cli_action); +$controller->dispatch($action, new Action('error/not_found')); + +// Output +$response->output(); +?> \ No newline at end of file diff --git a/cli/cli_export.php b/cli/cli_export.php new file mode 100644 index 0000000..095a52c --- /dev/null +++ b/cli/cli_export.php @@ -0,0 +1,3 @@ + Intstall module. Before running exchange you must configure module. + +### Export + +Setup cron job for periodically catalog export + +``` +0 */12 0 0 0 /usr/bin/php /path/to/opencart/instance/cli/cli_export.php >> /path/to/opencart/instance/system/logs/cronjob_export.log 2>&1 +``` + +### Exchange setup + + +#### Export new order from shop to CRM + +Open /catalog/model/checkout/order.php script. Into addOrder method add this line before return statement: + +``` +$this->crmOrderAction($data, $order_id, 'create'); +``` + +In the end of this file add method: + +``` +protected function crmOrderAction($order, $order_id, $action=null) +{ + $this->load->model('setting/setting'); + $settings = $this->model_setting_setting->getSetting('intarocrm'); + $settings['domain'] = parse_url(HTTP_SERVER, PHP_URL_HOST); + + if(isset($settings['intarocrm_url']) && $settings['intarocrm_url'] != '' && isset($settings['intarocrm_apikey']) && $settings['intarocrm_apikey'] != '') { + include_once __DIR__ . '/../../../system/library/intarocrm/apihelper.php'; + + $order['order_id'] = $order_id; + $crm = new ApiHelper($settings); + + if ($action != null) { + $method = 'order' . ucfirst($action); + $crm->$method($order); + } + } + +} +``` + +#### Export new order from CRM to shop + +Setup cron job for exchange between CRM & your shop + +``` +*/5 0 0 0 0 /usr/bin/php /path/to/opencart/instance/cli/cli_export.php >> /path/to/opencart/instance/system/logs/cronjob_history.log 2>&1 +``` \ No newline at end of file diff --git a/doc/TODO.md b/doc/TODO.md new file mode 100644 index 0000000..4b1d6c0 --- /dev/null +++ b/doc/TODO.md @@ -0,0 +1,6 @@ +TODO +==== + +* Edit order hook +* Export old customers & orders +* New customers export \ No newline at end of file diff --git a/system/library/intarocrm/.gitignore b/system/library/intarocrm/.gitignore new file mode 100644 index 0000000..61ead86 --- /dev/null +++ b/system/library/intarocrm/.gitignore @@ -0,0 +1 @@ +/vendor diff --git a/system/library/intarocrm/apihelper.php b/system/library/intarocrm/apihelper.php new file mode 100644 index 0000000..75938ee --- /dev/null +++ b/system/library/intarocrm/apihelper.php @@ -0,0 +1,230 @@ +dir = __DIR__ . '/../../logs/'; + $this->fileDate = $this->dir . 'intarocrm_history.log'; + $this->settings = $settings; + $this->domain = $settings['domain']; + + $this->intaroApi = new IntaroCrm\RestApi( + $settings['intarocrm_url'], + $settings['intarocrm_apikey'] + ); + + $this->initLogger(); + } + + public function dumperData($data) + { + return false; + } + + public function orderCreate($data) { + + $order = array(); + $customer = array(); + + $payment_code = $data['payment_code']; + $delivery_code = $data['shipping_code']; + $settings = $this->settings; + + try { + $customers = $this->intaroApi->customers($data['telephone'], $data['email'], $data['lastname'], 200, 0); + } catch (ApiException $e) { + $this->log->addError('['.$this->domain.'] RestApi::customers:' . $e->getMessage()); + $this->log->addError('['.$this->domain.'] RestApi::customers:' . json_encode($data)); + } catch (CurlException $e) { + $this->log->addError('['.$this->domain.'] RestApi::customers::Curl:' . $e->getMessage()); + } + + if(count($customers) > 0 && isset($customers[0]['externalId'])) { + $order['customerId'] = $customers[0]['externalId']; + } else { + $order['customerId'] = $data['customer_id']; + $customer['externalId'] = $data['customer_id']; + $customer['firstName'] = $data['firstname']; + $customer['lastName'] = $data['lastname']; + $customer['email'] = $data['email']; + $customer['phones']['number'] = $data['telephone']; + + $customer['address']['text'] = implode(', ', array( + $data['payment_postcode'], + $data['payment_country'], + $data['payment_city'], + $data['payment_address_1'], + $data['payment_address_2'] + )); + + try { + $this->intaroApi->customerCreate($customer); + } catch (ApiException $e) { + $this->log->addError('['.$this->domain.'] RestApi::orderCreate:' . $e->getMessage()); + $this->log->addError('['.$this->domain.'] RestApi::orderCreate:' . json_encode($order)); + } catch (CurlException $e) { + $this->log->addError('['.$this->domain.'] RestApi::orderCreate::Curl:' . $e->getMessage()); + } + } + + $order['externalId'] = $data['order_id']; + $order['firstName'] = $data['firstname']; + $order['lastName'] = $data['lastname']; + $order['email'] = $data['email']; + $order['phone'] = $data['telephone']; + $order['customerComment'] = $data['comment']; + + $order['deliveryCost'] = 0; + foreach ($data['totals'] as $totals) { + if ($totals['code'] == 'shipping') { + $order['deliveryCost'] = $totals['value']; + } + } + + $order['deliveryAddress']['text'] = implode(', ', array( + $data['shipping_postcode'], + $data['shipping_country'], + $data['shipping_city'], + $data['shipping_address_1'], + $data['shipping_address_2'] + )); + + $order['createdAt'] = date('Y-m-d H:i:s'); + $order['paymentType'] = $settings['intarocrm_payment'][$payment_code]; + + $order['delivery'] = array( + 'code' => $settings['intarocrm_delivery'][$delivery_code], + 'cost' => $order['deliveryCost'] + ); + + + foreach ($data['products'] as $product) { + $order['items'][] = array( + 'productId' => $product['product_id'], + 'productName' => $product['name'] . ' '. $product['model'], + 'initialPrice' => $product['price'], + 'quantity' => $product['quantity'], + ); + } + + try { + $this->intaroApi->orderCreate($order); + } catch (ApiException $e) { + $this->log->addError('['.$this->domain.'] RestApi::orderCreate:' . $e->getMessage()); + $this->log->addError('['.$this->domain.'] RestApi::orderCreate:' . json_encode($order)); + } catch (CurlException $e) { + $this->log->addError('['.$this->domain.'] RestApi::orderCreate::Curl:' . $e->getMessage()); + } + } + + public function orderHistory() { + + try { + $orders = $this->intaroApi->orderHistory($this->getDate()); + $this->saveDate($this->intaroApi->getGeneratedAt()->format('Y-m-d H:i:s')); + } catch (ApiException $e) { + $this->log->addError('['.$this->domain.'] RestApi::orderHistory:' . $e->getMessage()); + $this->log->addError('['.$this->domain.'] RestApi::orderHistory:' . json_encode($orders)); + + return false; + } catch (CurlException $e) { + $this->log->addError('['.$this->domain.'] RestApi::orderHistory::Curl:' . $e->getMessage()); + + return false; + } + + return $orders; + } + + public function orderFixExternalIds($data) + { + try { + $this->intaroApi->orderFixExternalIds($data); + } catch (ApiException $e) { + $this->log->addError('['.$this->domain.'] RestApi::orderFixExternalIds:' . $e->getMessage()); + $this->log->addError('['.$this->domain.'] RestApi::orderFixExternalIds:' . json_encode($data)); + + return false; + } catch (CurlException $e) { + $this->log->addError('['.$this->domain.'] RestApi::orderFixExternalIds::Curl:' . $e->getMessage()); + + return false; + } + } + + public function customerFixExternalIds($data) + { + try { + $this->intaroApi->customerFixExternalIds($data); + } catch (ApiException $e) { + $this->log->addError('['.$this->domain.'] RestApi::customerFixExternalIds:' . $e->getMessage()); + $this->log->addError('['.$this->domain.'] RestApi::customerFixExternalIds:' . json_encode($data)); + + return false; + } catch (CurlException $e) { + $this->log->addError('['.$this->domain.'] RestApi::customerFixExternalIds::Curl:' . $e->getMessage()); + + return false; + } + } + + public function getOrderItems($order_id) + { + try { + $order = $this->intaroApi->orderGet($order_id); + + return $order['items']; + } catch (ApiException $e) { + $this->log->addError('['.$this->domain.'] RestApi::orderFixExternalIds:' . $e->getMessage()); + $this->log->addError('['.$this->domain.'] RestApi::orderFixExternalIds:' . json_encode($data)); + + return false; + } catch (CurlException $e) { + $this->log->addError('['.$this->domain.'] RestApi::orderFixExternalIds::Curl:' . $e->getMessage()); + + return false; + } + + } + + private function saveDate($date) { + file_put_contents($this->fileDate, $date, LOCK_EX); + } + + private function getDate() { + if (file_exists($this->fileDate)) { + $result = file_get_contents($this->fileDate); + } else { + $result = date('Y-m-d H:i:s', strtotime('-2 days', strtotime(date('Y-m-d H:i:s')))); + } + + return $result; + } + + private function explodeFIO($str) { + if(!$str) + return array(); + + $array = explode(" ", $str, 3); + $newArray = array(); + + foreach($array as $ar) { + if(!$ar) + continue; + + $newArray[] = $ar; + } + + return $newArray; + } + + protected function initLogger() { + $this->log = new Monolog\Logger('intarocrm'); + $this->log->pushHandler(new Monolog\Handler\StreamHandler($this->dir . 'intarocrm_module.log', Monolog\Logger::INFO)); + } +} \ No newline at end of file diff --git a/system/library/intarocrm/composer.json b/system/library/intarocrm/composer.json new file mode 100644 index 0000000..202739e --- /dev/null +++ b/system/library/intarocrm/composer.json @@ -0,0 +1,34 @@ +{ + "name": "intarocrm/opencart-module", + "description": "Prestashop integration for IntaroCRM", + "type": "library", + "keywords": ["api", "Intaro CRM", "rest"], + "homepage": "http://www.intarocrm.ru/", + "authors": [ + { + "name": "Alex Lushpai", + "email": "lushpai@intaro.ru", + "role": "Developer" + } + ], + "support": { + "email": "support@intarocrm.ru" + }, + "require": { + "php": ">=5.3", + "intarocrm/rest-api-client": "1.2.*", + "symfony/console": "dev-master", + "monolog/monolog": "dev-master" + }, + "autoload": { + "psr-0": { + "": "/" + } + }, + "repositories": [ + { + "type": "git", + "url": "https://github.com/intarocrm/rest-api-client" + } + ] +} diff --git a/system/library/intarocrm/composer.lock b/system/library/intarocrm/composer.lock new file mode 100644 index 0000000..d035da2 --- /dev/null +++ b/system/library/intarocrm/composer.lock @@ -0,0 +1,231 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "de9234b1e9e1ac5b5aaa5c5377eaafce", + "packages": [ + { + "name": "intarocrm/rest-api-client", + "version": "v1.2.5", + "source": { + "type": "git", + "url": "https://github.com/intarocrm/rest-api-client", + "reference": "b54350ff2f09d8202cf2931895bba8dced4dcf21" + }, + "require": { + "ext-curl": "*", + "php": ">=5.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "IntaroCrm\\": "lib/" + } + }, + "authors": [ + { + "name": "Kruglov Kirill", + "email": "kruglov@intaro.ru", + "role": "Developer" + } + ], + "description": "PHP Client for IntaroCRM REST API", + "homepage": "http://www.intarocrm.ru/", + "keywords": [ + "Intaro CRM", + "api", + "rest" + ], + "support": { + "email": "support@intarocrm.ru" + }, + "time": "2014-04-13 09:58:37" + }, + { + "name": "monolog/monolog", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "12545cda2f7a0bd82a110f742ef455fe735e60cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/12545cda2f7a0bd82a110f742ef455fe735e60cf", + "reference": "12545cda2f7a0bd82a110f742ef455fe735e60cf", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "~2.4, >2.4.8", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "phpunit/phpunit": "~3.7.0", + "raven/raven": "~0.5", + "ruflin/elastica": "0.90.*", + "videlalvaro/php-amqplib": "~2.4" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "raven/raven": "Allow sending log messages to a Sentry server", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "videlalvaro/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "time": "2014-07-31 22:12:22" + }, + { + "name": "psr/log", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-0": { + "Psr\\Log\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2012-12-21 11:40:51" + }, + { + "name": "symfony/console", + "version": "dev-master", + "target-dir": "Symfony/Component/Console", + "source": { + "type": "git", + "url": "https://github.com/symfony/Console.git", + "reference": "919345e2757aa3d3aca1d1ce5a8f53f65d1990ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Console/zipball/919345e2757aa3d3aca1d1ce5a8f53f65d1990ef", + "reference": "919345e2757aa3d3aca1d1ce5a8f53f65d1990ef", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1", + "symfony/process": "~2.1" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Console\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony Console Component", + "homepage": "http://symfony.com", + "time": "2014-08-14 16:37:29" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "symfony/console": 20, + "monolog/monolog": 20 + }, + "prefer-stable": false, + "platform": { + "php": ">=5.3" + }, + "platform-dev": [] +} diff --git a/system/library/intarocrm/composer.phar b/system/library/intarocrm/composer.phar new file mode 100755 index 0000000..aa9c50d Binary files /dev/null and b/system/library/intarocrm/composer.phar differ