<?php declare(strict_types=1);
namespace Shopware\Core\Content\Product\Cart;
use Doctrine\DBAL\Connection;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemDefinition;
use Shopware\Core\Content\Product\Exception\ProductLineItemDifferentIdException;
use Shopware\Core\Content\Product\Exception\ProductLineItemInconsistentException;
use Shopware\Core\Defaults;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\SetNullOnDeleteCommand;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\UpdateCommand;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\WriteCommand;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
use Shopware\Core\Framework\Log\Package;
use Shopware\Core\Framework\Uuid\Uuid;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* @deprecated tag:v6.5.0 - reason:becomes-internal - EventSubscribers will become internal in v6.5.0
*/
#[Package('inventory')]
class ProductLineItemCommandValidator implements EventSubscriberInterface
{
private Connection $connection;
/**
* @internal
*/
public function __construct(Connection $connection)
{
$this->connection = $connection;
}
/**
* @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
*/
public static function getSubscribedEvents()
{
return [
PreWriteValidationEvent::class => 'preValidate',
];
}
public function preValidate(PreWriteValidationEvent $event): void
{
if ($event->getContext()->getVersionId() !== Defaults::LIVE_VERSION) {
return;
}
$products = $this->findProducts($event->getCommands());
foreach ($event->getCommands() as $command) {
if ($command->getDefinition()->getClass() !== OrderLineItemDefinition::class) {
continue;
}
if ($command instanceof SetNullOnDeleteCommand) {
continue;
}
$payload = $command->getPayload();
$lineItemId = Uuid::fromBytesToHex($command->getPrimaryKey()['id']);
$productIdChanged = \array_key_exists('product_id', $payload);
$referenceIdChanged = \array_key_exists('referenced_id', $payload);
$lineItemPayload = isset($payload['payload']) ? json_decode($payload['payload'], true) : [];
$orderNumberChanged = \array_key_exists('productNumber', $lineItemPayload);
if (!$this->isProduct($products, $payload, $lineItemId)) {
continue;
}
$somethingChanged = $productIdChanged || $referenceIdChanged || $orderNumberChanged;
$allChanged = $productIdChanged && $referenceIdChanged && $orderNumberChanged;
// has a field changed?
if (!$somethingChanged) {
continue;
}
$productId = isset($payload['product_id']) ? Uuid::fromBytesToHex($payload['product_id']) : null;
$referenceId = $payload['referenced_id'] ?? null;
if ($productId !== $referenceId) {
$event->getExceptions()->add(
new ProductLineItemDifferentIdException($lineItemId)
);
}
// all fields updated? everything is consistent
if ($allChanged) {
continue;
}
$event->getExceptions()->add(
new ProductLineItemInconsistentException($lineItemId)
);
}
}
/**
* @param list<WriteCommand> $commands
*
* @return array<string, int>
*/
private function findProducts(array $commands): array
{
$ids = array_map(function (WriteCommand $command) {
if ($command->getDefinition()->getClass() !== OrderLineItemDefinition::class) {
return null;
}
if ($command instanceof UpdateCommand) {
return $command->getPrimaryKey()['id'];
}
return null;
}, $commands);
$ids = array_values(array_filter($ids));
if (empty($ids)) {
return [];
}
/** @var array<string, int> $products */
$products = \array_flip($this->connection->fetchFirstColumn(
'SELECT DISTINCT LOWER(HEX(id)) FROM order_line_item WHERE id IN (:ids) AND type = \'product\'',
['ids' => $ids],
['ids' => Connection::PARAM_STR_ARRAY]
));
return $products;
}
/**
* @param array<string, mixed> $products
* @param array<string, mixed> $payload
*/
private function isProduct(array $products, array $payload, string $lineItemId): bool
{
if (isset($payload['type'])) {
return $payload['type'] === LineItem::PRODUCT_LINE_ITEM_TYPE;
}
return isset($products[$lineItemId]);
}
}