vendor/shopware/core/Content/Product/SalesChannel/Detail/ProductConfiguratorLoader.php line 50

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Product\SalesChannel\Detail;
  3. use Shopware\Core\Content\Product\Aggregate\ProductConfiguratorSetting\ProductConfiguratorSettingEntity;
  4. use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
  5. use Shopware\Core\Content\Property\Aggregate\PropertyGroupOption\PropertyGroupOptionCollection;
  6. use Shopware\Core\Content\Property\Aggregate\PropertyGroupOption\PropertyGroupOptionEntity;
  7. use Shopware\Core\Content\Property\PropertyGroupCollection;
  8. use Shopware\Core\Content\Property\PropertyGroupDefinition;
  9. use Shopware\Core\Content\Property\PropertyGroupEntity;
  10. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;
  12. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  14. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  15. class ProductConfiguratorLoader
  16. {
  17.     private EntityRepositoryInterface $configuratorRepository;
  18.     private AvailableCombinationLoader $combinationLoader;
  19.     /**
  20.      * @internal
  21.      */
  22.     public function __construct(
  23.         EntityRepositoryInterface $configuratorRepository,
  24.         AvailableCombinationLoader $combinationLoader
  25.     ) {
  26.         $this->combinationLoader $combinationLoader;
  27.         $this->configuratorRepository $configuratorRepository;
  28.     }
  29.     /**
  30.      * @throws InconsistentCriteriaIdsException
  31.      */
  32.     public function load(
  33.         SalesChannelProductEntity $product,
  34.         SalesChannelContext $context
  35.     ): PropertyGroupCollection {
  36.         if (!$product->getParentId()) {
  37.             return new PropertyGroupCollection();
  38.         }
  39.         $groups $this->loadSettings($product$context);
  40.         $groups $this->sortSettings($groups$product);
  41.         $combinations $this->combinationLoader->load(
  42.             $product->getParentId(),
  43.             $context->getContext(),
  44.             $context->getSalesChannelId()
  45.         );
  46.         $current $this->buildCurrentOptions($product$groups);
  47.         foreach ($groups as $group) {
  48.             $options $group->getOptions();
  49.             if ($options === null) {
  50.                 continue;
  51.             }
  52.             foreach ($options as $option) {
  53.                 $combinable $this->isCombinable($option$current$combinations);
  54.                 if ($combinable === null) {
  55.                     $options->remove($option->getId());
  56.                     continue;
  57.                 }
  58.                 $option->setGroup(null);
  59.                 $option->setCombinable($combinable);
  60.             }
  61.         }
  62.         return $groups;
  63.     }
  64.     /**
  65.      * @throws InconsistentCriteriaIdsException
  66.      *
  67.      * @return array<string, PropertyGroupEntity>|null
  68.      */
  69.     private function loadSettings(SalesChannelProductEntity $productSalesChannelContext $context): ?array
  70.     {
  71.         $criteria = (new Criteria())->addFilter(
  72.             new EqualsFilter('productId'$product->getParentId() ?? $product->getId())
  73.         );
  74.         $criteria->addAssociation('option.group')
  75.             ->addAssociation('option.media')
  76.             ->addAssociation('media');
  77.         $settings $this->configuratorRepository
  78.             ->search($criteria$context->getContext())
  79.             ->getEntities();
  80.         if ($settings->count() <= 0) {
  81.             return null;
  82.         }
  83.         $groups = [];
  84.         /** @var ProductConfiguratorSettingEntity $setting */
  85.         foreach ($settings as $setting) {
  86.             $option $setting->getOption();
  87.             if ($option === null) {
  88.                 continue;
  89.             }
  90.             $group $option->getGroup();
  91.             if ($group === null) {
  92.                 continue;
  93.             }
  94.             $groupId $group->getId();
  95.             if (isset($groups[$groupId])) {
  96.                 $group $groups[$groupId];
  97.             }
  98.             $groups[$groupId] = $group;
  99.             $options $group->getOptions();
  100.             if ($options === null) {
  101.                 $options = new PropertyGroupOptionCollection();
  102.                 $group->setOptions($options);
  103.             }
  104.             $options->add($option);
  105.             $options->add($option);
  106.             $option->setConfiguratorSetting($setting);
  107.         }
  108.         return $groups;
  109.     }
  110.     /**
  111.      * @param array<string, PropertyGroupEntity>|null $groups
  112.      */
  113.     private function sortSettings(?array $groupsSalesChannelProductEntity $product): PropertyGroupCollection
  114.     {
  115.         if (!$groups) {
  116.             return new PropertyGroupCollection();
  117.         }
  118.         $sorted = [];
  119.         foreach ($groups as $group) {
  120.             if (!$group) {
  121.                 continue;
  122.             }
  123.             if (!$group->getOptions()) {
  124.                 $group->setOptions(new PropertyGroupOptionCollection());
  125.             }
  126.             $sorted[$group->getId()] = $group;
  127.         }
  128.         /** @var PropertyGroupEntity $group */
  129.         foreach ($sorted as $group) {
  130.             $options $group->getOptions();
  131.             if ($options === null) {
  132.                 continue;
  133.             }
  134.             $options->sort(
  135.                 static function (PropertyGroupOptionEntity $aPropertyGroupOptionEntity $b) use ($group) {
  136.                     $configuratorSettingA $a->getConfiguratorSetting();
  137.                     $configuratorSettingB $b->getConfiguratorSetting();
  138.                     if ($configuratorSettingA !== null && $configuratorSettingB !== null
  139.                         && $configuratorSettingA->getPosition() !== $configuratorSettingB->getPosition()) {
  140.                         return $configuratorSettingA->getPosition() <=> $configuratorSettingB->getPosition();
  141.                     }
  142.                     if ($group->getSortingType() === PropertyGroupDefinition::SORTING_TYPE_ALPHANUMERIC) {
  143.                         return strnatcmp($a->getTranslation('name'), $b->getTranslation('name'));
  144.                     }
  145.                     return ($a->getTranslation('position') ?? $a->getPosition() ?? 0) <=> ($b->getTranslation('position') ?? $b->getPosition() ?? 0);
  146.                 }
  147.             );
  148.         }
  149.         $collection = new PropertyGroupCollection($sorted);
  150.         // check if product has an individual sorting configuration for property groups\
  151.         $config $product->getVariantListingConfig();
  152.         if ($config) {
  153.             $config $config->getConfiguratorGroupConfig();
  154.         }
  155.         if (!$config) {
  156.             $collection->sortByPositions();
  157.             return $collection;
  158.         }
  159.         $sortedGroupIds array_column($config'id');
  160.         // ensure all ids are in the array (but only once)
  161.         $sortedGroupIds array_unique(array_merge($sortedGroupIds$collection->getIds()));
  162.         $collection->sortByIdArray($sortedGroupIds);
  163.         return $collection;
  164.     }
  165.     /**
  166.      * @param array<string> $current
  167.      */
  168.     private function isCombinable(
  169.         PropertyGroupOptionEntity $option,
  170.         array $current,
  171.         AvailableCombinationResult $combinations
  172.     ): ?bool {
  173.         unset($current[$option->getGroupId()]);
  174.         $current[] = $option->getId();
  175.         // available with all other current selected options
  176.         if ($combinations->hasCombination($current) && $combinations->isAvailable($current)) {
  177.             return true;
  178.         }
  179.         // available but not with the other current selected options
  180.         if ($combinations->hasOptionId($option->getId())) {
  181.             return false;
  182.         }
  183.         return null;
  184.     }
  185.     /**
  186.      * @return array<int|string, string>
  187.      */
  188.     private function buildCurrentOptions(SalesChannelProductEntity $productPropertyGroupCollection $groups): array
  189.     {
  190.         $keyMap $groups->getOptionIdMap();
  191.         $optionIds $product->getOptionIds() ?? [];
  192.         $current = [];
  193.         if ($product->getOptionIds() === null) {
  194.             return $current;
  195.         }
  196.         foreach ($optionIds as $optionId) {
  197.             $groupId $keyMap[$optionId] ?? null;
  198.             if ($groupId === null) {
  199.                 continue;
  200.             }
  201.             $current[$groupId] = $optionId;
  202.         }
  203.         return $current;
  204.     }
  205. }