vendor/store.shopware.com/zweipunktvariantenausgrauen/src/Subscriber/AddStockToDetailPage.php line 75

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace ZweiPunktVariantenAusgrauen\Subscriber;
  4. use Doctrine\DBAL\Connection;
  5. use Shopware\Core\Content\Product\ProductEntity;
  6. use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
  7. use Shopware\Core\System\SystemConfig\SystemConfigService;
  8. use Shopware\Storefront\Page\Product\ProductPageLoadedEvent;
  9. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  10. use ZweiPunktVariantenAusgrauen\ZweiPunktVariantenAusgrauen;
  11. /**
  12.  * Class AddStockToDetailPage
  13.  *
  14.  * Is used to have the stock numbers of the variants submitted to the frontend
  15.  * when loading the detail page.
  16.  *
  17.  * @package ZweiPunktVariantenAusgrauen\Subscriber
  18.  */
  19. class AddStockToDetailPage implements EventSubscriberInterface
  20. {
  21.     /**
  22.      * @var mixed
  23.      */
  24.     private $config;
  25.     private Connection $connection;
  26.     /**
  27.      * AddStockToDetailPage constructor.
  28.      */
  29.     public function __construct(
  30.         SystemConfigService $systemConfigService,
  31.         Connection $connection
  32.     ) {
  33.         // Get plugin configuration
  34.         $this->config $systemConfigService
  35.             ->get(ZweiPunktVariantenAusgrauen::PLUGIN_NAME '.config');
  36.         $this->connection $connection;
  37.     }
  38.     /**
  39.      * @return string[]
  40.      */
  41.     public static function getSubscribedEvents(): array
  42.     {
  43.         return [
  44.             ProductPageLoadedEvent::class => 'onProductPageLoaded'
  45.         ];
  46.     }
  47.     /**
  48.      * This subscriber determens unavailable variants (siblings) and passes
  49.      * unavailable option ids to the theme where the standard shopware logic is
  50.      * used gray out the unavailable options.
  51.      *
  52.      * How it is done:
  53.      * If we have only 1 option group:
  54.      * Check the stock of all other siblings and gray them out
  55.      *
  56.      * If we have 2 option groups or more (i.e. color, size, material):
  57.      * We want to gray out all siblings that are only one change (color, size
  58.      * or material) away from the current product. We have to iterate over
  59.      * every group and check if there are siblings with another option in the
  60.      * current group but every other group/option is matching the current
  61.      * product. Then check if this sibling has stock and if not, save the
  62.      * option id in an array that we pass to the theme.
  63.      *
  64.      * @param ProductPageLoadedEvent $event
  65.      */
  66.     public function onProductPageLoaded(ProductPageLoadedEvent $event): void
  67.     {
  68.         $product $event
  69.             ->getPage()
  70.             ->getProduct();
  71.         // Check for a parent product and skip if it's not a variant
  72.         $parentId $product->getParentId();
  73.         if (empty($parentId)) {
  74.             return;
  75.         }
  76.         // Ermittelt die Options IDs der Produkte die (je nach Konfiguration)
  77.         // keinen (verfügbaren) Bestand haben.
  78.         $optionIds $this->getOptionIds($parentId);
  79.         if (empty($optionIds)) {
  80.             return;
  81.         }
  82.         $unavailable $this->getGreyOutOptions(
  83.             $product,
  84.             $optionIds
  85.         );
  86.         // Handling selection fields
  87.         $hideSelection 'hide' == $this->config["behaviorSelectionAttributes"];
  88.         $event
  89.             ->getPage()
  90.             ->assign([
  91.                 'unavailable' => $unavailable,
  92.                 'hideselection' => $hideSelection
  93.             ])
  94.         ;
  95.     }
  96.     /**
  97.      * Liefert die Option IDs der Produkte die aktuell keinen Bestand haben.
  98.      *
  99.      * @param string $parent
  100.      * @return array<int, array<string, mixed>>
  101.      */
  102.     private function getOptionIds(string $parent): array
  103.     {
  104.         $stockType = ('availableStock' == $this->config['stockType']) ? 'available_stock' 'stock';
  105.         return $this->connection->fetchAll(
  106.             'SELECT product.is_closeout,
  107.                     product.option_ids
  108.                FROM product
  109.               WHERE product.' $stockType ' < 1
  110.                 AND product.parent_id = UNHEX(:parent)',
  111.             ['parent' => $parent]
  112.         );
  113.     }
  114.     /**
  115.      * @param SalesChannelProductEntity $product
  116.      * @param array<int, array<string, mixed>> $siblings
  117.      * @return array<int, string>
  118.      * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  119.      * @throws \JsonException
  120.      */
  121.     private function getGreyOutOptions(
  122.         SalesChannelProductEntity $product,
  123.         array $siblings
  124.     ): array {
  125.         // Keeps all the options that are unavailable
  126.         $unavailable = [];
  127.         // Options for the main product
  128.         $optionIds $product
  129.             ->getOptionIds()
  130.         ;
  131.         // Iterate over all options (not groups)
  132.         // start with the first option / group
  133.         foreach ($optionIds as $index => $optionId) {
  134.             // create a clone of the array that holds all option ids
  135.             $allOptions $optionIds;
  136.             // we remove the first option/group because we want to check the
  137.             // stock on siblings with a different option in this group,
  138.             // but every other option (in other groups) has to be the same
  139.             unset($allOptions[$index]);
  140.             // iterate over all siblings with another option for the group
  141.             /** @var ProductEntity $sibling */
  142.             foreach ($siblings as $sibling) {
  143.                 // Check if only clouseout products (Abverkaufsartikel) shoud be
  144.                 // marked and skip the product if necessary.
  145.                 if (
  146.                     false == $product->getIsCloseout()
  147.                     && (null == $sibling['is_closeout'] || == $sibling['is_closeout'])
  148.                     && true == $this->config["onlyCloseoutProducts"]
  149.                 ) {
  150.                     continue;
  151.                 }
  152.                 // do this on all siblings which have a different option
  153.                 // in the current group. This will give us a sibling with only
  154.                 // ONE option different to the main product.
  155.                 if (!in_array($optionIdjson_decode($sibling['option_ids'], true512JSON_THROW_ON_ERROR))) {
  156.                     // 1. get all sibling option ids
  157.                     // 2. remove the matching options
  158.                     // 3. the one difference will remain
  159.                     $siblingOptions json_decode($sibling['option_ids'], true512JSON_THROW_ON_ERROR);
  160.                     // If there is more then one option
  161.                     // check if all other options match or
  162.                     // procceed with the next sibling
  163.                     if (count($siblingOptions) > 1) {
  164.                         foreach ($allOptions as $otherOption) {
  165.                             if (false == in_array($otherOption$siblingOptions)) {
  166.                                 continue 2;
  167.                             }
  168.                             // remove the matched option from the array
  169.                             if (false !== ($i array_search($otherOption$siblingOptions))) {
  170.                                 unset($siblingOptions[$i]);
  171.                             }
  172.                         }
  173.                     }
  174.                     // the current option
  175.                     $unavailable[] = array_pop($siblingOptions);
  176.                 }
  177.             }
  178.         }
  179.         return $unavailable;
  180.     }
  181. }