custom/plugins/DmSanettaTheme/src/Service/VariantLoader.php line 216

Open in your IDE?
  1. <?php
  2. namespace SanettaTheme\Service;
  3. use Doctrine\DBAL\Connection;
  4. use Monolog\Logger;
  5. use SanettaTheme\Struct\ProductHover;
  6. use Shopware\Core\Content\Product\ProductCollection;
  7. use Shopware\Core\Content\Product\SalesChannel\Detail\AvailableCombinationResult;
  8. use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
  9. use Shopware\Core\Content\Property\Aggregate\PropertyGroupOption\PropertyGroupOptionCollection;
  10. use Shopware\Core\Content\Property\Aggregate\PropertyGroupOption\PropertyGroupOptionEntity;
  11. use Shopware\Core\Content\Property\PropertyGroupCollection;
  12. use Shopware\Core\Content\Property\PropertyGroupDefinition;
  13. use Shopware\Core\Framework\Context;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Doctrine\FetchModeHelper;
  15. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  16. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  17. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
  18. use Shopware\Core\Framework\Uuid\Uuid;
  19. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  20. use function array_key_exists;
  21. class VariantLoader
  22. {
  23.     protected EntityRepositoryInterface $configuratorRepository;
  24.     protected Connection $connection;
  25.     protected Logger $logger;
  26.     public function __construct(
  27.         EntityRepositoryInterface $configuratorRepository,
  28.         Connection $connection,
  29.         Logger $logger
  30.     ) {
  31.         $this->configuratorRepository $configuratorRepository;
  32.         $this->connection $connection;
  33.         $this->logger $logger;
  34.     }
  35.     public function load(ProductCollection $productsSalesChannelContext $context): void
  36.     {
  37.         $productSettings $this->loadSettings($products$context);
  38.         if (empty($productSettings)) {
  39.             return;
  40.         }
  41.         $productIds array_filter($products->map(function (SalesChannelProductEntity $product) {
  42.             return $product->getParentId() ?? $product->getId();
  43.         }));
  44.         $allCombinations $this->loadCombinations($productIds$context->getContext());
  45.         /** @var \Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity $product */
  46.         foreach ($products as $product) {
  47.             if ($product->getConfiguratorSettings() !== null || !$product->getParentId() || empty($productSettings[$product->getParentId()])) {
  48.                 $product->addExtension('groups', new PropertyGroupCollection());
  49.                 continue;
  50.             }
  51.             $groups $this->sortSettings($productSettings[$product->getParentId()], $product);
  52.             $combinations $allCombinations[$product->getParentId()];
  53.             $current $this->buildCurrentOptions($product$groups);
  54.             foreach ($groups as $group) {
  55.                 $options $group->getOptions();
  56.                 if ($options === null) {
  57.                     continue;
  58.                 }
  59.                 foreach ($options as $option) {
  60.                     $combinable $this->isCombinable($option$current$combinations);
  61.                     if ($combinable === null) {
  62.                         $options->remove($option->getId());
  63.                         continue;
  64.                     }
  65.                     $option->setGroup(null);
  66.                     $option->setCombinable($combinable);
  67.                 }
  68.                 $group->setOptions($options);
  69.             }
  70.             $product->addExtension('groups'$groups);
  71.         }
  72.     }
  73.     protected function loadSettings(
  74.         ProductCollection $products,
  75.         SalesChannelContext $context
  76.     ): ?array {
  77.         $allSettings = [];
  78.         $criteria = (new Criteria())->addFilter(
  79.             new EqualsAnyFilter(
  80.                 'productId',
  81.                 $products->map(function (SalesChannelProductEntity $product) {
  82.                     return $product->getParentId() ?? $product->getId();
  83.                 })
  84.             )
  85.         );
  86.         $criteria->addAssociation('option.group')
  87.             ->addAssociation('option.media')
  88.             ->addAssociation('media');
  89.         $settings $this->configuratorRepository
  90.             ->search($criteria$context->getContext())
  91.             ->getEntities();
  92.         if ($settings->count() <= 0) {
  93.             return null;
  94.         }
  95.         /** @var \Shopware\Core\Content\Product\Aggregate\ProductConfiguratorSetting\ProductConfiguratorSettingEntity $setting */
  96.         foreach ($settings as $setting) {
  97.             $productId $setting->getProductId();
  98.             if (array_key_exists($productId$allSettings)) {
  99.                 $allSettings[$productId][] = clone $setting;
  100.             } else {
  101.                 $allSettings[$productId] = [clone $setting];
  102.             }
  103.         }
  104.         /** @var \Shopware\Core\Content\Product\Aggregate\ProductConfiguratorSetting\ProductConfiguratorSettingEntity[] $settings */
  105.         foreach ($allSettings as $productId => $settings) {
  106.             $groups = [];
  107.             foreach ($settings as $setting) {
  108.                 $option $setting->getOption();
  109.                 if ($option === null) {
  110.                     continue;
  111.                 }
  112.                 $group $option->getGroup();
  113.                 if ($group === null) {
  114.                     continue;
  115.                 }
  116.                 $groupId $group->getId();
  117.                 if (isset($groups[$groupId])) {
  118.                     $group $groups[$groupId];
  119.                 }
  120.                 $groups[$groupId] = $group;
  121.                 if ($group->getOptions() === null) {
  122.                     $group->setOptions(new PropertyGroupOptionCollection());
  123.                 }
  124.                 $group->getOptions()->add($option);
  125.                 $option->setConfiguratorSetting($setting);
  126.             }
  127.             $allSettings[$productId] = $groups;
  128.         }
  129.         return $allSettings;
  130.     }
  131.     protected function loadCombinations(
  132.         array $productIds,
  133.         Context $context
  134.     ): array {
  135.         $allCombinations = [];
  136.         $query $this->connection->createQueryBuilder();
  137.         $query->from('product')
  138.             ->select([
  139.                 'LOWER(HEX(product.id))',
  140.                 'LOWER(HEX(product.parent_id)) as parent_id',
  141.                 'product.option_ids as options',
  142.                 'product.product_number as productNumber',
  143.                 'product.available',
  144.             ])
  145.             ->leftJoin('product''product''parent''product.parent_id = parent.id')
  146.             ->andWhere('product.parent_id IN (:id)')
  147.             ->andWhere('product.version_id = :versionId')
  148.             ->andWhere('IFNULL(product.active, parent.active) = :active')
  149.             ->andWhere('product.option_ids IS NOT NULL')
  150.             ->setParameter('id'Uuid::fromHexToBytesList($productIds), Connection::PARAM_STR_ARRAY)
  151.             ->setParameter('versionId'Uuid::fromHexToBytes($context->getVersionId()))
  152.             ->setParameter('active'true);
  153.         $combinations $query->execute()->fetchAll();
  154.         $combinations FetchModeHelper::groupUnique($combinations);
  155.         foreach ($combinations as $combination) {
  156.             $parentId $combination['parent_id'];
  157.             if (array_key_exists($parentId$allCombinations)) {
  158.                 $allCombinations[$parentId][] = $combination;
  159.             } else {
  160.                 $allCombinations[$parentId] = [$combination];
  161.             }
  162.         }
  163.         foreach ($allCombinations as $parentId => $groupedCombinations) {
  164.             $available = [];
  165.             foreach ($groupedCombinations as $combination) {
  166.                 $combination['options'] = json_decode($combination['options'], true);
  167.                 $available[] = $combination;
  168.             }
  169.             $result = new AvailableCombinationResult();
  170.             foreach ($available as $combination) {
  171.                 $result->addCombination($combination['options']);
  172.             }
  173.             $allCombinations[$parentId] = $result;
  174.         }
  175.         return $allCombinations;
  176.     }
  177.     protected function sortSettings(
  178.         ?array $groups,
  179.         SalesChannelProductEntity $product
  180.     ): PropertyGroupCollection {
  181.         if (!$groups) {
  182.             return new PropertyGroupCollection();
  183.         }
  184.         $sorted = [];
  185.         foreach ($groups as $group) {
  186.             if (!$group) {
  187.                 continue;
  188.             }
  189.             if (!$group->getOptions()) {
  190.                 $group->setOptions(new PropertyGroupOptionCollection());
  191.             }
  192.             $sorted[$group->getId()] = $group;
  193.         }
  194.         /** @var \Shopware\Core\Content\Property\PropertyGroupEntity $group */
  195.         foreach ($sorted as $group) {
  196.             $group->getOptions()->sort(
  197.                 static function (PropertyGroupOptionEntity $aPropertyGroupOptionEntity $b) use ($group) {
  198.                     if ($a->getConfiguratorSetting()->getPosition() !== $b->getConfiguratorSetting()->getPosition()) {
  199.                         return $a->getConfiguratorSetting()->getPosition() <=> $b->getConfiguratorSetting()->getPosition();
  200.                     }
  201.                     if ($group->getSortingType() === PropertyGroupDefinition::SORTING_TYPE_ALPHANUMERIC) {
  202.                         return strnatcmp($a->getTranslation('name'), $b->getTranslation('name'));
  203.                     }
  204.                     return ($a->getTranslation('position') ?? $a->getPosition() ?? 0) <=> ($b->getTranslation('position') ?? $b->getPosition() ?? 0);
  205.                 }
  206.             );
  207.         }
  208.         $collection = new PropertyGroupCollection($sorted);
  209.         $config $product->getConfiguratorGroupConfig();
  210.         if (!$config) {
  211.             $collection->sortByPositions();
  212.             return $collection;
  213.         }
  214.         $sortedGroupIds array_column($config'id');
  215.         $sortedGroupIds array_unique(array_merge($sortedGroupIds$collection->getIds()));
  216.         $collection->sortByIdArray($sortedGroupIds);
  217.         return $collection;
  218.     }
  219.     protected function buildCurrentOptions(
  220.         SalesChannelProductEntity $product,
  221.         PropertyGroupCollection $groups
  222.     ): array {
  223.         $keyMap $groups->getOptionIdMap();
  224.         $current = [];
  225.         foreach ($product->getOptionIds() as $optionId) {
  226.             $groupId $keyMap[$optionId] ?? null;
  227.             if ($groupId === null) {
  228.                 continue;
  229.             }
  230.             $current[$groupId] = $optionId;
  231.         }
  232.         return $current;
  233.     }
  234.     protected function isCombinable(
  235.         PropertyGroupOptionEntity $option,
  236.         array $current,
  237.         AvailableCombinationResult $combinations
  238.     ): ?bool {
  239.         unset($current[$option->getGroupId()]);
  240.         $current[] = $option->getId();
  241.         if ($combinations->hasCombination($current)) {
  242.             return true;
  243.         }
  244.         if ($combinations->hasOptionId($option->getId())) {
  245.             return false;
  246.         }
  247.         return null;
  248.     }
  249. }