vendor/store.shopware.com/zweipunktvariantenausgrauen/src/Subscriber/ChooseAvailableVariant.php line 89

Open in your IDE?
  1. <?php
  2. namespace ZweiPunktVariantenAusgrauen\Subscriber;
  3. use Exception;
  4. use Shopware\Core\Content\Product\ProductEntity;
  5. use Shopware\Core\Framework\Context;
  6. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  7. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\RangeFilter;
  11. use Shopware\Core\System\SalesChannel\Entity\SalesChannelEntityLoadedEvent;
  12. use Shopware\Core\System\SystemConfig\SystemConfigService;
  13. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  14. use Symfony\Component\HttpFoundation\RequestStack;
  15. use Symfony\Component\HttpFoundation\Request;
  16. use ZweiPunktVariantenAusgrauen\ZweiPunktVariantenAusgrauen;
  17. /**
  18.  * Class ChooseAvailableVariant
  19.  *
  20.  * Used to set the link to an available variant.
  21.  *
  22.  * @package ZweiPunktVariantenAusgrauen\Subscriber
  23.  * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  24.  */
  25. class ChooseAvailableVariant implements EventSubscriberInterface
  26. {
  27.     /**
  28.      * @var EntityRepositoryInterface
  29.      */
  30.     private $productRepository;
  31.     /**
  32.      * @var RequestStack
  33.      */
  34.     protected $requestStack;
  35.     /**
  36.      * @var array<string, mixed>
  37.      */
  38.     private $config;
  39.     /**
  40.      * Holds the crontroller and action of the current request
  41.      *
  42.      * @var string
  43.      */
  44.     private string $controllerAction '';
  45.     private SalesChannelEntityLoadedEvent $event;
  46.     /**
  47.      * ChooseAvailableVariant constructor.
  48.      *
  49.      * @param EntityRepositoryInterface $productRepository
  50.      * @param RequestStack $requestStack
  51.      * @param SystemConfigService $systemConfigService
  52.      */
  53.     public function __construct(
  54.         EntityRepositoryInterface $productRepository,
  55.         RequestStack $requestStack,
  56.         SystemConfigService $systemConfigService
  57.     ) {
  58.         $this->productRepository $productRepository;
  59.         $this->requestStack $requestStack;
  60.         // Get plugin configuration
  61.         $this->config $systemConfigService
  62.             ->get(ZweiPunktVariantenAusgrauen::PLUGIN_NAME '.config');
  63.     }
  64.     /**
  65.      * @return string[]
  66.      */
  67.     public static function getSubscribedEvents(): array
  68.     {
  69.         return [
  70.             'sales_channel.product.loaded' => 'getUrlFromAvailableVariant'
  71.         ];
  72.     }
  73.     /**
  74.      * Determines the id for the url generation of an available variant for a variant product
  75.      *
  76.      * @param SalesChannelEntityLoadedEvent $event
  77.      * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  78.      */
  79.     public function getUrlFromAvailableVariant(
  80.         SalesChannelEntityLoadedEvent $event
  81.     ): void {
  82.         $this->event $event;
  83.         try {
  84.             $this->checkRequest();
  85.             $products $this->getProducts();
  86.         } catch (Exception $exception) {
  87.             return;
  88.         }
  89.         // Goes through all products to determine the parent ids
  90.         $productsForNewUrl = [];
  91.         foreach ($products as $product) {
  92.             try {
  93.                 $this->checkProduct($product);
  94.             } catch (Exception $exception) {
  95.                 continue;
  96.             }
  97.             // If a parent id is set on the product, then it must be entered into the array.
  98.             if (is_string($product->get('parentId'))) {
  99.                 $productsForNewUrl[] = $product->get('parentId');
  100.             }
  101.             // If no parent id is set on the product,
  102.             // then it must be checked if the product has a child count and
  103.             // accordingly its own id must be entered into the array.
  104.             // Both cases can never be true at the same time,
  105.             // because variants do not have a child count and main items do not have a parent id.
  106.             if ($product->get('childCount') > 0) {
  107.                 $productsForNewUrl[] = $product->get('id');
  108.             }
  109.         }
  110.         // For the set parent ids, the variants are determined
  111.         $siblings $this->getSiblings(
  112.             $productsForNewUrl,
  113.             $event->getContext()
  114.         )->getEntities();
  115.         // For each product, the variants are gone through to determine the ids.
  116.         foreach ($products as $product) {
  117.             foreach ($siblings as $sibling) {
  118.                 // Here again, a difference must be made between the main product
  119.                 // and the variant, since a main item can also be selected in a product box.
  120.                 // If the parent id of the product matches the parentid of the variant,
  121.                 // then it is a variant and the id of the variant is added to the product under newUrlId.
  122.                 if ($product->get('parentId') == $sibling->getParentId()) {
  123.                     $product->assign(['newUrlId' => $sibling->getId()]);
  124.                 }
  125.                 // If the id of the product matches the parentid of the variant,
  126.                 // then it is a main item and the id of the variant is added to the product under newUrlId.
  127.                 if ($product->get('id') == $sibling->getParentId()) {
  128.                     $product->assign(['instockProductId' => $sibling->getId()]);
  129.                 }
  130.             }
  131.         }
  132.     }
  133.     /**
  134.      * @param string[] $parentIds
  135.      * @param Context $context
  136.      * @return EntitySearchResult
  137.      */
  138.     private function getSiblings(
  139.         array $parentIds,
  140.         Context $context
  141.     ): EntitySearchResult {
  142.         $criteria = new Criteria();
  143.         $criteria->addFilter(new EqualsAnyFilter('parentId'$parentIds));
  144.         $criteria->addFilter(
  145.             new RangeFilter('availableStock', [
  146.                 RangeFilter::GT => 0
  147.             ])
  148.         );
  149.         return $this
  150.             ->productRepository
  151.             ->search($criteria$context);
  152.     }
  153.     private function checkRequest(): void
  154.     {
  155.         // Determines the current controller and its action
  156.         $request $this->requestStack->getCurrentRequest();
  157.         // no http protocol / browser request via console calls
  158.         if (!$request instanceof Request) {
  159.             throw new Exception('No request available');
  160.         }
  161.         $controllerPath explode('\\'$request->attributes->get('_controller'));
  162.         $this->controllerAction end($controllerPath);
  163.         // Sets the array for the controllers for which a change is to be made
  164.         // Allowed controllers and the action are:
  165.         // Home page, Listing, CMS Pages with product sliders or boxes, Detail page for cross selling.
  166.         $allowedController = [
  167.             'NavigationController::home',
  168.             'ProductController::index',
  169.             'NavigationController::index',
  170.             'CmsController::category'
  171.         ];
  172.         // If the current controller is not in the array, the function can be exited
  173.         if (false == in_array($this->controllerAction$allowedController)) {
  174.             throw new Exception('Not a valid request');
  175.         }
  176.     }
  177.     /**
  178.      * @return ProductEntity[]
  179.      * @throws Exception
  180.      */
  181.     private function getProducts(): array
  182.     {
  183.         // The products are determined
  184.         $products $this->event->getEntities();
  185.         // If no product is present, the function can be exited
  186.         if (empty($products)) {
  187.             throw new Exception('No products');
  188.         }
  189.         // If it is the controller and the action that is responsible for the detail page,
  190.         // then the pass can be skipped with only one product,
  191.         // since this is the called item.
  192.         // Another pass would then be the cross selling,
  193.         // which contains more articles.
  194.         if (
  195.             == count($products) &&
  196.             'ProductController::index' == $this->controllerAction
  197.         ) {
  198.             throw new Exception('Detail page');
  199.         }
  200.         return $products;
  201.     }
  202.     private function checkProduct(ProductEntity $product): void
  203.     {
  204.         // If the product is not a closeout product,
  205.         // but in the configuration only closeout products are marked,
  206.         // then it can continue with the next product.
  207.         if (
  208.             !$product->getIsCloseout() &&
  209.             $this->config["onlyCloseoutProducts"]
  210.         ) {
  211.             throw new Exception('Only closeout products');
  212.         }
  213.         // If the product has an available stock greater than zero
  214.         // and a child count of 0, then it is a variant that already has a stock
  215.         // and therefore does not need to be considered further.
  216.         // If a main item has been selected for a product box and has an available inventory greater than zero,
  217.         // then its variants with an inventory must still be checked
  218.         // so that a corresponding variant can be linked to.
  219.         // Therefore, only the variants are tested for available stock.
  220.         if ($product->get('availableStock') > && == $product->get('childCount')) {
  221.             throw new Exception('has stock');
  222.         }
  223.     }
  224. }