<?php
namespace Maxia\MaxiaListingVariants6\Service;
use Monolog\Logger;
use Maxia\MaxiaListingVariants6\Config\ProductConfig;
use Maxia\MaxiaListingVariants6\Config\PropertyGroupConfig;
use Shopware\Core\Content\Product\Exception\ProductNotFoundException;
use Shopware\Core\Content\Product\ProductEntity;
use Shopware\Core\Content\Property\Aggregate\PropertyGroupOption\PropertyGroupOptionEntity;
use Shopware\Core\Content\Property\PropertyGroupCollection;
use Shopware\Core\Content\Property\PropertyGroupEntity;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
/**
* Loads product assignments for each option.
*/
class VariantMappingLoader implements VariantMappingLoaderInterface
{
protected Logger $logger;
protected ConfigService $configService;
protected ProductCombinationFinderInterface $combinationFinder;
public function __construct(
Logger $logger,
ConfigService $configService,
ProductCombinationFinderInterface $combinationFinder)
{
$this->logger = $logger;
$this->configService = $configService;
$this->combinationFinder = $combinationFinder;
}
/**
* Search product IDs for each option
*/
public function loadAllMappings(ProductEntity $product, SalesChannelContext $context): array
{
/** @var ProductConfig $config */
$config = $product->getExtension('maxiaListingVariants');
$hideUnavailable = $this->configService->getBaseConfig($context)->isHideSoldOutCloseoutProducts()
&& $config->getTotalGroupCount() === 1;
$settings = $config->getOptions();
$mappings = $config->getOptionProductMappings() ?: [];
foreach ($settings->getElements() as $group) {
/** @var PropertyGroupConfig $groupConfig */
$groupConfig = $group->getExtension('maxiaListingVariants');
$optionIndex = 0;
foreach ($group->getOptions() as $key => $option) {
if (isset($mappings[$option->getId()])) {
continue;
}
$mappings[$option->getId()] = $this->loadMapping($product, $group, $option, $context);
if ($hideUnavailable && !$option->getCombinable()
&& !in_array($option->getId(), $product->getOptionIds()))
{
$group->getOptions()->remove($key);
continue;
}
$optionIndex++;
if ($groupConfig->getMaxEntries()
&& !$config->isExpanded()
&& $optionIndex > $groupConfig->getMaxEntries()
) {
break;
}
}
}
return $mappings;
}
/**
* Loads variant mapping for the given option.
*/
public function loadMapping(
ProductEntity $product,
PropertyGroupEntity $group,
PropertyGroupOptionEntity $option,
SalesChannelContext $context
): array {
/** @var ProductConfig $productConfig */
$productConfig = $product->getExtension('maxiaListingVariants');
/** @var PropertyGroupOptionEntity $option */
$combinationOptionIds = $this->getCombinationOptionIds($product, $productConfig->getOptions(), $option);
$parentId = $product->getParentId() ?: $product->getId();
// use less restrictive search, only if quick buy is off and expand by property values is inactive
$preferExactOptions = $productConfig->isQuickBuyActive();
if ($product->getConfiguratorGroupConfig()) {
foreach ($product->getConfiguratorGroupConfig() as $item) {
if ($item['expressionForListings']) {
$preferExactOptions = true;
}
}
}
try {
// try to find available variants first
$foundCombination = $this->combinationFinder->find(
$parentId,
$group->getId(),
$combinationOptionIds,
false,
$context
);
$option->setCombinable(true);
$mapping = [
'productId' => $foundCombination->getVariantId(),
'isCombinable' => $option->getCombinable()
];
} catch (ProductNotFoundException $e) {
try {
if ($preferExactOptions) {
$foundCombination = $this->combinationFinder->find(
$parentId,
$group->getId(),
$combinationOptionIds,
true,
$context
);
$option->setCombinable(false);
} else {
try {
$foundCombination = $this->combinationFinder->find(
$parentId,
$group->getId(),
[$option->getId()],
false,
$context
);
$option->setCombinable(true);
} catch (ProductNotFoundException $e) {
$foundCombination = $this->combinationFinder->find(
$parentId,
$group->getId(),
[$option->getId()],
true,
$context
);
$option->setCombinable(false);
}
}
$mapping = [
'productId' => $foundCombination->getVariantId(),
'isCombinable' => $option->getCombinable()
];
} catch (ProductNotFoundException $e) {
$this->logger->debug('No products found for combination', [
'productId' => $product->getId(),
'combinationOptionIds' => $combinationOptionIds
]);
$mapping = [
'productId' => $product->getId(),
'isCombinable' => $product->getAvailable()
];
}
}
return $mapping;
}
/**
* Returns the option IDs that are used for resolving the product for each option.
*/
public function getCombinationOptionIds(
ProductEntity $productEntity,
PropertyGroupCollection $settings,
PropertyGroupOptionEntity $option): array
{
$optionIds = [];
if (!$productEntity->getOptionIds()) {
return [$option->getId()];
}
foreach ($productEntity->getOptionIds() as $optionId) {
$group = $settings->filter(function (PropertyGroupEntity $group) use ($optionId) {
$optionIds = $group->getOptions()->map(function(PropertyGroupOptionEntity $option) {
return $option->getId();
});
return in_array($optionId, $optionIds);
})->first();
if ($group && $group->getId() === $option->getGroupId()) {
continue;
} else {
$optionIds[] = $optionId;
}
}
$optionIds[] = $option->getId();
return $optionIds;
}
}