<?php declare(strict_types=1);
namespace Neon\Unitarticles\Subscribers;
use Neon\Unitarticles\Cart\Checkout\ProductUnitarticleInvalidError;
use Neon\Unitarticles\Cart\Checkout\ProductUnitarticleStockReachedError;
use Neon\Unitarticles\NeonPlatformUnitarticles;
use Neon\Unitarticles\Services\PriceFactorService;
use Neon\Unitarticles\Services\UnitarticlesLineItemValidator;
use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Cart\Event\BeforeLineItemAddedEvent;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
use Shopware\Core\Content\Product\ProductEntity;
use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Core\System\SalesChannel\Entity\SalesChannelRepository;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class OnLineItemAdded implements EventSubscriberInterface
{
/** @var RequestStack */
private $requestStack;
/** @var SalesChannelRepository */
private $productRepository;
/** @var PriceFactorService */
private $priceFactorService;
/** @var UnitarticlesLineItemValidator */
private $unitarticlesLineItemValidator;
public function __construct($requestStack, $productRepository, $priceFactorService, $unitarticlesLineItemValidator)
{
$this->requestStack = $requestStack;
$this->productRepository = $productRepository;
$this->priceFactorService = $priceFactorService;
$this->unitarticlesLineItemValidator = $unitarticlesLineItemValidator;
}
/**
* @inheritDoc
*/
public static function getSubscribedEvents()
{
return [
BeforeLineItemAddedEvent::class => 'onLineItemAdded'
];
}
/**
* @param BeforeLineItemAddedEvent $event
* @throws \Shopware\Core\Checkout\Cart\Exception\InvalidPayloadException
* @throws \Shopware\Core\Checkout\Cart\Exception\InvalidQuantityException
* @throws \Shopware\Core\Checkout\Cart\Exception\LineItemNotStackableException
* @throws \Shopware\Core\Checkout\Cart\Exception\MixedLineItemTypeException
* @throws \Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException
*/
public function onLineItemAdded(BeforeLineItemAddedEvent $event): void
{
$lineItem = $event->getLineItem();
$isExistingProductAfterLogin = $event->getLineItem()->getPayloadValue(NeonPlatformUnitarticles::PAYLOAD_KEY) != null;
if (!$isExistingProductAfterLogin && $lineItem->getType() === LineItem::PRODUCT_LINE_ITEM_TYPE) {
$product = $this->getProduct($event, $lineItem);
$lineItemParam = $this->requestStack->getCurrentRequest()->request->get('lineItems');
if ($lineItemParam == null) {
$lineItemParam = $this->getDefaultUnit($product, $lineItem);
if ($this->isQuantityHidden($product)) {
$lineItemParam = $this->updateLineItem($lineItemParam, $lineItem, $product);
}
}
if ($this->isProductUnitarticle($product) && $lineItemParam != null) {
if ($this->isMergeUserValues($product)) {
foreach ($event->getCart()->getLineItems() as $cartLineItem) {
if (array_key_exists($lineItem->getId(), $lineItemParam) &&
$cartLineItem->getReferencedId() == $lineItemParam[$lineItem->getId()]['referencedId']) {
$payload = $cartLineItem->getPayloadValue(NeonPlatformUnitarticles::PAYLOAD_KEY);
if ($payload) {
foreach ($lineItemParam[$lineItem->getId()]['neonUnitarticleUnitValue'] as $key => $userValue) {
$formattedUserValue = floatval(str_replace(',', '.', $userValue));
$payload['dimensions'][$key - 1]['unitvalue'] = $payload['dimensions'][$key - 1]['unitvalue'] + $formattedUserValue;
}
$cartLineItem->setQuantity(1);
$cartLineItem->setPayloadValue(NeonPlatformUnitarticles::PAYLOAD_KEY, $payload);
}
}
}
}
$unitValues = [];
if (array_key_exists($lineItem->getId(), $lineItemParam)) {
foreach ($lineItemParam[$lineItem->getId()][NeonPlatformUnitarticles::PARAM_UNITVALUE] as $dimensionNumber => $value) {
$descriptionKey = substr(NeonPlatformUnitarticles::CUSTOMFIELD_DESCRIPTION, 0, strlen(NeonPlatformUnitarticles::CUSTOMFIELD_DESCRIPTION) - 1) . $dimensionNumber;
$unitlabelKey = substr(NeonPlatformUnitarticles::CUSTOMFIELD_UNITLABEL, 0, strlen(NeonPlatformUnitarticles::CUSTOMFIELD_UNITLABEL) - 1) . $dimensionNumber;
$floatval = floatval(str_replace(",", ".", $value . ''));
$unitValues[] = [
'dimension' => $dimensionNumber,
'unitvalue' => $floatval,
'description' => $product->getCustomFields()[$descriptionKey],
'unitlabel' => $product->getCustomFields()[$unitlabelKey]
];
}
$isValid = $this->unitarticlesLineItemValidator->isValid($unitValues, $product);
$isUnitArticleStockReached = false;
if (!$isValid) {
$this->setProductUnitarticleInvalidError($event->getCart(), $product);
$isUnitArticleStockReached = true;
}
if ($isValid && $this->isQuantityHidden($product)) {
$totalQuantity = $this->getQuantityOfSameProducts($event->getCart(), $product);
if ($totalQuantity > $product->getCalculatedMaxPurchase()) {
$isUnitArticleStockReached = true;
$this->setProductUnitarticleStockReachedError($event->getCart(), $product->getCustomFields()[$unitlabelKey],
$product->getCustomFields()[$descriptionKey], $product->getCalculatedMaxPurchase(), $product);
}
}
if (!$isUnitArticleStockReached && $isValid) {
$lineItem->setPayloadValue(NeonPlatformUnitarticles::PAYLOAD_KEY,
[
NeonPlatformUnitarticles::PAYLOAD_KEY_DIMENSION => $unitValues,
'isQuantityHidden' => $this->isQuantityHidden($product),
'isMergeUserValues' => $this->isMergeUserValues($product),
'basePrice' => $this->getBasePrice($product),
'basePriceUnitValue' => $product->getCustomFields()[NeonPlatformUnitarticles::CUSTOMFIELD_BASEPRICEVALUE],
'surcharges' => $this->getSurcharges($product)]
);
} else {
$event->getCart()->remove($lineItem->getId());
}
}
}
}
}
private function setProductUnitarticleInvalidError(Cart $toCalculate, ProductEntity $product)
{
$toCalculate->addErrors(
new ProductUnitarticleInvalidError(
$product->getId(),
(string)$product->getTranslation('name'))
);
}
private function setProductUnitarticleStockReachedError(Cart $cart, string $unitLabel, string $description, int $quantity, ProductEntity $product): void
{
$cart->addErrors(
new ProductUnitarticleStockReachedError(
$product->getId(),
(string)$product->getTranslation('name'),
$quantity / $this->priceFactorService->calculateWithCustomFields($product->getCustomFields()),
$unitLabel,
$description)
);
}
private function getQuantityOfSameProducts(Cart $cart, ProductEntity $referenceProduct)
{
$quantity = 0;
foreach ($cart->getLineItems() as $lineItem) {
if ($referenceProduct->getId() == $lineItem->getReferencedId()) {
$quantity += $lineItem->getQuantity();
}
}
return $quantity;
}
private function getSurcharges($product)
{
$surcharges = [];
if ($this->getSurcharge1($product)) {
$surcharges[] = $this->getSurcharge1($product);
}
if ($this->getSurcharge2($product)) {
$surcharges[] = $this->getSurcharge2($product);
}
return $surcharges;
}
private function getSurcharge1($product)
{
$value = floatval($this->getProductCustomField($product, NeonPlatformUnitarticles::CUSTOMFIELD_SURCHARGE1VALUE));
$label = $this->getProductCustomField($product, NeonPlatformUnitarticles::CUSTOMFIELD_SURCHARGE1LABEL);
return $value !== null
? [
'value' => $value,
'label' => $label
]
: null;
}
private function getSurcharge2($product)
{
$value = floatval($this->getProductCustomField($product, NeonPlatformUnitarticles::CUSTOMFIELD_SURCHARGE2VALUE));
$label = $this->getProductCustomField($product, NeonPlatformUnitarticles::CUSTOMFIELD_SURCHARGE2LABEL);
return $value !== null
? [
'value' => $value,
'label' => $label
]
: null;
}
/**
* @param ProductEntity $product
* @return bool
*/
private function isProductUnitarticle($product): bool
{
return isset($product->getCustomFields()[NeonPlatformUnitarticles::CUSTOMFIELD_ISUNITARTICLE]) && $product->getCustomFields()[NeonPlatformUnitarticles::CUSTOMFIELD_ISUNITARTICLE];
}
/**
* @param BeforeLineItemAddedEvent $event
* @param LineItem $lineItem
* @throws \Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException
*/
private function getProduct(BeforeLineItemAddedEvent $event, LineItem $lineItem): SalesChannelProductEntity
{
$productCollection = $this->productRepository->search(new Criteria([
$lineItem->getReferencedId()
]), $event->getSalesChannelContext());
if ($productCollection->count() === 1) {
$product = $productCollection->first();
}
return $product;
}
/**
* @param ProductEntity $product
* @return bool
*/
private function isQuantityHidden(SalesChannelProductEntity $product): bool
{
return array_key_exists(NeonPlatformUnitarticles::CUSTOMFIELD_HIDEQUANTITYSELECTION, $product->getCustomFields()) && $product->getCustomFields()[NeonPlatformUnitarticles::CUSTOMFIELD_HIDEQUANTITYSELECTION];
}
/**
* @param ProductEntity $product
* @return bool
*/
private function isMergeUserValues(SalesChannelProductEntity $product): bool
{
return array_key_exists(NeonPlatformUnitarticles::CUSTOMFIELD_MERGEUSERVALUES, $product->getCustomFields()) && $product->getCustomFields()[NeonPlatformUnitarticles::CUSTOMFIELD_MERGEUSERVALUES];
}
private function getProductCustomField(SalesChannelProductEntity $product, string $key)
{
return array_key_exists($key, $product->getCustomFields())
? $product->getCustomFields()[$key]
: null;
}
private function getDefaultUnit($product, $lineItem): array
{
$params = [
$lineItem->getId() => [
NeonPlatformUnitarticles::PARAM_UNITVALUE => [
1 => $this->getProductCustomField($product, NeonPlatformUnitarticles::CUSTOMFIELD_DEFAULTVALUE)
]
]
];
if ($this->getProductCustomField($product, NeonPlatformUnitarticles::CUSTOMFIELD_HASDIMENSION2) != null) {
$params[$lineItem->getId()][NeonPlatformUnitarticles::PARAM_UNITVALUE][2] = $product->getCustomFields()[NeonPlatformUnitarticles::CUSTOMFIELD_DEFAULTVALUE2];
}
return $params;
}
/**
* @param array $lineItemParam
* @param LineItem $lineItem
* @param ProductEntity $product
* @return array
*/
private function updateLineItem(array $lineItemParam, LineItem $lineItem, ProductEntity $product): array
{
//set new ID as we don't stack the line items which have active stock updates
$newId = Uuid::randomHex();
$lineItemParam[$newId] = $lineItemParam[$lineItem->getId()];
$factor = $this->priceFactorService->calculateWithCustomFields($product->getCustomFields());
$newQuantity = intval(floatval($lineItemParam[$newId][NeonPlatformUnitarticles::PARAM_UNITVALUE]) * $factor);
$lineItem->setId($newId);
$lineItem->setQuantity($newQuantity);
return $lineItemParam;
}
private function getBasePrice(SalesChannelProductEntity $product)
{
$unitSign = $this->getUnitSign($product);
return [
'sign' => $unitSign,
'price' => $product->getCalculatedPrice()->getUnitPrice() * $this->priceFactorService->calculateWithCustomFields($product->getCustomFields())
];
}
private function getUnitSign(SalesChannelProductEntity $product)
{
$unitSign = '';
if ($product->getCalculatedPrice()->getReferencePrice() &&
$product->getCalculatedPrice()->getReferencePrice()->getReferenceUnit()) {
if ($product->getCalculatedPrice()->getReferencePrice()->getReferenceUnit() != 1) {
$unitSign = $product->getCalculatedPrice()->getReferencePrice()->getReferenceUnit();
}
$unitSign .= $product->getCalculatedPrice()->getReferencePrice()->getUnitName();
} elseif ($product->getReferenceUnit() == 1 && $product->getPurchaseUnit() == 1) {
$unitSign = $product->getUnit()->getName();
} else {
if ($this->getProductCustomField($product, NeonPlatformUnitarticles::CUSTOMFIELD_BASEPRICEVALUE) != 1) {
$unitSign = $this->getProductCustomField($product, NeonPlatformUnitarticles::CUSTOMFIELD_BASEPRICEVALUE);
}
if ($this->getProductCustomField($product, NeonPlatformUnitarticles::CUSTOMFIELD_BASEPRICEVALUELABEL)) {
$unitSign .= $this->getProductCustomField($product, NeonPlatformUnitarticles::CUSTOMFIELD_BASEPRICEVALUELABEL);
} else {
$unitSign .= $this->getProductCustomField($product, NeonPlatformUnitarticles::CUSTOMFIELD_UNITLABEL);
}
}
return $unitSign;
}
}