<?php declare(strict_types=1);
namespace Res\ResChannableConnector\Subscriber;
use Doctrine\DBAL\Connection;
use Res\ResChannableConnector\Setting\Service\Config\ResChannableConfigService;
use Shopware\Core\Content\Product\ProductDefinition;
use Shopware\Core\Content\Product\ProductEntity;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityWriteResult;
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Pricing\PriceCollection;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Core\System\Language\LanguageCollection;
use Shopware\Core\System\SalesChannel\SalesChannelEntity;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\UpdateCommand;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Shopware\Core\Content\Product\ProductEvents;
use Shopware\Core\Checkout\Order\OrderEvents;
class Subscriber implements EventSubscriberInterface
{
/**
* @var ResChannableConfigService
*/
private $resChannableConfigService;
/**
* @var EntityRepositoryInterface
*/
private $salesChannelRepository;
/**
* @var EntityRepositoryInterface
*/
private $productRepository;
/**
* @var Connection
*/
private $connection;
/**
* Subscriber constructor.
*
* @param ResChannableConfigService $resChannableConfigService
* @param EntityRepositoryInterface $salesChannelRepository
* @param EntityRepositoryInterface $productRepository
* @param Connection $connection
*/
public function __construct(
ResChannableConfigService $resChannableConfigService,
EntityRepositoryInterface $salesChannelRepository,
EntityRepositoryInterface $productRepository,
Connection $connection
) {
$this->resChannableConfigService = $resChannableConfigService;
$this->salesChannelRepository = $salesChannelRepository;
$this->productRepository = $productRepository;
$this->connection = $connection;
}
/**
* @return array
*/
public static function getSubscribedEvents(): array
{
return [
ProductEvents::PRODUCT_WRITTEN_EVENT => 'onProductsWritten',
OrderEvents::ORDER_LINE_ITEM_WRITTEN_EVENT => 'onOrderLineItemWritten',
PreWriteValidationEvent::class => [['triggerChangeSet',100]],
];
}
/**
* @param PreWriteValidationEvent $event
* @throws \Doctrine\DBAL\DBALException
* @throws \Doctrine\DBAL\Exception
*/
public function triggerChangeSet(PreWriteValidationEvent $event): void
{
$writeCommands = $event->getCommands();
foreach ($writeCommands as $command) {
/** @var UpdateCommand $command */
if ($command instanceof UpdateCommand && $command->getDefinition()->getEntityName() === ProductDefinition::ENTITY_NAME) {
/** @var string $productId */
$productId = $command->getPrimaryKey()['id'];
if ( $productId ) {
$payload = $command->getPayload();
# Continue if stock not changed
if ( !isset($payload['stock']) )
continue;
$stockData = $this->connection->fetchAssoc(
'SELECT stock, available_stock FROM `product` WHERE `id` = :id',
['id' => $productId]
);
$newAvailableStock = $stockData['available_stock']+($payload['stock']-$stockData['stock']);
$context = Context::createDefaultContext();
$criteria = new Criteria();
$criteria->addAssociation('languages');
$salesChannels = $this->salesChannelRepository->search($criteria, $context);
# Walk through channels
/** @var SalesChannelEntity $salesChannel */
foreach ($salesChannels as $salesChannel) {
/** @var LanguageCollection $languages */
$languages = $salesChannel->getLanguages();
if ( $languages instanceof LanguageCollection ) {
foreach ($languages as $language) {
$langId = $language->getId();
$salesChannableId = $salesChannel->getId();
$webhookUrl = $this->resChannableConfigService->get('ResChannableConnector.settings.webhookUrl', $salesChannableId, $langId);
$webhookEnabled = $this->resChannableConfigService->get('ResChannableConnector.settings.webhookEnabled', $salesChannableId, $langId);
if (!$webhookUrl)
continue;
if (!$webhookEnabled)
continue;
$data = [
'id' => UUID::fromBytesToHex($productId),
'shop' => $salesChannableId,
'lang' => $langId,
'stock' => $newAvailableStock,
];
$this->_postData($data, $webhookUrl);
}
}
}
}
}
}
}
/**
* @param EntityWrittenEvent $event
*/
public function onProductsWritten(EntityWrittenEvent $event)
{
$context = Context::createDefaultContext();
$criteria = new Criteria();
$criteria->addAssociation('languages');
$salesChannels = $this->salesChannelRepository->search($criteria, $context);
if ( $salesChannels->count() === 0 )
return 0;
foreach ($event->getWriteResults() as $writeResult) {
if ($writeResult->getOperation() === EntityWriteResult::OPERATION_UPDATE) {
$payLoad = $writeResult->getPayload();
if ( empty($payLoad) )
continue;
# Walk through channels
/** @var SalesChannelEntity $salesChannel */
foreach ($salesChannels as $salesChannel) {
/** @var LanguageCollection $languages */
$languages = $salesChannel->getLanguages();
foreach ($languages as $language) {
$langId = $language->getId();
$salesChannableId = $salesChannel->getId();
$webhookUrl = $this->resChannableConfigService->get('ResChannableConnector.settings.webhookUrl', $salesChannableId, $langId);
$webhookEnabled = $this->resChannableConfigService->get('ResChannableConnector.settings.webhookEnabled', $salesChannableId, $langId);
if (!$webhookUrl)
continue;
if (!$webhookEnabled)
continue;
$data = $this->_convertProductWriteResult($payLoad, $salesChannel, $langId);
if ( empty($data) || count($data) < 4 )
continue;
$this->_postData($data, $webhookUrl);
}
}
}
}
}
/**
* @param EntityWrittenEvent $event
*/
public function onOrderLineItemWritten(EntityWrittenEvent $event)
{
$context = Context::createDefaultContext();
$criteria = new Criteria();
$criteria->addAssociation('languages');
$salesChannels = $this->salesChannelRepository->search($criteria, $context);
foreach ($event->getWriteResults() as $writeResult) {
if ($writeResult->getOperation() === EntityWriteResult::OPERATION_INSERT) {
$payLoad = $writeResult->getPayload();
if ( empty($payLoad) )
continue;
# Walk through channels
/** @var SalesChannelEntity $salesChannel */
foreach ($salesChannels as $salesChannel) {
/** @var LanguageCollection $languages */
$languages = $salesChannel->getLanguages();
foreach ($languages as $language) {
$langId = $language->getId();
$salesChannableId = $salesChannel->getId();
$webhookUrl = $this->resChannableConfigService->get('ResChannableConnector.settings.webhookUrl', $salesChannableId, $langId);
$webhookEnabled = $this->resChannableConfigService->get('ResChannableConnector.settings.webhookEnabled', $salesChannableId, $langId);
if (!$webhookUrl)
continue;
if (!$webhookEnabled)
continue;
$data = $this->_convertOrderLineItemWriteResult($payLoad, $salesChannel, $langId, $context);
if (empty($data) || count($data) < 4)
continue;
$this->_postData($data, $webhookUrl);
}
}
}
}
}
/**
* @param $payLoad
* @param SalesChannelEntity $salesChannel
* @param $langId
* @param Context $context
* @return array
*/
private function _convertOrderLineItemWriteResult($payLoad, $salesChannel, $langId, $context)
{
if ( !isset($payLoad['productId']) )
return;
$result = [
'id' => $payLoad['productId'],
'shop' => $salesChannel->getId(),
'lang' => $langId
];
$product = $this->_getProduct($payLoad['productId'], $context);
if ( !$product )
return;
if ( isset($payLoad['quantity']) )
$result['stock'] = $product->getAvailableStock();
return $result;
}
/**
* @param array $payLoad
* @param SalesChannelEntity $salesChannel
* @param $langId
* @return array
*/
private function _convertProductWriteResult($payLoad, $salesChannel, $langId)
{
if ( !isset($payLoad['id']) )
return;
$result = [
'id' => $payLoad['id'],
'shop' => $salesChannel->getId(),
'lang' => $langId
];
if ( isset($payLoad['productNumber']) )
$result['number'] = $payLoad['productNumber'];
if ( isset($payLoad['ean']) )
$result['ean'] = $payLoad['ean'];
if ( isset($payLoad['price']) ) {
/** @var PriceCollection $prices */
$prices = $payLoad['price'];
if ( $prices instanceof PriceCollection ) {
/** @var \Shopware\Core\Framework\DataAbstractionLayer\Pricing\Price $price */
$price = $prices->getCurrencyPrice($salesChannel->getCurrencyId());
$result['price'] = $price->getGross();
}
}
return $result;
}
/**
* @param $id
* @param $context
* @return ProductEntity|void
*/
private function _getProduct($id, $context)
{
$productCriteria = new Criteria([$id]);
$products = $this->productRepository->search($productCriteria, $context);
if ( !$products->count() )
return;
return $products->first();
}
/**
* Post data to Channable webhook url
*
* @param array $data
* @param string $url
*/
private function _postData($data, $url)
{
# Check webhook url
if ( !$url )
return;
# JSON encoding
$data = json_encode($data);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Content-Length: ' . strlen($data))
);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_exec($ch);
}
}