custom/plugins/SasVariantSwitch/src/Storefront/Page/ProductListingConfigurationLoader.php line 49

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