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

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.     /**
  18.      * @var EntityRepositoryInterface
  19.      */
  20.     private $configuratorRepository;
  21.     /**
  22.      * @var AvailableCombinationLoader
  23.      */
  24.     private $combinationLoader;
  25.     public function __construct(
  26.         EntityRepositoryInterface $configuratorRepository,
  27.         AvailableCombinationLoader $combinationLoader
  28.     ) {
  29.         $this->combinationLoader $combinationLoader;
  30.         $this->configuratorRepository $configuratorRepository;
  31.     }
  32.     /**
  33.      * @throws InconsistentCriteriaIdsException
  34.      */
  35.     public function load(
  36.         SalesChannelProductEntity $product,
  37.         SalesChannelContext $context
  38.     ): PropertyGroupCollection {
  39.         if (!$product->getParentId()) {
  40.             return new PropertyGroupCollection();
  41.         }
  42.         $groups $this->loadSettings($product$context);
  43.         $groups $this->sortSettings($groups$product);
  44.         $combinations $this->combinationLoader->load(
  45.             $product->getParentId(),
  46.             $context->getContext()
  47.         );
  48.         $current $this->buildCurrentOptions($product$groups);
  49.         foreach ($groups as $group) {
  50.             $options $group->getOptions();
  51.             if ($group->getOptions() === null) {
  52.                 continue;
  53.             }
  54.             foreach ($options as $option) {
  55.                 $combinable $this->isCombinable($option$current$combinations);
  56.                 if ($combinable === null) {
  57.                     $group->getOptions()->remove($option->getId());
  58.                     continue;
  59.                 }
  60.                 $option->setGroup(null);
  61.                 $option->setCombinable($combinable);
  62.             }
  63.         }
  64.         return $groups;
  65.     }
  66.     /**
  67.      * @throws InconsistentCriteriaIdsException
  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.             if (!$group->getOptions()) {
  100.                 $group->setOptions(new PropertyGroupOptionCollection());
  101.             }
  102.             $group->getOptions()->add($option);
  103.             $option->setConfiguratorSetting($setting);
  104.         }
  105.         return $groups;
  106.     }
  107.     private function sortSettings(?array $groupsSalesChannelProductEntity $product): PropertyGroupCollection
  108.     {
  109.         if (!$groups) {
  110.             return new PropertyGroupCollection();
  111.         }
  112.         $sorted = [];
  113.         foreach ($groups as $group) {
  114.             if (!$group) {
  115.                 continue;
  116.             }
  117.             if (!$group->getOptions()) {
  118.                 $group->setOptions(new PropertyGroupOptionCollection());
  119.             }
  120.             $sorted[$group->getId()] = $group;
  121.         }
  122.         /** @var PropertyGroupEntity $group */
  123.         foreach ($sorted as $group) {
  124.             $group->getOptions()->sort(
  125.                 static function (PropertyGroupOptionEntity $aPropertyGroupOptionEntity $b) use ($group) {
  126.                     if ($a->getConfiguratorSetting()->getPosition() !== $b->getConfiguratorSetting()->getPosition()) {
  127.                         return $a->getConfiguratorSetting()->getPosition() <=> $b->getConfiguratorSetting()->getPosition();
  128.                     }
  129.                     if ($group->getSortingType() === PropertyGroupDefinition::SORTING_TYPE_ALPHANUMERIC) {
  130.                         return strnatcmp($a->getTranslation('name'), $b->getTranslation('name'));
  131.                     }
  132.                     /* @deprecated tag:v6.4.0 - SORTING_TYPE_NUMERIC will be removed in 6.4.0 */
  133.                     if ($group->getSortingType() === PropertyGroupDefinition::SORTING_TYPE_NUMERIC) {
  134.                         return $a->getTranslation('name') <=> $b->getTranslation('name');
  135.                     }
  136.                     return ($a->getTranslation('position') ?? $a->getPosition() ?? 0) <=> ($b->getTranslation('position') ?? $b->getPosition() ?? 0);
  137.                 }
  138.             );
  139.         }
  140.         $collection = new PropertyGroupCollection($sorted);
  141.         // check if product has an individual sorting configuration for property groups
  142.         $config $product->getConfiguratorGroupConfig();
  143.         if (!$config) {
  144.             $collection->sortByPositions();
  145.             return $collection;
  146.         }
  147.         $sortedGroupIds array_column($config'id');
  148.         // ensure all ids are in the array (but only once)
  149.         $sortedGroupIds array_unique(array_merge($sortedGroupIds$collection->getIds()));
  150.         $collection->sortByIdArray($sortedGroupIds);
  151.         return $collection;
  152.     }
  153.     private function isCombinable(
  154.         PropertyGroupOptionEntity $option,
  155.         array $current,
  156.         AvailableCombinationResult $combinations
  157.     ): ?bool {
  158.         unset($current[$option->getGroupId()]);
  159.         $current[] = $option->getId();
  160.         // available with all other current selected options
  161.         if ($combinations->hasCombination($current)) {
  162.             return true;
  163.         }
  164.         // available but not with the other current selected options
  165.         if ($combinations->hasOptionId($option->getId())) {
  166.             return false;
  167.         }
  168.         return null;
  169.     }
  170.     private function buildCurrentOptions(SalesChannelProductEntity $productPropertyGroupCollection $groups): array
  171.     {
  172.         $keyMap $groups->getOptionIdMap();
  173.         $current = [];
  174.         foreach ($product->getOptionIds() as $optionId) {
  175.             $groupId $keyMap[$optionId] ?? null;
  176.             if ($groupId === null) {
  177.                 continue;
  178.             }
  179.             $current[$groupId] = $optionId;
  180.         }
  181.         return $current;
  182.     }
  183. }