config = $config; } public function getClient(): \SoapClient { if (!$this->client) { ini_set('default_socket_timeout', 60); $this->client = new \SoapClient($this->getWsdl(), [ 'trace' => 1, 'features' => SOAP_SINGLE_ELEMENT_ARRAYS, 'connection_timeout' => 60, ]); if ($clientNumber = $this->getClientNumber()) { $auth = new \stdClass(); $auth->ClientNumber = $clientNumber; $this->client->__setSoapHeaders( new \SoapHeader('urn:VarioSOAPServiceIntf', 'TAuthHeader', $auth) ); } } return $this->client; } public function getProductByCode(string $code): ?object { $products = $this->getClient()->GetProductsByCriteria($code); if (count($products) <= 0) { return null; } return reset($products); } public function getCustomerByEmail(string $email): ?object { $customer = $this->getClient()->GetCustomersByCriteria(null, null, $email); if (count($customer) <= 0) { return null; } return reset($customer); } public function getCustomerIds(): array { return $this->getClient()->GetCustomers(); } public function getCategories(bool $onlyIds = false): array { static $categoriesCache = []; if (empty($categoriesCache[$onlyIds])) { $categories = $this->getClient()->GetCategories(); foreach ($categories as $categoryId) { $categoriesCache[$onlyIds][$categoryId] = $onlyIds ? $categoryId : (array) $this->getClient()->GetCategory($categoryId); } } return $categoriesCache[$onlyIds] ?? []; } public function getParameters(): array { static $parametersCache = []; if (empty($parametersCache)) { $parameters = $this->getClient()->GetParametres(); foreach ($parameters as $parameterId) { $parametersCache[$parameterId] = $this->getClient()->GetParameter($parameterId); } } return $parametersCache; } public function getStores(): array { static $storesCache = []; if (empty($storesCache)) { $stores = $this->getClient()->GetStores(); foreach ($stores as $storeId) { $store = $this->getClient()->GetStore($storeId); $storesCache[$store->ID] = $store->StoreName; } } return $storesCache; } public function getPriceLists(): array { static $priceListsCache = []; if (empty($priceListsCache)) { $priceLists = $this->getClient()->GetPricelists(); foreach ($priceLists as $priceListId) { $priceList = $this->getClient()->GetPricelist($priceListId); $priceListsCache[$priceList->ID] = $priceList->PriceListName; } } return $priceListsCache; } public function createOrUpdate($type, $data) { if (isDevelopment()) { return null; } if (empty($data)) { return null; } $method = null; switch ($type) { case '_otCustomer': case 'otCustomer': $method = 'CreateOrUpdateCustomer'; break; } if (!$method) { throw new SynchronizerException( sprintf('Unknown type %s in method createOrUpdate()', $type) ); } return $this->getClient()->$method($data); } public function createOrUpdateOrder($order) { if (isDevelopment()) { return; } $varioOrderId = $this->getClient()->CreateOrUpdateDocument($order); if (empty($varioOrderId)) { throw new SynchronizerException('Sending order to Vario failed!'); } return $varioOrderId; } public function updateProduct($product, $fields): ?string { $varioProductId = $this->getClient()->UpdateProduct($product, $fields); if (empty($varioProductId)) { throw new SynchronizerException('Update product to Vario failed!'); } return $varioProductId; } public function setJobsDone(array $jobIds): void { if (isDevelopment()) { return; } if (!empty($jobIds)) { try { $this->getClient()->SetJobs($jobIds, true); } catch (\Exception $e) { getRaven()->captureException($e); } } } public function setJobDone($jobId) { // do not set job done if in development if (isDevelopment()) { return; } if ($jobId) { try { $this->getClient()->SetJob($jobId, true); } catch (\Exception $e) { getRaven()->captureException($e); } } } public function forceObjectData(string $type, $objectID) { $idField = 'ID'; switch ($type) { case 'otDocument': $method = 'GetDocument'; break; case 'otProduct': case 'otProductSimple': $method = 'GetProduct'; $idField = 'Id'; break; case 'otAvailableSupply': $method = 'GetAvailableSuppliesByProduct'; $data = $this->getClient()->$method($objectID); $return = []; foreach ($data as $item) { $return[] = $this->createSyncItem([ 'ID' => false, 'ObjectID' => trim($item->ID, '{}'), 'Action' => 'acInsert', ], $item); } return $return; case 'otSupply': $method = 'GetSuppliesByProduct'; $data = $this->getClient()->$method($objectID); $return = []; foreach ($data as $item) { $return[] = $this->createSyncItem([ 'ID' => false, 'ObjectID' => trim($item->ID, '{}'), 'Action' => 'acInsert', ], $item); } return $return; case 'otCategory': $method = 'GetCategory'; break; case 'otCustomer': $method = 'GetCustomer'; break; case 'otTranslation': $method = 'GetTranslation'; break; case 'otPrice': try { $data = $this->getClient()->GetPricesByProduct($objectID); } catch (\Exception $e) { getRaven()->captureException($e); $data = []; } $return = []; foreach ($data as $item) { $return[] = $this->createSyncItem([ 'ID' => false, 'ObjectID' => trim($item->ID, '{}'), 'Action' => 'acInsert', ], $item); } return $return; default: throw new SynchronizerException( sprintf('Type %s does not support single object synchronization! You have to implement it if you need it.', $type) ); } $data = $this->getClient()->$method($objectID); if ($data) { return [ 0 => $this->createSyncItem( [ 'ID' => false, 'ObjectID' => trim($data->$idField, '{}'), 'Action' => 'acInsert', ], $data ), ]; } return []; } public function getJobsData(string $type, int $batch) { switch ($type) { // custom type - creating result equals to GetJobsData case 'otPricelist': $pricelists = $this->getClient()->GetPricelists(); $return = []; foreach ($pricelists as $pricelist) { if (!in_array(trim($pricelist, '{}'), $this->config->getPriceListIds())) { continue; } $data = $this->getClient()->GetPricelist($pricelist); $return[] = $this->createSyncItem([ 'ID' => false, 'ObjectID' => trim($pricelist, '{}'), 'Action' => 'acInsert', ], $data); } return $return; case 'otStore': // fetch all stores - to have mapping of store IDs (then in supplies sync is not need of fetching store) $stores = $this->getClient()->GetStores(); $return = []; foreach ($stores as $store) { if (!in_array(trim($store, '{}'), $this->config->get('stores'))) { continue; } $return[] = $this->createSyncItem([ 'ID' => false, 'ObjectID' => trim($store, '{}'), 'Action' => 'acInsert', ], $this->getClient()->GetStore($store)); } return $return; case 'otParameter': $parameters = $this->getParameters(); $return = []; foreach ($parameters as $parameterId => $parameter) { $return[] = $this->createSyncItem([ 'ID' => false, 'ObjectID' => trim($parameterId, '{}'), 'Action' => 'acInsert', ], $parameter); } return $return; // some types are not supported by GetJobsData :/ - use prepareJobsData to get same result as in GetJobsData case 'otDocument': return $this->prepareJobsData($batch, $type, 'GetDocument'); case 'otAvailableSupply': return $this->prepareJobsData($batch, $type, 'GetAvailableSupply'); case 'otSupply': return $this->prepareJobsData($batch, $type, 'GetSupply'); case 'otCategory': return $this->prepareJobsData($batch, $type, 'GetCategory'); case 'otProductCategory': return $this->prepareJobsData($batch, $type, 'GetProductCategory'); case 'otTranslation': return $this->prepareJobsData($batch, $type, 'GetTranslation', null, false, true); case 'otProductSimple': $jobs = $this->getClient()->GetJobsByType($batch, $type); $items = Mapping::mapKeys($this->getClient()->GetProductsArray( array_map( function ($x) { return $x->ObjectID; }, $jobs ) ), function ($k, $v) { return [trim($v->Id ?? null, '{}'), $v]; }); $result = []; foreach ($jobs as $job) { if ($data = ($items[trim($job->ObjectID, '{}')] ?? null)) { $result[] = $this->createSyncItem((array) $job, $data); } } return $result; case 'otCustomer': return $this->prepareJobsData($batch, $type, 'GetCustomer'); case 'otProductExt': return $this->prepareJobsData($batch, $type, 'GetProductExt'); default: return $this->getClient()->GetJobsData( $batch, $type ); } } public function getSuppliesByProduct(string $productId, ?string $variationId = null): ?array { try { $supplies = $this->getClient()->GetAvailableSuppliesByProduct($productId); } catch (\Throwable $e) { $supplies = null; } if ($supplies === null) { return null; } // filter only active stores $supplies = array_filter($supplies, function ($x) { return in_array(trim($x->StoreID, '{}'), $this->config->getStores()); }); // filter by variation if ($variationId) { $supplies = array_filter($supplies, function ($x) use ($variationId) { return $x->VariantID === '{'.$variationId.'}'; }); } return $supplies; } public function getProductIds(): iterable { $page = 0; do { $products = $this->getClient()->GetProductsPage(1000, $page++); foreach ($products as $id) { yield $id; } } while (!empty($products)); } protected function prepareJobsData(int $batch, string $type, string $objectGetMethod, ?callable $dataCallback = null, bool $multi = false, bool $ignoreErrors = false) { $items = []; $jobs = $this->getClient()->GetJobsByType($batch, $type); $index = 0; $multiData = []; if ($multi) { $multiData = Mapping::mapKeys( array_filter($this->getClient()->$objectGetMethod(array_map(function ($x) { return $x->ObjectID; }, $jobs))), function ($k, $v) { return [$v->ID, $v]; } ); } $doneJobs = []; foreach ($jobs as $job) { if (isset($job->Action) && $job->Action == 'acDelete') { $data = new \stdClass(); } else { $data = $this->ignoreErrors(function () use ($multi, $multiData, $job, $objectGetMethod) { if ($multi && $multiData) { return $multiData[$job->ObjectID] ?? null; } else { return $this->getClient()->$objectGetMethod($job->ObjectID); } }, $ignoreErrors); if ($dataCallback !== null) { $data = $dataCallback($data); } } if ($data) { $items[$index] = new \stdClass(); $items[$index]->Job = $job; $items[$index]->Data = $data; $index++; } else { if (!empty($job->ID)) { $doneJobs[] = $job->ID; } } } if (!empty($doneJobs)) { $this->setJobsDone($doneJobs); } return $items; } protected function getClientNumber(): ?int { return $this->config->get('client_number'); } private function ignoreErrors(callable $callback, bool $ignoreErrors = false) { if ($ignoreErrors) { try { return $callback(); } catch (\Exception $e) { return null; } } return $callback(); } private function createSyncItem(array $job, $data) { $item = new \stdClass(); $item->Job = new \stdClass(); $item->Job->ID = $job['ID']; $item->Job->ObjectID = $job['ObjectID']; $item->Job->Action = $job['Action']; $item->Job->Created = $job['Created'] ?? null; $item->Data = $data; return $item; } protected function getWsdl() { return $this->config->get('wsdl'); } }