diff --git a/src/config/objects.xml b/src/config/objects.xml
index f5ca5cf..4480004 100644
--- a/src/config/objects.xml
+++ b/src/config/objects.xml
@@ -15,6 +15,20 @@
personalDiscount
discountCardNumber
+ id
+ externalId
+ nickName
+ vip
+ bad
+ customFields
+ personalDiscount
+ discountCardNumber
+ manager
+ address
+ mainCustomerContact
+ companyInn
+ company
+
index
country
region
@@ -62,6 +76,7 @@
shipmentStore
shipmentDate
shipped
+ contact
payment
amount
diff --git a/src/include/abstracts/class-wc-retailcrm-abstract-builder.php b/src/include/abstracts/class-wc-retailcrm-abstract-builder.php
new file mode 100644
index 0000000..a0842b3
--- /dev/null
+++ b/src/include/abstracts/class-wc-retailcrm-abstract-builder.php
@@ -0,0 +1,86 @@
+data = $data;
+ return $this;
+ }
+
+ /**
+ * @return array|mixed
+ */
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * @return $this|\WC_Retailcrm_Builder_Interface
+ */
+ public function reset()
+ {
+ $this->data = array();
+ return $this;
+ }
+
+ /**
+ * Returns key if it's present in data array (or object which implements ArrayAccess).
+ * Returns default value if key is not present in data, or data is not accessible as array.
+ *
+ * @param string $key
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ protected function dataValue($key, $default = '')
+ {
+ return self::arrayValue($this->data, $key, $default);
+ }
+
+ /**
+ * Returns key from array if it's present in array
+ *
+ * @param array|\ArrayObject $data
+ * @param mixed $key
+ * @param string $default
+ *
+ * @return mixed|string
+ */
+ protected static function arrayValue($data, $key, $default = '')
+ {
+ if (!is_array($data) && !($data instanceof ArrayAccess)) {
+ return $default;
+ }
+
+ if (array_key_exists($key, $data) && !empty($data[$key])) {
+ return $data[$key];
+ }
+
+ return $default;
+ }
+
+ /**
+ * @return \WC_Retailcrm_Builder_Interface
+ */
+ abstract public function build();
+
+ /**
+ * Returns builder result. Should return null if WC_Retailcrm_Abstract_Builder::isBuilt() == false.
+ *
+ * @return mixed|null
+ */
+ abstract public function getResult();
+}
diff --git a/src/include/class-wc-retailcrm-customers.php b/src/include/class-wc-retailcrm-customers.php
index 5690cf1..4caa04c 100644
--- a/src/include/class-wc-retailcrm-customers.php
+++ b/src/include/class-wc-retailcrm-customers.php
@@ -480,7 +480,8 @@ if (!class_exists('WC_Retailcrm_Customers')) :
}
}
} else {
- $customer = reset($search['customers']);
+ $dataCustomers = $search['customers'];
+ $customer = reset($dataCustomers);
}
} else {
$customer = !empty($search['customer']) ? $search['customer'] : false;
@@ -536,7 +537,8 @@ if (!class_exists('WC_Retailcrm_Customers')) :
if ($returnGroup) {
return $search['customersCorporate'];
} else {
- $customer = reset($search['customersCorporate']);
+ $dataCorporateCustomers = $search['customersCorporate'];
+ $customer = reset($dataCorporateCustomers);
}
} elseif (isset($search['customerCorporate'])) {
$customer = $search['customerCorporate'];
diff --git a/src/include/class-wc-retailcrm-history.php b/src/include/class-wc-retailcrm-history.php
index 729b037..6d4c100 100644
--- a/src/include/class-wc-retailcrm-history.php
+++ b/src/include/class-wc-retailcrm-history.php
@@ -101,13 +101,14 @@ if ( ! class_exists( 'WC_Retailcrm_History' ) ) :
* History customers
*
* @param string $date
- * @param int $since_id
+ * @param int $sinceId
*
- * @return null
+ * @return void
* @throws \Exception
*/
- protected function customersHistory($date, $since_id)
+ protected function customersHistory($date, $sinceId)
{
+<<<<<<< HEAD
if ($since_id) {
$response = $this->retailcrm->customersHistory(array('sinceId' => $since_id));
} else {
@@ -133,43 +134,73 @@ if ( ! class_exists( 'WC_Retailcrm_History' ) ) :
'address.country' => 'billing_country',
'address.city' => 'billing_city'
);
+=======
+ $filter = array('startDate' => $date);
- foreach ($history as $record) {
- if ($record['source'] == 'api' && $record['apiKey']['current'] == true) {
+ if ($sinceId) {
+ $filter = array('sinceId' => $sinceId);
+ }
+
+ $request = new WC_Retailcrm_Paginated_Request();
+ $history = $request
+ ->setApi($this->retailcrm)
+ ->setMethod('customersHistory')
+ ->setParams(array($filter, '{{page}}'))
+ ->setDataKey('history')
+ ->setLimit(100)
+ ->execute()
+ ->getData();
+
+ if (!empty($history)) {
+ $builder = new WC_Retailcrm_WC_Customer_Builder();
+ $lastChange = end($history);
+ $customers = WC_Retailcrm_History_Assembler::assemblyCustomer($history);
+ WC_Retailcrm_Plugin::$history_run = true;
+>>>>>>> WIP: Change client in the order (not ready at this point; also tests should fail)
+
+ foreach ($customers as $crmCustomer) {
+ if (!isset($crmCustomer['externalId'])) {
continue;
}
- if (isset($record['customer']['externalId'])) {
- $customer = new WC_Customer($record['customer']['externalId']);
+ try {
+ $builder->reset();
- if ($customer->get_id() == 0) {
+ if (!$builder->loadExternalId($crmCustomer['externalId'])) {
+ WC_Retailcrm_Logger::addCaller(__METHOD__, sprintf(
+ 'Customer with id=%s is not found in the DB, skipping...',
+ $crmCustomer['externalId']
+ ));
continue;
}
- }
- WC_Retailcrm_Plugin::$history_run = true;
+ $wcCustomer = $builder
+ ->setData($crmCustomer)
+ ->build()
+ ->getResult();
- if (isset($mapping[$record['field']]) && isset($record['customer']['externalId'])) {
- if ($record['newValue']) {
- if (is_array($mapping[$record['field']])) {
- foreach ($mapping[$record['field']] as $mappingField) {
- update_user_meta($record['customer']['externalId'], $mappingField, $record['newValue']);
- }
- } else {
- update_user_meta($record['customer']['externalId'], $mapping[$record['field']], $record['newValue']);
- }
+ if ($wcCustomer instanceof WC_Customer) {
+ $wcCustomer->save();
}
+
+ WC_Retailcrm_Logger::debug(__METHOD__, 'Updated WC_Customer:', $wcCustomer);
+ } catch (\Exception $exception) {
+ WC_Retailcrm_Logger::error(sprintf(
+ 'Error while trying to process history: %s',
+ $exception->getMessage()
+ ));
+ WC_Retailcrm_Logger::error(sprintf(
+ '%s:%d',
+ $exception->getFile(),
+ $exception->getLine()
+ ));
+ WC_Retailcrm_Logger::error($exception->getTraceAsString());
}
-
- WC_Retailcrm_Plugin::$history_run = false;
}
- }
- if (isset($new_since_id)) {
- update_option('retailcrm_customers_history_since_id', $new_since_id);
+ update_option('retailcrm_customers_history_since_id', $lastChange['id']);
+ WC_Retailcrm_Plugin::$history_run = false;
}
-
- return;
}
/**
@@ -182,22 +213,33 @@ if ( ! class_exists( 'WC_Retailcrm_History' ) ) :
*/
protected function ordersHistory($date, $since_id)
{
+ $filter = array('startDate' => $date);
$options = array_flip(array_filter($this->retailcrm_settings));
if ($since_id) {
- $response = $this->retailcrm->ordersHistory(array('sinceId' => $since_id));
- } else {
- $response = $this->retailcrm->ordersHistory(array('startDate' => $date));
+ $filter = array('sinceId' => $since_id);
}
+<<<<<<< HEAD
if (!empty($response) && $response->isSuccessful()) {
if (empty($response['history'])) {
return false;
}
+=======
+ $request = new WC_Retailcrm_Paginated_Request();
+ $history = $request
+ ->setApi($this->retailcrm)
+ ->setMethod('ordersHistory')
+ ->setParams(array($filter, '{{page}}'))
+ ->setDataKey('history')
+ ->setLimit(100)
+ ->execute()
+ ->getData();
+>>>>>>> WIP: Change client in the order (not ready at this point; also tests should fail)
- $history = $response['history'];
+ if (!empty($history)) {
$last_change = end($history);
- $historyAssembly = self::assemblyOrder($response['history']);
+ $historyAssembly = WC_Retailcrm_History_Assembler::assemblyOrder($history);
WC_Retailcrm_Plugin::$history_run = true;
@@ -559,7 +601,7 @@ if ( ! class_exists( 'WC_Retailcrm_History' ) ) :
$customerId = isset($order['customer']['externalId']) ? $order['customer']['externalId'] : null;
- if ($order['customer']['type'] == 'customer_corporate') {
+ if (self::isOrderCorporate($order)) {
$customerId = isset($order['contact']['externalId']) ? $order['contact']['externalId'] : null;
}
@@ -573,9 +615,18 @@ if ( ! class_exists( 'WC_Retailcrm_History' ) ) :
/** @var WC_Order|WP_Error $wc_order */
$wc_order = wc_create_order($args);
$customer = $order['customer'];
+ $contactOrCustomer = array();
$address = isset($order['customer']['address']) ? $order['customer']['address'] : array();
$companyName = '';
+ if (self::isOrderCorporate($order)) {
+ if (isset($order['contact'])) {
+ $contactOrCustomer = $order['contact'];
+ }
+ } else {
+ $contactOrCustomer = $customer;
+ }
+
if ($wc_order instanceof WP_Error) {
WC_Retailcrm_Logger::add(sprintf(
'[%d] error while creating order: %s',
@@ -590,12 +641,12 @@ if ( ! class_exists( 'WC_Retailcrm_History' ) ) :
$wc_order->add_order_note($order['managerComment'], 0, false);
}
- if (isset($order['customer']['type'])
- && $order['customer']['type'] == 'customer_corporate'
- && !empty($order['customer']['mainCompany'])
- && isset($order['customer']['mainCompany']['name'])
+ // TODO Check if that works; also don't forget to set this company field while creating order from CMS!
+ if (self::isOrderCorporate($order)
+ && !empty($order['company'])
+ && isset($order['company']['name'])
) {
- $companyName = $order['customer']['mainCompany']['name'];
+ $companyName = $order['company']['name'];
}
$address_shipping = array(
@@ -611,11 +662,11 @@ if ( ! class_exists( 'WC_Retailcrm_History' ) ) :
);
$address_billing = array(
- 'first_name' => $customer['firstName'],
- 'last_name' => isset($customer['lastName']) ? $customer['lastName'] : '',
+ 'first_name' => $contactOrCustomer['firstName'],
+ 'last_name' => isset($contactOrCustomer['lastName']) ? $contactOrCustomer['lastName'] : '',
'company' => $companyName,
'email' => isset($customer['email']) ? $customer['email'] : '',
- 'phone' => isset($customer['phones'][0]['number']) ? $customer['phones'][0]['number'] : '',
+ 'phone' => isset($contactOrCustomer['phones'][0]['number']) ? $contactOrCustomer['phones'][0]['number'] : '',
'address_1' => isset($address['text']) ? $address['text'] : '',
'address_2' => '',
'city' => isset($address['city']) ? $address['city'] : '',
@@ -872,154 +923,15 @@ if ( ! class_exists( 'WC_Retailcrm_History' ) ) :
}
/**
- * @param array $orderHistory
+ * Returns true if provided crm order is corporate
*
- * @return array
+ * @param array $order
+ *
+ * @return bool
*/
- public static function assemblyOrder($orderHistory)
+ private static function isOrderCorporate($order)
{
- $fields = array();
-
- if (file_exists(__DIR__ . '/../config/objects.xml')) {
- $objects = simplexml_load_file(__DIR__ . '/../config/objects.xml');
-
- foreach($objects->fields->field as $object) {
- $fields[(string)$object["group"]][(string)$object["id"]] = (string)$object;
- }
- }
-
- $orders = array();
-
- foreach ($orderHistory as $change) {
- if ($change['source'] == 'api'
- && isset($change['apiKey']['current'])
- && $change['apiKey']['current'] == true
- ) {
- continue;
- }
-
- $change['order'] = self::removeEmpty($change['order']);
- if(isset($change['order']['items']) && $change['order']['items']) {
- $items = array();
- foreach($change['order']['items'] as $item) {
- if(isset($change['created'])) {
- $item['create'] = 1;
- }
- $items[$item['id']] = $item;
- }
- $change['order']['items'] = $items;
- }
-
- if(isset($change['order']['contragent']['contragentType']) && $change['order']['contragent']['contragentType']) {
- $change['order']['contragentType'] = $change['order']['contragent']['contragentType'];
- unset($change['order']['contragent']);
- }
-
- if (!empty($orders) && isset($orders[$change['order']['id']])) {
- $orders[$change['order']['id']] = array_merge($orders[$change['order']['id']], $change['order']);
- } else {
- $orders[$change['order']['id']] = $change['order'];
- }
-
- if (isset($change['item']) && $change['item']) {
- if(isset($orders[$change['order']['id']]['items'][$change['item']['id']])) {
- $orders[$change['order']['id']]['items'][$change['item']['id']] = array_merge($orders[$change['order']['id']]['items'][$change['item']['id']], $change['item']);
- } else {
- $orders[$change['order']['id']]['items'][$change['item']['id']] = $change['item'];
- }
-
- if ($change['oldValue'] === null
- && $change['field'] == 'order_product'
- ) {
- $orders[$change['order']['id']]['items'][$change['item']['id']]['create'] = true;
- }
-
- if ($change['newValue'] === null
- && $change['field'] == 'order_product'
- ) {
- $orders[$change['order']['id']]['items'][$change['item']['id']]['delete'] = true;
- }
-
- if (!isset($orders[$change['order']['id']]['items'][$change['item']['id']]['create'])
- && isset($fields['item'][$change['field']])
- && $fields['item'][$change['field']]
- ) {
- $orders[$change['order']['id']]['items'][$change['item']['id']][$fields['item'][$change['field']]] = $change['newValue'];
- }
- } elseif ($change['field'] == 'payments' && isset($change['payment'])) {
- if ($change['newValue'] !== null) {
- $orders[$change['order']['id']]['payments'][] = self::newValue($change['payment']);
- }
- } else {
- if (isset($fields['delivery'][$change['field']]) && $fields['delivery'][$change['field']] == 'service') {
- $orders[$change['order']['id']]['delivery']['service']['code'] = self::newValue($change['newValue']);
- } elseif (isset($fields['delivery'][$change['field']]) && $fields['delivery'][$change['field']]) {
- $orders[$change['order']['id']]['delivery'][$fields['delivery'][$change['field']]] = self::newValue($change['newValue']);
- } elseif (isset($fields['orderAddress'][$change['field']]) && $fields['orderAddress'][$change['field']]) {
- $orders[$change['order']['id']]['delivery']['address'][$fields['orderAddress'][$change['field']]] = $change['newValue'];
- } elseif (isset($fields['integrationDelivery'][$change['field']]) && $fields['integrationDelivery'][$change['field']]) {
- $orders[$change['order']['id']]['delivery']['service'][$fields['integrationDelivery'][$change['field']]] = self::newValue($change['newValue']);
- } elseif (isset($fields['customerContragent'][$change['field']]) && $fields['customerContragent'][$change['field']]) {
- $orders[$change['order']['id']][$fields['customerContragent'][$change['field']]] = self::newValue($change['newValue']);
- } elseif (strripos($change['field'], 'custom_') !== false) {
- $orders[$change['order']['id']]['customFields'][str_replace('custom_', '', $change['field'])] = self::newValue($change['newValue']);
- } elseif (isset($fields['order'][$change['field']]) && $fields['order'][$change['field']]) {
- $orders[$change['order']['id']][$fields['order'][$change['field']]] = self::newValue($change['newValue']);
- }
-
- if (isset($change['created'])) {
- $orders[$change['order']['id']]['create'] = 1;
- }
-
- if (isset($change['deleted'])) {
- $orders[$change['order']['id']]['deleted'] = 1;
- }
- }
- }
-
- return $orders;
- }
-
- public static function assemblyCustomer($customerHistory)
- {
- $customers = array();
- foreach ($customerHistory as $change) {
- $change['order'] = self::removeEmpty($change['customer']);
-
- if (!empty($customers[$change['customer']['id']]) && $customers[$change['customer']['id']]) {
- $customers[$change['customer']['id']] = array_merge($customers[$change['customer']['id']], $change['customer']);
- } else {
- $customers[$change['customer']['id']] = $change['customer'];
- }
- }
-
- return $customers;
- }
-
- public static function newValue($value)
- {
- if (isset($value['code'])) {
- return $value['code'];
- } else {
- return $value;
- }
- }
-
- public static function removeEmpty($inputArray)
- {
- $outputArray = array();
- if (!empty($inputArray)) {
- foreach ($inputArray as $key => $element) {
- if(!empty($element) || $element === 0 || $element === '0'){
- if (is_array($element)) {
- $element = self::removeEmpty($element);
- }
- $outputArray[$key] = $element;
- }
- }
- }
-
- return $outputArray;
+ return isset($order['customer']['type']) && $order['customer']['type'] == 'customer_corporate';
}
}
diff --git a/src/include/class-wc-retailcrm-plugin.php b/src/include/class-wc-retailcrm-plugin.php
index f66761d..ea5722a 100644
--- a/src/include/class-wc-retailcrm-plugin.php
+++ b/src/include/class-wc-retailcrm-plugin.php
@@ -164,7 +164,6 @@ class WC_Retailcrm_Plugin {
return $result;
}
-
/**
* Check running history
*
diff --git a/src/include/components/class-wc-retailcrm-customer-data-replacer.php b/src/include/components/class-wc-retailcrm-customer-data-replacer.php
new file mode 100644
index 0000000..ec9eee7
--- /dev/null
+++ b/src/include/components/class-wc-retailcrm-customer-data-replacer.php
@@ -0,0 +1,152 @@
+reset();
+ }
+
+ /**
+ * In fact, this will execute customer change in provided order.
+ * This will not build anything.
+ *
+ * @return $this|\WC_Retailcrm_Builder_Interface
+ */
+ public function build()
+ {
+ $this->data->validate();
+
+ $wcOrder = $this->data->getWcOrder();
+ $newCustomer = $this->data->getNewCustomer();
+ $newCorporateCustomer = $this->data->getNewCorporateCustomer();
+ $newContact = $this->data->getNewContact();
+ $newCompany = $this->data->getNewCompany();
+
+ if (!empty($newCustomer)) {
+ $this->processChangeToRegular($wcOrder, $newCustomer);
+ return $this;
+ }
+
+ if (!empty($newContact)) {
+ $this->processChangeToContactPerson($wcOrder, $newContact);
+ }
+
+ if (!empty($newCompany)) {
+ $this->updateCompany($wcOrder, $newCorporateCustomer, $newCompany);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Change order customer to regular one
+ *
+ * @param \WC_Order $wcOrder
+ * @param array $newCustomer
+ */
+ public function processChangeToRegular($wcOrder, $newCustomer)
+ {
+ // TODO: Implement
+ }
+
+ /**
+ * Change order customer to corporate one (we only care about it's contact)
+ *
+ * @param \WC_Order $wcOrder
+ * @param array $newContact
+ */
+ public function processChangeToContactPerson($wcOrder, $newContact)
+ {
+ // TODO: Implement
+ }
+
+ /**
+ * Update company in the order
+ *
+ * @param WC_Order $wcOrder
+ * @param array $corporateCustomer
+ * @param array $company
+ */
+ public function updateCompany($wcOrder, $corporateCustomer, $company)
+ {
+ // TODO: Implement
+ }
+
+ /**
+ * @return $this|\WC_Retailcrm_Builder_Interface
+ */
+ public function reset()
+ {
+ $this->data = new WC_Retailcrm_Customer_Data_Replacer_State();
+ $this->result = null;
+ return $this;
+ }
+
+ /**
+ * Set initial state into component
+ *
+ * @param \WC_Retailcrm_Customer_Data_Replacer_State $data
+ *
+ * @return $this|\WC_Retailcrm_Builder_Interface
+ */
+ public function setData($data)
+ {
+ if (!($data instanceof WC_Retailcrm_Customer_Data_Replacer_State)) {
+ throw new \InvalidArgumentException('Invalid data type');
+ }
+
+ $this->data = $data;
+ return $this;
+ }
+
+ /**
+ * @return \WC_Retailcrm_Customer_Data_Replacer_Result|null
+ */
+ public function getResult()
+ {
+ return $this->result;
+ }
+
+ /**
+ * @return \WC_Retailcrm_Customer_Data_Replacer_State
+ */
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Returns WC_Customer by id. Returns null if there's no such customer.
+ *
+ * @param int $id
+ *
+ * @return \WC_Customer|null
+ */
+ private function getWcCustomerById($id)
+ {
+ try {
+ return new WC_Customer($id);
+ } catch (\Exception $exception) {
+ return null;
+ }
+ }
+}
diff --git a/src/include/components/class-wc-retailcrm-history-assembler.php b/src/include/components/class-wc-retailcrm-history-assembler.php
new file mode 100644
index 0000000..85ffe09
--- /dev/null
+++ b/src/include/components/class-wc-retailcrm-history-assembler.php
@@ -0,0 +1,402 @@
+ &$customer) {
+ if (empty($customer['id']) && !empty($id)) {
+ $customer['id'] = $id;
+ $customer['deleted'] = true;
+ }
+ }
+
+ return $customersCorporate;
+ }
+
+ /**
+ * Returns mapping data for retailCRM entities. Used to assembly entities from history.
+ *
+ * @param array $groupFilter
+ *
+ * @return array
+ */
+ private static function getMappingValues($groupFilter = array())
+ {
+ $fields = array();
+ $mappingFile = implode(
+ DIRECTORY_SEPARATOR,
+ array(WP_CONTENT_DIR, 'plugins', 'woo-retailcrm', 'config', 'objects.xml')
+ );
+
+ if (file_exists($mappingFile)) {
+ $objects = simplexml_load_file($mappingFile);
+
+ foreach($objects->fields->field as $object) {
+ if (empty($groupFilter) || in_array($object["group"], $groupFilter)) {
+ $fields[(string)$object["group"]][(string)$object["id"]] = (string)$object;
+ }
+ }
+ }
+
+ return $fields;
+ }
+
+ /**
+ * Value accessor
+ *
+ * @param array $value
+ *
+ * @return mixed
+ */
+ private static function newValue($value)
+ {
+ if (isset($value['code'])) {
+ return $value['code'];
+ } else {
+ return $value;
+ }
+ }
+
+ /**
+ * Returns array without values which are considered empty
+ *
+ * @param array|\ArrayAccess $inputArray
+ *
+ * @return array
+ */
+ private static function removeEmpty($inputArray)
+ {
+ $outputArray = array();
+
+ if (!empty($inputArray)) {
+ foreach ($inputArray as $key => $element) {
+ if(!empty($element) || $element === 0 || $element === '0'){
+ if (is_array($element)) {
+ $element = self::removeEmpty($element);
+ }
+
+ $outputArray[$key] = $element;
+ }
+ }
+ }
+
+ return $outputArray;
+ }
+
+ /**
+ * 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;
+ }
+}
\ No newline at end of file
diff --git a/src/include/class-wc-retailcrm-logger.php b/src/include/components/class-wc-retailcrm-logger.php
similarity index 50%
rename from src/include/class-wc-retailcrm-logger.php
rename to src/include/components/class-wc-retailcrm-logger.php
index 90816eb..319ecb4 100644
--- a/src/include/class-wc-retailcrm-logger.php
+++ b/src/include/components/class-wc-retailcrm-logger.php
@@ -52,24 +52,60 @@ if (!class_exists('WC_Retailcrm_Logger') && class_exists('WC_Log_Levels')):
}
/**
- * Debug logging. Contains a lot of debug data like full requests & responses.
+ * Regular logging with caller prefix
*
- * @param string $method
+ * @param string $caller
* @param string $message
* @param string $level
*/
- public static function debug($method, $message, $level = WC_Log_Levels::DEBUG)
+ public static function addCaller($caller, $message, $level = WC_Log_Levels::NOTICE)
+ {
+ self::add(sprintf('<%s> => %s', $caller, $message), $level);
+ }
+
+ /**
+ * Log error
+ *
+ * @param string $message
+ */
+ public static function error($message)
+ {
+ self::add($message, WC_Log_Levels::ERROR);
+ }
+
+ /**
+ * Debug logging. Contains a lot of debug data like full requests & responses.
+ * This log will work only if debug mode is enabled (see retailcrm_is_debug() for details).
+ * Caller should be specified, or message will be ignored at all.
+ *
+ * @param string $method
+ * @param array $messages
+ */
+ public static function debug($method, ...$messages)
{
if (retailcrm_is_debug()) {
- if (!empty($method)) {
- $message = sprintf(
- '<%s> => %s',
- $method,
- $message
+ if (!empty($method) && !empty($messages)) {
+ $result = substr(
+ array_reduce(
+ $messages,
+ function ($carry, $item) {
+ $carry .= ' ' . print_r($item, true);
+ return $carry;
+ }
+ ),
+ 1
+ );
+
+ self::getInstance()->add(
+ self::HANDLE . '_debug',
+ sprintf(
+ '<%s> => %s',
+ $method,
+ $result
+ ),
+ WC_Log_Levels::DEBUG
);
}
-
- self::getInstance()->add(self::HANDLE . '_debug', $message, $level);
}
}
}
diff --git a/src/include/components/class-wc-retailcrm-paginated-request.php b/src/include/components/class-wc-retailcrm-paginated-request.php
new file mode 100644
index 0000000..9c5fa28
--- /dev/null
+++ b/src/include/components/class-wc-retailcrm-paginated-request.php
@@ -0,0 +1,188 @@
+reset();
+ }
+
+ /**
+ * Sets retailCRM api client to request
+ *
+ * @param \WC_Retailcrm_Proxy|\WC_Retailcrm_Client_V5 $api
+ *
+ * @return self
+ */
+ public function setApi($api)
+ {
+ $this->api = $api;
+ return $this;
+ }
+
+ /**
+ * Sets API client method to request
+ *
+ * @param string $method
+ *
+ * @return self
+ */
+ 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 self
+ */
+ public function setParams($params)
+ {
+ $this->params = $params;
+ return $this;
+ }
+
+ /**
+ * Sets dataKey (key with data in response)
+ *
+ * @param string $dataKey
+ *
+ * @return self
+ */
+ public function setDataKey($dataKey)
+ {
+ $this->dataKey = $dataKey;
+ return $this;
+ }
+
+ /**
+ * Sets record limit per request
+ *
+ * @param int $limit
+ *
+ * @return self
+ */
+ 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 WC_Retailcrm_Response && $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/src/include/customer/woocommerce/class-wc-retailcrm-wc-customer-builder.php b/src/include/customer/woocommerce/class-wc-retailcrm-wc-customer-builder.php
new file mode 100644
index 0000000..f65cf77
--- /dev/null
+++ b/src/include/customer/woocommerce/class-wc-retailcrm-wc-customer-builder.php
@@ -0,0 +1,235 @@
+reset();
+ }
+
+ /**
+ * @param string $firstName
+ *
+ * @return $this
+ */
+ public function setFirstName($firstName)
+ {
+ $this->data['firstName'] = $firstName;
+ return $this;
+ }
+
+ /**
+ * @param string $lastName
+ *
+ * @return $this
+ */
+ public function setLastName($lastName)
+ {
+ $this->data['lastName'] = $lastName;
+ return $this;
+ }
+
+ /**
+ * @param string $email
+ *
+ * @return $this
+ */
+ public function setEmail($email)
+ {
+ $this->data['email'] = $email;
+ return $this;
+ }
+
+ /**
+ * @param string $externalId
+ *
+ * @return $this
+ */
+ public function setExternalId($externalId)
+ {
+ $this->data['externalId'] = $externalId;
+ return $this;
+ }
+
+ /**
+ * @param array $phones
+ *
+ * @return $this
+ */
+ public function setPhones($phones)
+ {
+ if (self::isPhonesArrayValid($phones)) {
+ $this->data['phones'] = $phones;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param array $address
+ *
+ * @return $this
+ */
+ public function setAddress($address)
+ {
+ if (is_array($address)) {
+ $this->data['address'] = $address;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param \WC_Customer $customer
+ *
+ * @return $this
+ */
+ public function setWcCustomer($customer)
+ {
+ if ($customer instanceof WC_Customer) {
+ $this->customer = $customer;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets provided externalId and loads associated customer from DB (it it exists there).
+ * Returns true if everything went find; returns false if customer wasn't found.
+ *
+ * @param string $externalId
+ *
+ * @return bool
+ * @throws \Exception
+ */
+ public function loadExternalId($externalId)
+ {
+ try {
+ $wcCustomer = new WC_Customer($externalId);
+ } catch (\Exception $exception) {
+ return false;
+ }
+
+ $this->setExternalId($externalId);
+ $this->setWcCustomer($wcCustomer);
+
+ return true;
+ }
+
+ public function reset()
+ {
+ parent::reset();
+ $this->customer = new WC_Customer();
+
+ return $this;
+ }
+
+ /**
+ * Fill WC_Customer fields with customer data from retailCRM.
+ * If field is not present in retailCRM customer - it will remain unchanged.
+ *
+ * @return $this|\WC_Retailcrm_Builder_Interface
+ */
+ public function build()
+ {
+ $this->checkBuilderValidity();
+ WC_Retailcrm_Logger::debug(__METHOD__, 'Building WC_Customer from data:', $this->data);
+ $this->customer->set_first_name($this->dataValue('firstName', $this->customer->get_first_name()));
+ $this->customer->set_last_name($this->dataValue('lastName', $this->customer->get_last_name()));
+ $this->customer->set_billing_email($this->dataValue('email', $this->customer->get_billing_email()));
+ $phones = $this->dataValue('phones', array());
+
+ if (count($phones) > 0) {
+ $phoneData = reset($phones);
+
+ if (is_array($phoneData) && isset($phoneData['number'])) {
+ $this->customer->set_billing_phone($phoneData['number']);
+ }
+ }
+
+ if (!empty($this->dataValue('address'))) {
+ $address = $this->dataValue('address');
+
+ $this->customer->set_billing_state(self::arrayValue(
+ $address,
+ 'region',
+ $this->customer->get_billing_state())
+ );
+ $this->customer->set_billing_postcode(self::arrayValue(
+ $address,
+ 'index',
+ $this->customer->get_billing_postcode())
+ );
+ $this->customer->set_billing_country(self::arrayValue(
+ $address,
+ 'country',
+ $this->customer->get_billing_country())
+ );
+ $this->customer->set_billing_city(self::arrayValue(
+ $address,
+ 'city',
+ $this->customer->get_billing_city())
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return mixed|\WC_Customer|null
+ */
+ public function getResult()
+ {
+ return $this->customer;
+ }
+
+ /**
+ * Throws an exception if internal state is not ready for data building.
+ *
+ * @throws \RuntimeException
+ */
+ private function checkBuilderValidity()
+ {
+ if (empty($this->data)) {
+ throw new \RuntimeException('Empty data');
+ }
+
+ if (!is_array($this->data)) {
+ throw new \RuntimeException('Data must be an array');
+ }
+ }
+
+ /**
+ * Returns true if provided variable contains array with customer phones.
+ *
+ * @param mixed $phones
+ *
+ * @return bool
+ */
+ private static function isPhonesArrayValid($phones)
+ {
+ if (!is_array($phones)) {
+ return false;
+ }
+
+ foreach ($phones as $phone) {
+ if (!is_array($phone) || count($phone) != 1 || !array_key_exists('number', $phone)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/include/customer/woocommerce/class-wc-retailcrm-wc-customer-corporate-builder.php b/src/include/customer/woocommerce/class-wc-retailcrm-wc-customer-corporate-builder.php
new file mode 100644
index 0000000..784ffd7
--- /dev/null
+++ b/src/include/customer/woocommerce/class-wc-retailcrm-wc-customer-corporate-builder.php
@@ -0,0 +1,248 @@
+reset();
+ }
+
+ /**
+ * @param array $contactPerson
+ *
+ * @return \WC_Retailcrm_WC_Customer_Corporate_Builder
+ */
+ public function setContactPerson($contactPerson)
+ {
+ $this->contactPerson = $contactPerson;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getContactPerson()
+ {
+ return $this->contactPerson;
+ }
+
+ /**
+ * @param \WC_Retailcrm_Builder_Interface $customerBuilder
+ */
+ public function setCustomerBuilder($customerBuilder)
+ {
+ $this->customerBuilder = $customerBuilder;
+ }
+
+ /**
+ * @param string $firstName
+ *
+ * @return $this
+ */
+ public function setFirstName($firstName)
+ {
+ $this->contactPerson['firstName'] = $firstName;
+ return $this;
+ }
+
+ /**
+ * @param string $lastName
+ *
+ * @return $this
+ */
+ public function setLastName($lastName)
+ {
+ $this->contactPerson['lastName'] = $lastName;
+ return $this;
+ }
+
+ /**
+ * @param string $email
+ *
+ * @return $this
+ */
+ public function setEmail($email)
+ {
+ $this->contactPerson['email'] = $email;
+ return $this;
+ }
+
+ /**
+ * @param string $externalId
+ *
+ * @return $this
+ */
+ public function setExternalId($externalId)
+ {
+ $this->contactPerson['externalId'] = $externalId;
+ return $this;
+ }
+
+ /**
+ * @param array $phones
+ *
+ * @return $this
+ */
+ public function setPhones($phones)
+ {
+ if (self::isPhonesArrayValid($phones)) {
+ $this->contactPerson['phones'] = $phones;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param array $address
+ *
+ * @return $this
+ */
+ public function setAddress($address)
+ {
+ if (is_array($address)) {
+ $this->contactPerson['address'] = $address;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param \WC_Customer $customer
+ *
+ * @return $this
+ */
+ public function setWcCustomer($customer)
+ {
+ if ($customer instanceof WC_Customer) {
+ $this->customer = $customer;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets provided externalId and loads associated customer from DB (it it exists there).
+ * Returns true if everything went find; returns false if customer wasn't found.
+ *
+ * @param string $externalId
+ *
+ * @return bool
+ * @throws \Exception
+ */
+ public function loadExternalId($externalId)
+ {
+ try {
+ $wcCustomer = new WC_Customer($externalId);
+ } catch (\Exception $exception) {
+ return false;
+ }
+
+ $this->setExternalId($externalId);
+ $this->setWcCustomer($wcCustomer);
+
+ return true;
+ }
+
+ public function reset()
+ {
+ parent::reset();
+ $this->customer = new WC_Customer();
+ $this->contactPerson = array();
+ $this->customerBuilder = new WC_Retailcrm_WC_Customer_Builder();
+
+ return $this;
+ }
+
+ /**
+ * Fill WC_Customer fields with customer data from retailCRM.
+ * If field is not present in retailCRM customer - it will remain unchanged.
+ *
+ * @return $this|\WC_Retailcrm_Builder_Interface
+ */
+ public function build()
+ {
+ $this->checkBuilderValidity();
+ WC_Retailcrm_Logger::debug(
+ __METHOD__,
+ 'Building WC_Customer from corporate data:',
+ $this->data,
+ "\nContact:",
+ $this->contactPerson
+ );
+
+ $wcCustomer = $this->customerBuilder
+ ->setData($this->contactPerson)
+ ->build()
+ ->getResult();
+
+ return $this;
+ }
+
+ /**
+ * @return mixed|\WC_Customer|null
+ */
+ public function getResult()
+ {
+ return $this->customer;
+ }
+
+ /**
+ * Throws an exception if internal state is not ready for data building.
+ *
+ * @throws \RuntimeException
+ */
+ private function checkBuilderValidity()
+ {
+ if (empty($this->data)) {
+ throw new \RuntimeException('Empty data');
+ }
+
+ if (!is_array($this->data)) {
+ throw new \RuntimeException('Data must be an array');
+ }
+ }
+
+ /**
+ * Returns true if provided variable contains array with customer phones.
+ *
+ * @param mixed $phones
+ *
+ * @return bool
+ */
+ private static function isPhonesArrayValid($phones)
+ {
+ if (!is_array($phones)) {
+ return false;
+ }
+
+ foreach ($phones as $phone) {
+ if (!is_array($phone) || count($phone) != 1 || !array_key_exists('number', $phone)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/include/interfaces/class-wc-retailcrm-builder-interface.php b/src/include/interfaces/class-wc-retailcrm-builder-interface.php
new file mode 100644
index 0000000..a493078
--- /dev/null
+++ b/src/include/interfaces/class-wc-retailcrm-builder-interface.php
@@ -0,0 +1,48 @@
+wcCustomer = $wcCustomer;
+ $this->wcOrder = $wcOrder;
+
+ if ((!is_null($this->wcCustomer) && !($this->wcCustomer instanceof WC_Customer))
+ || !($this->wcOrder instanceof WC_Order)
+ ) {
+ throw new \InvalidArgumentException(sprintf('Incorrect data provided to %s', __CLASS__));
+ }
+ }
+
+ /**
+ * @return \WC_Customer|null
+ */
+ public function getWcCustomer()
+ {
+ return $this->wcCustomer;
+ }
+
+ /**
+ * @return \WC_Order
+ */
+ public function getWcOrder()
+ {
+ return $this->wcOrder;
+ }
+}
diff --git a/src/include/models/class-wc-retailcrm-customer-data-replacer-state.php b/src/include/models/class-wc-retailcrm-customer-data-replacer-state.php
new file mode 100644
index 0000000..e7a0180
--- /dev/null
+++ b/src/include/models/class-wc-retailcrm-customer-data-replacer-state.php
@@ -0,0 +1,146 @@
+wcOrder;
+ }
+
+ /**
+ * @param \WC_Order $wcOrder
+ *
+ * @return WC_Retailcrm_Customer_Data_Replacer_State
+ */
+ public function setWcOrder($wcOrder)
+ {
+ $this->wcOrder = $wcOrder;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getNewCustomer()
+ {
+ return $this->newCustomer;
+ }
+
+ /**
+ * @param array $newCustomer
+ *
+ * @return WC_Retailcrm_Customer_Data_Replacer_State
+ */
+ public function setNewCustomer($newCustomer)
+ {
+ $this->newCustomer = $newCustomer;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getNewContact()
+ {
+ return $this->newContact;
+ }
+
+ /**
+ * @param array $newContact
+ *
+ * @return WC_Retailcrm_Customer_Data_Replacer_State
+ */
+ public function setNewContact($newContact)
+ {
+ $this->newContact = $newContact;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getNewCorporateCustomer()
+ {
+ return $this->newCorporateCustomer;
+ }
+
+ /**
+ * @param array $newCorporateCustomer
+ *
+ * @return WC_Retailcrm_Customer_Data_Replacer_State
+ */
+ public function setNewCorporateCustomer($newCorporateCustomer)
+ {
+ $this->newCorporateCustomer = $newCorporateCustomer;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getNewCompany()
+ {
+ return $this->newCompany;
+ }
+
+ /**
+ * @param array $newCompany
+ *
+ * @return WC_Retailcrm_Customer_Data_Replacer_State
+ */
+ public function setNewCompany($newCompany)
+ {
+ $this->newCompany = $newCompany;
+ return $this;
+ }
+
+ /**
+ * Throws an exception if state is not valid
+ *
+ * @throws \InvalidArgumentException
+ * @return void
+ */
+ public function validate()
+ {
+ if (empty($this->getWcOrder())) {
+ throw new \InvalidArgumentException('Empty WC_Order.');
+ }
+
+ if (empty($this->getNewCustomer())
+ && empty($this->getNewContact())
+ && empty($this->getNewCorporateCustomer())
+ ) {
+ throw new \InvalidArgumentException('New customer, new contact and new corporate customer is empty.');
+ }
+
+ if (!empty($this->getNewCustomer())
+ && (!empty($this->getNewContact()) || !empty($this->getNewCorporateCustomer()))
+ ) {
+ throw new \InvalidArgumentException(
+ 'Too much data in state - cannot determine which customer should be used.'
+ );
+ }
+ }
+}
diff --git a/src/retailcrm.php b/src/retailcrm.php
index 8c1392f..e3247fb 100644
--- a/src/retailcrm.php
+++ b/src/retailcrm.php
@@ -41,10 +41,19 @@ if (!class_exists( 'WC_Integration_Retailcrm')) :
$this->load_plugin_textdomain();
if (class_exists( 'WC_Integration' )) {
- require_once(dirname(__FILE__ ) . '/include/class-wc-retailcrm-logger.php');
+ require_once(dirname(__FILE__) . '/include/interfaces/class-wc-retailcrm-builder-interface.php');
+ require_once(dirname(__FILE__ ) . '/include/models/class-wc-retailcrm-customer-data-replacer-state.php');
+ require_once(dirname(__FILE__ ) . '/include/models/class-wc-retailcrm-customer-data-replacer-result.php');
+ require_once(dirname(__FILE__ ) . '/include/components/class-wc-retailcrm-logger.php');
+ require_once(dirname(__FILE__ ) . '/include/components/class-wc-retailcrm-history-assembler.php');
+ require_once(dirname(__FILE__ ) . '/include/components/class-wc-retailcrm-paginated-request.php');
+ require_once(dirname(__FILE__ ) . '/include/components/class-wc-retailcrm-customer-data-replacer.php');
+ require_once(dirname(__FILE__ ) . '/include/components/class-wc-retailcrm-customer-data-replacer.php');
+ require_once(dirname(__FILE__ ) . '/include/abstracts/class-wc-retailcrm-abstract-builder.php');
require_once(dirname(__FILE__ ) . '/include/abstracts/class-wc-retailcrm-abstracts-settings.php');
require_once(dirname(__FILE__ ) . '/include/abstracts/class-wc-retailcrm-abstracts-data.php');
require_once(dirname(__FILE__ ) . '/include/abstracts/class-wc-retailcrm-abstracts-address.php');
+ require_once(dirname(__FILE__ ) . '/include/customer/woocommerce/class-wc-retailcrm-wc-customer-builder.php');
require_once(dirname(__FILE__ ) . '/include/order/class-wc-retailcrm-order.php');
require_once(dirname(__FILE__ ) . '/include/order/class-wc-retailcrm-order-payment.php');
require_once(dirname(__FILE__ ) . '/include/order/class-wc-retailcrm-order-item.php');