app/Plugin/VariationSwatches42/Event.php line 374

Open in your IDE?
  1. <?php
  2. namespace Plugin\VariationSwatches42;
  3. use Eccube\Entity\Product;
  4. use Eccube\Event\TemplateEvent;
  5. use Knp\Component\Pager\Pagination\PaginationInterface;
  6. use Plugin\VariationSwatches42\Entity\ProductClassImageIndex;
  7. use Plugin\VariationSwatches42\Entity\VariationSwatchClassConfig;
  8. use Plugin\VariationSwatches42\Repository\ProductClassImageIndexRepository;
  9. use Plugin\VariationSwatches42\Service\VariationSwatchService;
  10. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  11. use Doctrine\Persistence\ManagerRegistry;
  12. class Event implements EventSubscriberInterface
  13. {
  14.     /**
  15.      * @var VariationSwatchService
  16.      */
  17.     private $variationSwatchService;
  18.     /**
  19.      * @var ManagerRegistry
  20.      */
  21.     private $registry;
  22.     /**
  23.      * Event constructor.
  24.      *
  25.      * @param  VariationSwatchService  $variationSwatchService
  26.      * @param  ManagerRegistry  $registry
  27.      */
  28.     public function __construct(
  29.         VariationSwatchService $variationSwatchService,
  30.         ManagerRegistry $registry
  31.     ) {
  32.         $this->variationSwatchService $variationSwatchService;
  33.         $this->registry               $registry;
  34.     }
  35.     /**
  36.      * @return array
  37.      */
  38.     public static function getSubscribedEvents()
  39.     {
  40.         return [
  41.             'default_frame.twig'  => 'includeAssets',
  42.             'Product/detail.twig' => 'replaceClassCategoryWidget',
  43.         ];
  44.     }
  45.     /**
  46.      * Include assets for variation swatches
  47.      *
  48.      * @param  TemplateEvent  $event
  49.      */
  50.     public function includeAssets(TemplateEvent $event)
  51.     {
  52.         if (!$this->variationSwatchService->isEnabled()) {
  53.             return;
  54.         }
  55.         $parameters   $event->getParameters();
  56.         $classConfigs $this->variationSwatchService->getClassConfigs();
  57.         // Build the main config object passed to JS
  58.         $config                     $this->buildJsConfig($parameters$classConfigs);
  59.         $parameters['configScript'] = sprintf(
  60.             '<script>window.variationSwatchesConfig = %s;</script>',
  61.             json_encode($configJSON_HEX_TAG JSON_HEX_APOS JSON_HEX_QUOT JSON_HEX_AMP)
  62.         );
  63.         // Build product-specific class name map for list pages
  64.         if (isset($parameters['pagination']) && $parameters['pagination'] instanceof PaginationInterface) {
  65.             $prodMap                      $this->buildProductClassMap($parameters['pagination'], $classConfigs);
  66.             $parameters['productsScript'] = sprintf(
  67.                 '<script>window.variationSwatchesProductCategories = %s;</script>',
  68.                 json_encode(
  69.                     $prodMap,
  70.                     JSON_UNESCAPED_UNICODE JSON_HEX_TAG JSON_HEX_APOS JSON_HEX_QUOT JSON_HEX_AMP
  71.                 )
  72.             );
  73.         }
  74.         $event->setParameters($parameters);
  75.         $event->addAsset('@VariationSwatches42/assets.twig');
  76.     }
  77.     /**
  78.      * Builds the main configuration object for javascript.
  79.      */
  80.     private function buildJsConfig(array $parameters, array $classConfigs)
  81.     {
  82.         $config   = ['enabled' => true];
  83.         $classMap = [];
  84.         foreach ($classConfigs as $cid => $cfg) {
  85.             $classMap[$cid] = $this->convertConfigEntityToArray($cfg);
  86.         }
  87.         // On product detail page, narrow the map to only used classes
  88.         if (isset($parameters['Product']) && $parameters['Product'] instanceof Product) {
  89.             $product  $parameters['Product'];
  90.             $usedIds  = [];
  91.             $idToName = [];
  92.             foreach ($product->getProductClasses() as $pc) {
  93.                 if ($pc->getClassCategory1()) {
  94.                     $id            $pc->getClassCategory1()->getClassName()->getId();
  95.                     $usedIds[$id]  = true;
  96.                     $idToName[$id] = $pc->getClassCategory1()->getClassName()->getName();
  97.                 }
  98.                 if ($pc->getClassCategory2()) {
  99.                     $id            $pc->getClassCategory2()->getClassName()->getId();
  100.                     $usedIds[$id]  = true;
  101.                     $idToName[$id] = $pc->getClassCategory2()->getClassName()->getName();
  102.                 }
  103.             }
  104.             if ($usedIds) {
  105.                 $classMap array_intersect_key($classMap$usedIds);
  106.                 $missing  array_diff_key($usedIds$classMap);
  107.                 foreach (array_keys($missing) as $mid) {
  108.                     $classMap[$mid] = $this->getDefaultConfigArray($mid$idToName[$mid] ?? ('Class '.$mid));
  109.                 }
  110.             }
  111.             // Add media index mapping for the product
  112.             $config['mediaIndexMap'] = $this->buildMediaIndexMap($product);
  113.             // Add image URL mapping for product list
  114.             $config['imageUrlMap'] = $this->buildImageUrlMap($product);
  115.         }
  116.         $config['classConfigs'] = $classMap;
  117.         return $config;
  118.     }
  119.     /**
  120.      * Helper to get legacy config format.
  121.      */
  122.     private function getCompatConfig(?int $cid, array $classConfigs)
  123.     {
  124.         if ($cid === null || !isset($classConfigs[$cid])) {
  125.             return [null'label', []];
  126.         }
  127.         $cfg $classConfigs[$cid];
  128.         return [$cfg->getClassName()->getName(), $cfg->getDisplayType(), $cfg->getValuesDecoded()];
  129.     }
  130.     /**
  131.      * Helper to get appearance array.
  132.      */
  133.     private function getAppearanceConfig($cfg)
  134.     {
  135.         return [
  136.             'shape'       => $cfg->getShape(),
  137.             'size'        => $cfg->getSize(),
  138.             'gap'         => $cfg->getGap(),
  139.             'orientation' => $cfg->getOrientation(),
  140.             'showTooltip' => $cfg->isShowTooltip(),
  141.             'showLabel'   => $cfg->isShowLabel(),
  142.         ];
  143.     }
  144.     /**
  145.      * Builds product => class name map for list pages.
  146.      */
  147.     private function buildProductClassMap($pagination, array $classConfigs)
  148.     {
  149.         $prodMap = [];
  150.         foreach ($pagination as $prod) {
  151.             if (!$prod instanceof Product) {
  152.                 continue;
  153.             }
  154.             $pid           = (string)$prod->getId();
  155.             $prodMap[$pid] = [];
  156.             // Map ClassName ID (e.g. size, color) to the full ClassConfig object
  157.             $classIdToConfig = [];
  158.             foreach ($prod->getProductClasses() as $pc) {
  159.                 // ClassCategory1
  160.                 if ($cc1 $pc->getClassCategory1()) {
  161.                     $cid = (string)$cc1->getClassName()->getId();
  162.                     if (!isset($classIdToConfig[$cid])) {
  163.                         $cn                    $cc1->getClassName()->getName();
  164.                         $classIdToConfig[$cid] = $this->findClassNameEntity($cn$classConfigs);
  165.                     }
  166.                 }
  167.                 // ClassCategory2
  168.                 if ($cc2 $pc->getClassCategory2()) {
  169.                     $cid = (string)$cc2->getClassName()->getId();
  170.                     if (!isset($classIdToConfig[$cid])) {
  171.                         $cn                    $cc2->getClassName()->getName();
  172.                         $classIdToConfig[$cid] = $this->findClassNameEntity($cn$classConfigs);
  173.                     }
  174.                 }
  175.             }
  176.             // Determine primary class for position 1 and 2
  177.             $pos1Cfg $pos2Cfg null;
  178.             foreach ($prod->getProductClasses() as $pc) {
  179.                 if (!$pos1Cfg && $pc->getClassCategory1()) {
  180.                     $cn      $pc->getClassCategory1()->getClassName()->getName();
  181.                     $pos1Cfg $this->findClassNameEntity($cn$classConfigs);
  182.                 }
  183.                 if (!$pos2Cfg && $pc->getClassCategory2()) {
  184.                     $cn      $pc->getClassCategory2()->getClassName()->getName();
  185.                     $pos2Cfg $this->findClassNameEntity($cn$classConfigs);
  186.                 }
  187.                 if ($pos1Cfg && $pos2Cfg) {
  188.                     break;
  189.                 }
  190.             }
  191.             if ($pos1Cfg) {
  192.                 $prodMap[$pid]['1'] = $this->getAppearanceConfig($pos1Cfg) + [
  193.                         'id'          => $pos1Cfg->getClassName()->getId(),
  194.                         'name'        => $pos1Cfg->getClassName()->getName(),
  195.                         'displayType' => $pos1Cfg->getDisplayType(),
  196.                         'values'      => $pos1Cfg->getValuesDecoded(),
  197.                     ];
  198.             } elseif (
  199.                 isset($prod->getProductClasses()[0]) &&
  200.                 $prod->getProductClasses()[0]->getClassCategory1()
  201.             ) {
  202.                 $cn                 $prod->getProductClasses()[0]->getClassCategory1()->getClassName();
  203.                 $prodMap[$pid]['1'] = $this->getDefaultConfigArray($cn->getId(), $cn->getName());
  204.             }
  205.             if ($pos2Cfg) {
  206.                 $prodMap[$pid]['2'] = $this->getAppearanceConfig($pos2Cfg) + [
  207.                         'id'          => $pos2Cfg->getClassName()->getId(),
  208.                         'name'        => $pos2Cfg->getClassName()->getName(),
  209.                         'displayType' => $pos2Cfg->getDisplayType(),
  210.                         'values'      => $pos2Cfg->getValuesDecoded(),
  211.                     ];
  212.             } elseif (
  213.                 isset($prod->getProductClasses()[0]) &&
  214.                 $prod->getProductClasses()[0]->getClassCategory2()
  215.             ) {
  216.                 $cn                 $prod->getProductClasses()[0]->getClassCategory2()->getClassName();
  217.                 $prodMap[$pid]['2'] = $this->getDefaultConfigArray($cn->getId(), $cn->getName());
  218.             }
  219.             // Add image URL mapping for this product
  220.             $prodMap[$pid]['imageUrlMap'] = $this->buildImageUrlMap($prod);
  221.         }
  222.         return $prodMap;
  223.     }
  224.     /**
  225.      * Helper to find a ClassConfig entity by name.
  226.      * @return VariationSwatchClassConfig|null
  227.      */
  228.     private function findClassNameEntity(string $name, array $classConfigs)
  229.     {
  230.         foreach ($classConfigs as $cfg) {
  231.             if ($cfg->getClassName()->getName() === $name) {
  232.                 return $cfg;
  233.             }
  234.         }
  235.         return null;
  236.     }
  237.     /**
  238.      * @param $cfg
  239.      * @return array
  240.      */
  241.     private function convertConfigEntityToArray($cfg)
  242.     {
  243.         return [
  244.             'id'          => $cfg->getClassName()->getId(),
  245.             'name'        => $cfg->getClassName()->getName(),
  246.             'displayType' => $cfg->getDisplayType(),
  247.             'shape'       => $cfg->getShape(),
  248.             'size'        => $cfg->getSize(),
  249.             'gap'         => $cfg->getGap(),
  250.             'orientation' => $cfg->getOrientation(),
  251.             'showTooltip' => $cfg->isShowTooltip(),
  252.             'showLabel'   => $cfg->isShowLabel(),
  253.             'values'      => $cfg->getValuesDecoded(),
  254.         ];
  255.     }
  256.     /**
  257.      * @param  int  $id
  258.      * @param  string  $name
  259.      * @return array
  260.      */
  261.     private function getDefaultConfigArray(int $idstring $name)
  262.     {
  263.         return [
  264.             'id'          => $id,
  265.             'name'        => $name,
  266.             'displayType' => 'dropdown',
  267.             'shape'       => 'squared',
  268.             'size'        => 'small',
  269.             'gap'         => 'small',
  270.             'orientation' => 'horizontal',
  271.             'showTooltip' => true,
  272.             'showLabel'   => false,
  273.             'values'      => [],
  274.         ];
  275.     }
  276.     /**
  277.      * Build media index mapping for a product
  278.      */
  279.     private function buildMediaIndexMap(Product $product)
  280.     {
  281.         $mediaIndexMap = [];
  282.         // Get the ProductClassImageIndex repository
  283.         $em $this->registry->getManager();
  284.         /** @var ProductClassImageIndexRepository $imageIndexRepo */
  285.         $imageIndexRepo $em->getRepository(ProductClassImageIndex::class);
  286.         // Get all image index configurations for this product
  287.         $imageIndexConfigs $imageIndexRepo->findByProduct($product);
  288.         foreach ($imageIndexConfigs as $config) {
  289.             $comboKey   $config->getComboKey();
  290.             $imageIndex $config->getImageIndex();
  291.             // Only add to map if image index is not null
  292.             if ($imageIndex !== null) {
  293.                 $mediaIndexMap[$comboKey] = $imageIndex;
  294.             }
  295.         }
  296.         return $mediaIndexMap;
  297.     }
  298.     /**
  299.      * Build image URL mapping for a product
  300.      */
  301.     private function buildImageUrlMap(Product $product)
  302.     {
  303.         $imageUrlMap = [];
  304.         // Get the ProductClassImageIndex repository
  305.         $em $this->registry->getManager();
  306.         /** @var ProductClassImageIndexRepository $imageIndexRepo */
  307.         $imageIndexRepo $em->getRepository(ProductClassImageIndex::class);
  308.         // Get all image index configurations for this product
  309.         $imageIndexConfigs $imageIndexRepo->findByProduct($product);
  310.         foreach ($imageIndexConfigs as $config) {
  311.             $comboKey $config->getComboKey();
  312.             $imageUrl $config->getImageUrl();
  313.             // Only add to map if image URL is not null
  314.             if ($imageUrl !== null) {
  315.                 $imageUrlMap[$comboKey] = $imageUrl;
  316.             }
  317.         }
  318.         return $imageUrlMap;
  319.     }
  320.     public function replaceClassCategoryWidget(TemplateEvent $event)
  321.     {
  322.         $source $event->getSource();
  323.         $replaced str_replace(
  324.             ['{{ form_widget(form.classcategory_id1) }}''{{ form_widget(form.classcategory_id2) }}'],
  325.             ['{{ form_row(form.classcategory_id1) }}''{{ form_row(form.classcategory_id2) }}'],
  326.             $source
  327.         );
  328.         $event->setSource($replaced);
  329.     }
  330. }