Files
kupshop/bundles/External/VarioBundle/SoapClient.php
2025-08-02 16:30:27 +02:00

520 lines
16 KiB
PHP

<?php
namespace External\VarioBundle;
use External\VarioBundle\Exception\SynchronizerException;
use External\VarioBundle\Util\VarioConfig;
use KupShop\KupShopBundle\Util\Functional\Mapping;
class SoapClient
{
public $settings;
public $config;
private $client;
public function __construct(VarioConfig $config)
{
$this->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');
}
}