app/Plugin/PiaHistory/EventSubscriber/ProductEditEventSubscriber.php line 185

Open in your IDE?
  1. <?php
  2. namespace Plugin\PiaHistory\EventSubscriber;
  3. use Eccube\Entity\Product;
  4. use Eccube\Repository\ProductRepository;
  5. use Eccube\Repository\MemberRepository;
  6. use Doctrine\ORM\EntityManagerInterface;
  7. use Plugin\PiaHistory\Entity\PiaHistory;
  8. use Plugin\PiaHistory\Entity\PiaHisProduct;
  9. use Plugin\PiaHistory\Entity\PiaHisProductClass;
  10. use Plugin\PiaHistory\Entity\PiaHisProductCategory;
  11. use Plugin\PiaHistory\Entity\PiaHisProductImage;
  12. use Plugin\PiaHistory\Entity\PiaHisProductStock;
  13. use Plugin\PiaHistory\Entity\PiaHisProductTag;
  14. use Plugin\PiaHistory\Entity\PiaHisProductAdd;
  15. use Plugin\PiaHistory\Entity\PiaHisRankPrice;
  16. use Plugin\PiaHistory\Entity\PiaHisCustomerClass;
  17. use Plugin\PiaHistory\Entity\PiaHisPoint;
  18. use Plugin\PiaHistory\Entity\PiaHisMeyasu;
  19. use Plugin\PiaHistory\Repository\PiaHistoryRepository;
  20. use Plugin\PiaHistory\Repository\PiaHisProductRepository;
  21. use Plugin\PiaHistory\Repository\PiaHisProductClassRepository;
  22. use Plugin\PiaHistory\Repository\PiaHisProductCategoryRepository;
  23. use Plugin\PiaHistory\Repository\PiaHisProductImageRepository;
  24. use Plugin\PiaHistory\Repository\PiaHisProductStockRepository;
  25. use Plugin\PiaHistory\Repository\PiaHisProductTagRepository;
  26. use Plugin\PiaHistory\Repository\PiaHisProductAddRepository;
  27. use Plugin\PiaHistory\Repository\PiaHisRankPriceRepository;
  28. use Plugin\PiaHistory\Repository\PiaHisCustomerClassRepository;
  29. use Plugin\PiaHistory\Repository\PiaHisPointRepository;
  30. use Plugin\PiaHistory\Repository\PiaHisMeyasuRepository;
  31. use Eccube\Event\EccubeEvents;
  32. use Eccube\Event\EventArgs;
  33. use Symfony\Component\HttpFoundation\RequestStack;
  34. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  35. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  36. use Symfony\Component\HttpKernel\Event\RequestEvent;
  37. use Plugin\PiaProductDisp\Entity\ProductDisplayPeriod;
  38. class ProductEditEventSubscriber implements EventSubscriberInterface
  39. {
  40.     /**
  41.      * @var PiaHistoryRepository
  42.      */
  43.     private $piaHistoryRepository;
  44.     /**
  45.      * @var PiaHisProductRepository
  46.      */
  47.     private $piaHisProductRepository;
  48.     /**
  49.      * @var PiaHisProductClassRepository
  50.      */
  51.     private $piaHisProductClassRepository;
  52.     /**
  53.      * @var PiaHisProductCategoryRepository
  54.      */
  55.     private $piaHisProductCategoryRepository;
  56.     /**
  57.      * @var PiaHisProductImageRepository
  58.      */
  59.     private $piaHisProductImageRepository;
  60.     /**
  61.      * @var PiaHisProductStockRepository
  62.      */
  63.     private $piaHisProductStockRepository;
  64.     /**
  65.      * @var PiaHisProductTagRepository
  66.      */
  67.     private $piaHisProductTagRepository;
  68.     /**
  69.      * @var PiaHisProductAddRepository
  70.      */
  71.     private $piaHisProductAddRepository;
  72.     /**
  73.      * @var PiaHisRankPriceRepository
  74.      */
  75.     private $piaHisRankPriceRepository;
  76.     /**
  77.      * @var PiaHisCustomerClassRepository
  78.      */
  79.     private $piaHisCustomerClassRepository;
  80.     /**
  81.      * @var PiaHisPointRepository
  82.      */
  83.     private $piaHisPointRepository;
  84.     /**
  85.      * @var PiaHisMeyasuRepository
  86.      */
  87.     private $piaHisMeyasuRepository;
  88.     /**
  89.      * @var ProductRepository
  90.      */
  91.     private $productRepository;
  92.     /**
  93.      * @var MemberRepository
  94.      */
  95.     private $memberRepository;
  96.     /**
  97.      * @var TokenStorageInterface
  98.      */
  99.     private $tokenStorage;
  100.     /**
  101.      * @var EntityManagerInterface
  102.      */
  103.     private $entityManager;
  104.     /**
  105.      * @var RequestStack
  106.      */
  107.     private $requestStack;
  108.     /**
  109.      * ProductEditEventSubscriber constructor.
  110.      */
  111.     public function __construct(
  112.         PiaHistoryRepository $piaHistoryRepository,
  113.         PiaHisProductRepository $piaHisProductRepository,
  114.         PiaHisProductClassRepository $piaHisProductClassRepository,
  115.         PiaHisProductCategoryRepository $piaHisProductCategoryRepository,
  116.         PiaHisProductImageRepository $piaHisProductImageRepository,
  117.         PiaHisProductStockRepository $piaHisProductStockRepository,
  118.         PiaHisProductTagRepository $piaHisProductTagRepository,
  119.         PiaHisProductAddRepository $piaHisProductAddRepository,
  120.         PiaHisRankPriceRepository $piaHisRankPriceRepository,
  121.         PiaHisCustomerClassRepository $piaHisCustomerClassRepository,
  122.         PiaHisPointRepository $piaHisPointRepository,
  123.         PiaHisMeyasuRepository $piaHisMeyasuRepository,
  124.         ProductRepository $productRepository,
  125.         MemberRepository $memberRepository,
  126.         TokenStorageInterface $tokenStorage,
  127.         EntityManagerInterface $entityManager,
  128.         RequestStack $requestStack
  129.     ) {
  130.         $this->piaHistoryRepository $piaHistoryRepository;
  131.         $this->piaHisProductRepository $piaHisProductRepository;
  132.         $this->piaHisProductClassRepository $piaHisProductClassRepository;
  133.         $this->piaHisProductCategoryRepository $piaHisProductCategoryRepository;
  134.         $this->piaHisProductImageRepository $piaHisProductImageRepository;
  135.         $this->piaHisProductStockRepository $piaHisProductStockRepository;
  136.         $this->piaHisProductTagRepository $piaHisProductTagRepository;
  137.         $this->piaHisProductAddRepository $piaHisProductAddRepository;
  138.         $this->piaHisRankPriceRepository $piaHisRankPriceRepository;
  139.         $this->piaHisCustomerClassRepository $piaHisCustomerClassRepository;
  140.         $this->piaHisPointRepository $piaHisPointRepository;
  141.         $this->piaHisMeyasuRepository $piaHisMeyasuRepository;
  142.         $this->productRepository $productRepository;
  143.         $this->memberRepository $memberRepository;
  144.         $this->tokenStorage $tokenStorage;
  145.         $this->entityManager $entityManager;
  146.         $this->requestStack $requestStack;
  147.     }
  148.     /**
  149.      * @return array
  150.      */
  151.     public static function getSubscribedEvents()
  152.     {
  153.         return [
  154.             'kernel.request' => ['onKernelRequest'0],
  155.             EccubeEvents::ADMIN_PRODUCT_EDIT_COMPLETE => ['onProductEditComplete', -1000], // 優先度をさらに下げる
  156.         ];
  157.     }
  158.     /**
  159.      * 商品編集画面のリクエストを検知して変更前の値を保存
  160.      *
  161.      * @param RequestEvent $event
  162.      */
  163.     public function onKernelRequest(RequestEvent $event)
  164.     {
  165.         $request $event->getRequest();
  166.         
  167.         // 商品編集画面のPOSTリクエストのみ処理
  168.         if ($request->isMethod('POST') && 
  169.             $request->attributes->get('_route') === 'admin_product_product_edit') {
  170.             
  171.             $productId $request->get('id');
  172.             error_log('PiaHistory: onKernelRequest - Product ID: ' $productId);
  173.             
  174.             if ($productId) {
  175.                 $this->saveProductBeforeData($productId);
  176.                 error_log('PiaHistory: Before data saved for Product ID: ' $productId);
  177.             }
  178.         }
  179.     }
  180.     /**
  181.      * @param EventArgs $event
  182.      */
  183.     public function onProductEditComplete(EventArgs $event)
  184.     {
  185.         error_log('PiaHistory: onProductEditComplete called');
  186.         $Product $event->getArgument('Product');
  187.         if ($Product && $Product->getId()) {
  188.             error_log('PiaHistory: Product ID: ' $Product->getId());
  189.             $this->saveProductHistory($Product->getId());
  190.         } else {
  191.             error_log('PiaHistory: No product or product ID found');
  192.         }
  193.     }
  194.     /**
  195.      * リクエスト終了時の処理
  196.      *
  197.      * @param \Symfony\Component\HttpKernel\Event\TerminateEvent $event
  198.      */
  199.     public function onKernelTerminate(\Symfony\Component\HttpKernel\Event\TerminateEvent $event)
  200.     {
  201.         $request $event->getRequest();
  202.         
  203.         // デバッグログ
  204.         error_log('PiaHistory: onKernelTerminate called - Route: ' $request->attributes->get('_route') . ', Method: ' $request->getMethod());
  205.         
  206.         // 商品編集画面のPOSTリクエストの場合のみ処理
  207.         if ($request->isMethod('POST') && 
  208.             $request->attributes->get('_route') === 'admin_product_product_edit') {
  209.             
  210.             $productId $request->get('id');
  211.             error_log('PiaHistory: Product edit detected - Product ID: ' $productId);
  212.             
  213.             if ($productId) {
  214.                 $this->saveProductHistory($productId);
  215.             }
  216.         }
  217.     }
  218.     /**
  219.      * 商品編集前のデータをセッションに保存
  220.      *
  221.      * @param int $productId
  222.      */
  223.     private function saveProductBeforeData($productId)
  224.     {
  225.         try {
  226.             $product $this->productRepository->find($productId);
  227.             if (!$product) {
  228.                 return;
  229.             }
  230.             $request $this->requestStack->getCurrentRequest();
  231.             if (!$request) {
  232.                 return;
  233.             }
  234.             $session $request->getSession();
  235.             
  236.             // 変更前の商品データをセッションに保存
  237.             $beforeData = [
  238.                 'product' => [
  239.                     'id' => $product->getId(),
  240.                     'name' => $product->getName(),
  241.                     'note' => $product->getNote(),
  242.                     'description_list' => $product->getDescriptionList(),
  243.                     'description_detail' => $product->getDescriptionDetail(),
  244.                     'search_word' => $product->getSearchWord(),
  245.                     'free_area' => $product->getFreeArea(),
  246.                     'status' => $product->getStatus() ? $product->getStatus()->getId() : null,
  247.                     'datetime_start' => $this->getDisplayStartDate($product),
  248.                     'datetime_end' => $this->getDisplayEndDate($product),
  249.                 ],
  250.                 'product_classes' => [],
  251.                 'product_categories' => [],
  252.                 'product_images' => [],
  253.                 'product_tags' => [],
  254.             ];
  255.             // 商品規格データ
  256.             foreach ($product->getProductClasses() as $productClass) {
  257.                 $beforeData['product_classes'][] = [
  258.                     'id' => $productClass->getId(),
  259.                     'product_type_id' => null// EC-CUBE4では商品規格タイプが存在しない
  260.                     'class_category_id1' => $productClass->getClassCategory1() ? $productClass->getClassCategory1()->getId() : null,
  261.                     'class_category_id2' => $productClass->getClassCategory2() ? $productClass->getClassCategory2()->getId() : null,
  262.                     'delivery_date_id' => $productClass->getDeliveryDuration() ? $productClass->getDeliveryDuration()->getId() : null,
  263.                     'code' => $productClass->getCode(),
  264.                     'stock' => $productClass->getStock(),
  265.                     'stock_unlimited' => $productClass->isStockUnlimited(),
  266.                     'sale_limit' => $productClass->getSaleLimit(),
  267.                     'price01' => $productClass->getPrice01(),
  268.                     'price02' => $productClass->getPrice02(),
  269.                     'delivery_fee' => $productClass->getDeliveryFee(),
  270.                 ];
  271.             }
  272.             // 商品カテゴリーデータ
  273.             foreach ($product->getProductCategories() as $index => $productCategory) {
  274.                 $beforeData['product_categories'][] = [
  275.                     'category_id' => $productCategory->getCategory()->getId(),
  276.                     'rank' => $index 1// EC-CUBE4では順序を配列のインデックスで管理
  277.                 ];
  278.             }
  279.             // 商品画像データ
  280.             foreach ($product->getProductImage() as $index => $productImage) {
  281.                 $beforeData['product_images'][] = [
  282.                     'file_name' => $productImage->getFileName(),
  283.                     'rank' => $productImage->getSortNo() ?: ($index 1), // EC-CUBE4ではgetSortNo()を使用
  284.                 ];
  285.             }
  286.             // 商品タグデータ
  287.             foreach ($product->getProductTag() as $productTag) {
  288.                 $beforeData['product_tags'][] = [
  289.                     'tag_id' => $productTag->getTag()->getId(),
  290.                 ];
  291.             }
  292.             $session->set('pia_history_before_data_' $productId$beforeData);
  293.         } catch (\Exception $e) {
  294.             error_log('PiaHistory: 変更前データ保存エラー - ' $e->getMessage());
  295.         }
  296.     }
  297.     /**
  298.      * 商品履歴を保存
  299.      *
  300.      * @param int $productId
  301.      */
  302.     private function saveProductHistory($productId)
  303.     {
  304.         try {
  305.             error_log('PiaHistory: saveProductHistory called for Product ID: ' $productId);
  306.             
  307.             // EntityManagerの状態をチェック
  308.             if (!$this->entityManager->isOpen()) {
  309.                 error_log('PiaHistory: EntityManager is closed, skipping history save');
  310.                 return;
  311.             }
  312.             
  313.             error_log('PiaHistory: EntityManager is open, proceeding with history save');
  314.             $product $this->productRepository->find($productId);
  315.             if (!$product) {
  316.                 return;
  317.             }
  318.             $request $this->requestStack->getCurrentRequest();
  319.             if (!$request) {
  320.                 return;
  321.             }
  322.             $session $request->getSession();
  323.             $beforeData $session->get('pia_history_before_data_' $productId);
  324.             
  325.             // 変更前のデータがない場合はスキップ
  326.             if (!$beforeData) {
  327.                 return;
  328.             }
  329.             // 変更があったかチェック
  330.             $hasChanges $this->checkProductChanges($product$beforeData);
  331.             error_log('PiaHistory: Changes detected: ' . ($hasChanges 'YES' 'NO'));
  332.             
  333.             if (!$hasChanges) {
  334.                 // 変更がない場合はセッションをクリアして終了
  335.                 error_log('PiaHistory: No changes detected, skipping history save');
  336.                 $session->remove('pia_history_before_data_' $productId);
  337.                 return;
  338.             }
  339.             // トランザクション開始
  340.             $this->entityManager->beginTransaction();
  341.             // 管理者情報を取得
  342.             $adminId 1;
  343.             $adminName '管理者';
  344.             $adminDepartment '未設定';
  345.             if ($this->tokenStorage->getToken() && $this->tokenStorage->getToken()->getUser()) {
  346.                 $admin $this->tokenStorage->getToken()->getUser();
  347.                 $adminId $admin->getId();
  348.                 $adminName $admin->getUsername();
  349.                 
  350.                 // 管理者の詳細情報を取得
  351.                 $member $this->memberRepository->find($adminId);
  352.                 if ($member) {
  353.                     $adminName $member->getName();
  354.                     // 所属情報を取得(Memberエンティティに所属フィールドがある場合)
  355.                     if (method_exists($member'getDepartment')) {
  356.                         $adminDepartment $member->getDepartment() ?: '未設定';
  357.                     }
  358.                 }
  359.             }
  360.             // 履歴メイン情報を作成
  361.             $history = new PiaHistory();
  362.             $history->setProductId($productId);
  363.             $history->setUpdateId($adminId);
  364.             $history->setUpdateName($adminName);
  365.             $history->setUpdateDepartment($adminDepartment);
  366.             $history->setUpdateNum(1); // 更新回数(仮)
  367.             $history->setUpdateNote('商品情報を更新しました');
  368.             $history->setCreateDate(new \DateTime());
  369.             $history->setUpdateDate(new \DateTime());
  370.             $history->setDelFlg(0);
  371.             $this->entityManager->persist($history);
  372.             $this->entityManager->flush();
  373.             $historyId $history->getId();
  374.             // 変更箇所を詳細に検知
  375.             $changes $this->detectDetailedChanges($product$beforeData);
  376.             
  377.             // 変更があった項目のみ履歴を保存
  378.             if (isset($changes['product'])) {
  379.                 $this->saveProductBasicHistory($historyId$product$beforeData['product'], $changes['product']);
  380.             }
  381.             
  382.             if (isset($changes['product_classes'])) {
  383.                 $this->saveProductClassHistory($historyId$product$beforeData['product_classes'], $changes['product_classes']);
  384.             }
  385.             
  386.             if (isset($changes['product_categories'])) {
  387.                 $this->saveProductCategoryHistory($historyId$product$beforeData['product_categories'], $changes['product_categories']);
  388.             }
  389.             
  390.             if (isset($changes['product_images'])) {
  391.                 $this->saveProductImageHistory($historyId$product$beforeData['product_images'], $changes['product_images']);
  392.             }
  393.             
  394.             if (isset($changes['product_tags'])) {
  395.                 $this->saveProductTagHistory($historyId$product$beforeData['product_tags'], $changes['product_tags']);
  396.             }
  397.             
  398.             // その他の履歴保存(変更検知は別途実装)
  399.             $this->saveProductStockHistory($historyId$product);
  400.             $this->saveProductAddHistory($historyId$product);
  401.             $this->saveRankPriceHistory($historyId$product);
  402.             $this->saveCustomerClassHistory($historyId$product);
  403.             $this->savePointHistory($historyId$product);
  404.             $this->saveMeyasuHistory($historyId$product);
  405.             
  406.             // 最後にまとめてflush
  407.             $this->entityManager->flush();
  408.             
  409.             // トランザクションコミット
  410.             $this->entityManager->commit();
  411.             // セッションをクリア
  412.             $session->remove('pia_history_before_data_' $productId);
  413.         } catch (\Exception $e) {
  414.             // トランザクションロールバック
  415.             if ($this->entityManager->getConnection()->isTransactionActive()) {
  416.                 $this->entityManager->rollback();
  417.             }
  418.             
  419.             // エラーログを出力
  420.             error_log('PiaHistory: 商品履歴保存エラー - ' $e->getMessage());
  421.         }
  422.     }
  423.     /**
  424.      * 商品の変更をチェック
  425.      *
  426.      * @param Product $product
  427.      * @param array $beforeData
  428.      * @return bool
  429.      */
  430.     private function checkProductChanges(Product $product, array $beforeData)
  431.     {
  432.         $beforeProduct $beforeData['product'];
  433.         
  434.         // 基本情報の変更チェック
  435.         if ($product->getName() !== $beforeProduct['name'] ||
  436.             $product->getNote() !== $beforeProduct['note'] ||
  437.             $product->getDescriptionList() !== $beforeProduct['description_list'] ||
  438.             $product->getDescriptionDetail() !== $beforeProduct['description_detail'] ||
  439.             $product->getSearchWord() !== $beforeProduct['search_word'] ||
  440.             $product->getFreeArea() !== $beforeProduct['free_area'] ||
  441.             ($product->getStatus() ? $product->getStatus()->getId() : null) !== $beforeProduct['status']) {
  442.             return true;
  443.         }
  444.         // 日時の変更チェック(PiaProductDispプラグインとの連携)
  445.         $currentStart $this->getDisplayStartDate($product);
  446.         $currentEnd $this->getDisplayEndDate($product);
  447.         
  448.         if ($currentStart !== $beforeProduct['datetime_start'] || $currentEnd !== $beforeProduct['datetime_end']) {
  449.             return true;
  450.         }
  451.         // 商品規格の変更チェック
  452.         $currentClasses = [];
  453.         foreach ($product->getProductClasses() as $productClass) {
  454.             $currentClasses[] = [
  455.                 'id' => $productClass->getId(),
  456.                 'code' => $productClass->getCode(),
  457.                 'stock' => $productClass->getStock(),
  458.                 'price01' => $productClass->getPrice01(),
  459.                 'price02' => $productClass->getPrice02(),
  460.             ];
  461.         }
  462.         
  463.         if ($currentClasses !== $beforeData['product_classes']) {
  464.             return true;
  465.         }
  466.         return false;
  467.     }
  468.     /**
  469.      * 変更箇所を詳細に検知
  470.      *
  471.      * @param Product $product
  472.      * @param array $beforeData
  473.      * @return array
  474.      */
  475.     private function detectDetailedChanges(Product $product, array $beforeData)
  476.     {
  477.         $changes = [];
  478.         $beforeProduct $beforeData['product'];
  479.         
  480.         // 基本情報の変更検知
  481.         if ($product->getName() !== $beforeProduct['name']) {
  482.             $changes['product']['name'] = [
  483.                 'before' => $beforeProduct['name'],
  484.                 'after' => $product->getName()
  485.             ];
  486.         }
  487.         
  488.         if ($product->getNote() !== $beforeProduct['note']) {
  489.             $changes['product']['note'] = [
  490.                 'before' => $beforeProduct['note'],
  491.                 'after' => $product->getNote()
  492.             ];
  493.         }
  494.         
  495.         if ($product->getDescriptionList() !== $beforeProduct['description_list']) {
  496.             $changes['product']['description_list'] = [
  497.                 'before' => $beforeProduct['description_list'],
  498.                 'after' => $product->getDescriptionList()
  499.             ];
  500.         }
  501.         
  502.         if ($product->getDescriptionDetail() !== $beforeProduct['description_detail']) {
  503.             $changes['product']['description_detail'] = [
  504.                 'before' => $beforeProduct['description_detail'],
  505.                 'after' => $product->getDescriptionDetail()
  506.             ];
  507.         }
  508.         
  509.         if ($product->getSearchWord() !== $beforeProduct['search_word']) {
  510.             $changes['product']['search_word'] = [
  511.                 'before' => $beforeProduct['search_word'],
  512.                 'after' => $product->getSearchWord()
  513.             ];
  514.         }
  515.         
  516.         if ($product->getFreeArea() !== $beforeProduct['free_area']) {
  517.             $changes['product']['free_area'] = [
  518.                 'before' => $beforeProduct['free_area'],
  519.                 'after' => $product->getFreeArea()
  520.             ];
  521.         }
  522.         
  523.         $currentStatus $product->getStatus() ? $product->getStatus()->getId() : null;
  524.         if ($currentStatus !== $beforeProduct['status']) {
  525.             $changes['product']['status'] = [
  526.                 'before' => $beforeProduct['status'],
  527.                 'after' => $currentStatus
  528.             ];
  529.         }
  530.         // 日時の変更検知
  531.         $currentStart $this->getDisplayStartDate($product);
  532.         $currentEnd $this->getDisplayEndDate($product);
  533.         
  534.         if ($currentStart !== $beforeProduct['datetime_start']) {
  535.             $changes['product']['datetime_start'] = [
  536.                 'before' => $beforeProduct['datetime_start'],
  537.                 'after' => $currentStart
  538.             ];
  539.         }
  540.         
  541.         if ($currentEnd !== $beforeProduct['datetime_end']) {
  542.             $changes['product']['datetime_end'] = [
  543.                 'before' => $beforeProduct['datetime_end'],
  544.                 'after' => $currentEnd
  545.             ];
  546.         }
  547.         // 商品規格の変更検知
  548.         $currentClasses = [];
  549.         foreach ($product->getProductClasses() as $productClass) {
  550.             $currentClasses[] = [
  551.                 'id' => $productClass->getId(),
  552.                 'code' => $productClass->getCode(),
  553.                 'stock' => $productClass->getStock(),
  554.                 'price01' => $productClass->getPrice01(),
  555.                 'price02' => $productClass->getPrice02(),
  556.             ];
  557.         }
  558.         
  559.         if ($currentClasses !== $beforeData['product_classes']) {
  560.             $changes['product_classes'] = [
  561.                 'before' => $beforeData['product_classes'],
  562.                 'after' => $currentClasses
  563.             ];
  564.         }
  565.         // 商品カテゴリーの変更検知
  566.         $currentCategories = [];
  567.         foreach ($product->getProductCategories() as $index => $productCategory) {
  568.             $currentCategories[] = [
  569.                 'category_id' => $productCategory->getCategory()->getId(),
  570.                 'rank' => $index 1,
  571.             ];
  572.         }
  573.         
  574.         if ($currentCategories !== $beforeData['product_categories']) {
  575.             $changes['product_categories'] = [
  576.                 'before' => $beforeData['product_categories'],
  577.                 'after' => $currentCategories
  578.             ];
  579.         }
  580.         // 商品画像の変更検知
  581.         $currentImages = [];
  582.         foreach ($product->getProductImage() as $index => $productImage) {
  583.             $currentImages[] = [
  584.                 'file_name' => $productImage->getFileName(),
  585.                 'rank' => $productImage->getSortNo() ?: ($index 1),
  586.             ];
  587.         }
  588.         
  589.         if ($currentImages !== $beforeData['product_images']) {
  590.             $changes['product_images'] = [
  591.                 'before' => $beforeData['product_images'],
  592.                 'after' => $currentImages
  593.             ];
  594.         }
  595.         // 商品タグの変更検知
  596.         $currentTags = [];
  597.         foreach ($product->getProductTag() as $productTag) {
  598.             $currentTags[] = [
  599.                 'tag_id' => $productTag->getTag()->getId(),
  600.             ];
  601.         }
  602.         
  603.         if ($currentTags !== $beforeData['product_tags']) {
  604.             $changes['product_tags'] = [
  605.                 'before' => $beforeData['product_tags'],
  606.                 'after' => $currentTags
  607.             ];
  608.         }
  609.         return $changes;
  610.     }
  611.     /**
  612.      * 商品基本情報履歴を保存
  613.      *
  614.      * @param int $historyId
  615.      * @param Product $product
  616.      * @param array $beforeProduct
  617.      * @param array $changes
  618.      */
  619.     private function saveProductBasicHistory($historyIdProduct $product, array $beforeProduct, array $changes = [])
  620.     {
  621.         $productHistory = new PiaHisProduct();
  622.         $productHistory->setHisId($historyId);
  623.         $productHistory->setProductId($product->getId());
  624.         $productHistory->setStatus($product->getStatus()->getId());
  625.         $productHistory->setName($product->getName());
  626.         $productHistory->setNote($product->getNote());
  627.         $productHistory->setDescriptionList($product->getDescriptionList());
  628.         $productHistory->setDescriptionDetail($product->getDescriptionDetail());
  629.         $productHistory->setSearchWord($product->getSearchWord());
  630.         $productHistory->setFreeArea($product->getFreeArea());
  631.         $productHistory->setDatetimeStart($this->getDisplayStartDate($product));
  632.         $productHistory->setDatetimeEnd($this->getDisplayEndDate($product));
  633.         $productHistory->setDelFlg(0);
  634.         
  635.         // 変更情報をJSONで保存
  636.         if (!empty($changes)) {
  637.             $productHistory->setChangeData(json_encode($changesJSON_UNESCAPED_UNICODE));
  638.         }
  639.         $this->entityManager->persist($productHistory);
  640.     }
  641.     /**
  642.      * 商品規格履歴を保存
  643.      *
  644.      * @param int $historyId
  645.      * @param Product $product
  646.      * @param array $beforeClasses
  647.      */
  648.     private function saveProductClassHistory($historyIdProduct $product, array $beforeClasses)
  649.     {
  650.         foreach ($product->getProductClasses() as $productClass) {
  651.             $classHistory = new PiaHisProductClass();
  652.             $classHistory->setHisId($historyId);
  653.             $classHistory->setProductId($product->getId());
  654.             $classHistory->setProductTypeId(null); // EC-CUBE4では商品規格タイプが存在しない
  655.             $classHistory->setClassCategoryId1($productClass->getClassCategory1() ? $productClass->getClassCategory1()->getId() : null);
  656.             $classHistory->setClassCategoryId2($productClass->getClassCategory2() ? $productClass->getClassCategory2()->getId() : null);
  657.             $classHistory->setDeliveryDateId($productClass->getDeliveryDuration() ? $productClass->getDeliveryDuration()->getId() : null);
  658.             $classHistory->setProductCode($productClass->getCode());
  659.             $classHistory->setStock($productClass->getStock());
  660.             $classHistory->setStockUnlimited($productClass->isStockUnlimited());
  661.             $classHistory->setSaleLimit($productClass->getSaleLimit());
  662.             $classHistory->setPrice01($productClass->getPrice01());
  663.             $classHistory->setPrice02($productClass->getPrice02());
  664.             $classHistory->setDeliveryFee($productClass->getDeliveryFee());
  665.             $this->entityManager->persist($classHistory);
  666.         }
  667.     }
  668.     /**
  669.      * 商品カテゴリー履歴を保存
  670.      *
  671.      * @param int $historyId
  672.      * @param Product $product
  673.      * @param array $beforeCategories
  674.      */
  675.     private function saveProductCategoryHistory($historyIdProduct $product, array $beforeCategories)
  676.     {
  677.         foreach ($product->getProductCategories() as $index => $productCategory) {
  678.             $categoryHistory = new PiaHisProductCategory();
  679.             $categoryHistory->setHisId($historyId);
  680.             $categoryHistory->setProductId($product->getId());
  681.             $categoryHistory->setCategoryId($productCategory->getCategory()->getId());
  682.             $categoryHistory->setRank($index 1); // EC-CUBE4では順序を配列のインデックスで管理
  683.             $this->entityManager->persist($categoryHistory);
  684.         }
  685.     }
  686.     /**
  687.      * 商品画像履歴を保存
  688.      *
  689.      * @param int $historyId
  690.      * @param Product $product
  691.      * @param array $beforeImages
  692.      */
  693.     private function saveProductImageHistory($historyIdProduct $product, array $beforeImages)
  694.     {
  695.         foreach ($product->getProductImage() as $index => $productImage) {
  696.             $imageHistory = new PiaHisProductImage();
  697.             $imageHistory->setHisId($historyId);
  698.             $imageHistory->setProductId($product->getId());
  699.             $imageHistory->setFileName($productImage->getFileName());
  700.             $imageHistory->setRank($productImage->getSortNo() ?: ($index 1)); // EC-CUBE4ではgetSortNo()を使用
  701.             $this->entityManager->persist($imageHistory);
  702.         }
  703.     }
  704.     /**
  705.      * 商品在庫履歴を保存
  706.      *
  707.      * @param int $historyId
  708.      * @param Product $product
  709.      */
  710.     private function saveProductStockHistory($historyIdProduct $product)
  711.     {
  712.         foreach ($product->getProductClasses() as $productClass) {
  713.             $stockHistory = new PiaHisProductStock();
  714.             $stockHistory->setHisId($historyId);
  715.             $stockHistory->setProductClassId($productClass->getId());
  716.             $stockHistory->setStock($productClass->getStock());
  717.             $this->entityManager->persist($stockHistory);
  718.         }
  719.     }
  720.     /**
  721.      * 商品タグ履歴を保存
  722.      *
  723.      * @param int $historyId
  724.      * @param Product $product
  725.      * @param array $beforeTags
  726.      */
  727.     private function saveProductTagHistory($historyIdProduct $product, array $beforeTags)
  728.     {
  729.         foreach ($product->getProductTag() as $productTag) {
  730.             $tagHistory = new PiaHisProductTag();
  731.             $tagHistory->setHisId($historyId);
  732.             $tagHistory->setProductId($product->getId());
  733.             $tagHistory->setTag($productTag->getTag()->getId());
  734.             $this->entityManager->persist($tagHistory);
  735.         }
  736.     }
  737.     /**
  738.      * 商品追加項目履歴を保存
  739.      *
  740.      * @param int $historyId
  741.      * @param Product $product
  742.      */
  743.     private function saveProductAddHistory($historyIdProduct $product)
  744.     {
  745.         // 商品追加項目の履歴保存処理
  746.         // 他のプラグインとの連携が必要な場合はここで実装
  747.     }
  748.     /**
  749.      * 商品ランク価格履歴を保存
  750.      *
  751.      * @param int $historyId
  752.      * @param Product $product
  753.      */
  754.     private function saveRankPriceHistory($historyIdProduct $product)
  755.     {
  756.         // 商品ランク価格の履歴保存処理
  757.         // 他のプラグインとの連携が必要な場合はここで実装
  758.         foreach ($product->getProductClasses() as $productClass) {
  759.             // ランク価格の取得と保存処理
  760.             // 例: CustomerRankプラグインとの連携
  761.         }
  762.     }
  763.     /**
  764.      * 閲覧可能会員種別履歴を保存
  765.      *
  766.      * @param int $historyId
  767.      * @param Product $product
  768.      */
  769.     private function saveCustomerClassHistory($historyIdProduct $product)
  770.     {
  771.         // EC-CUBE4では閲覧可能会員種別の管理が削除されているためスキップ
  772.         // 必要に応じて他のプラグインとの連携で実装
  773.         return;
  774.     }
  775.     /**
  776.      * ポイント付与率履歴を保存
  777.      *
  778.      * @param int $historyId
  779.      * @param Product $product
  780.      */
  781.     private function savePointHistory($historyIdProduct $product)
  782.     {
  783.         // ポイント付与率の履歴保存処理
  784.         // 他のプラグインとの連携が必要な場合はここで実装
  785.     }
  786.     /**
  787.      * 目安価格履歴を保存
  788.      *
  789.      * @param int $historyId
  790.      * @param Product $product
  791.      */
  792.     private function saveMeyasuHistory($historyIdProduct $product)
  793.     {
  794.         // PiaMeyasuプラグインとの連携
  795.         try {
  796.             // PiaMeyasuプラグインのEntityが存在するかチェック
  797.             if (class_exists('Plugin\PiaMeyasu\Entity\PiaMeyasu')) {
  798.                 $meyasuRepository $this->entityManager->getRepository('Plugin\PiaMeyasu\Entity\PiaMeyasu');
  799.                 $meyasuData $meyasuRepository->findOneBy(['product_id' => $product->getId()]);
  800.                 
  801.                 if ($meyasuData) {
  802.                     $meyasuHistory = new PiaHisMeyasu();
  803.                     $meyasuHistory->setHisId($historyId);
  804.                     $meyasuHistory->setProductId($product->getId());
  805.                     $meyasuHistory->setCoinCost($meyasuData->getCoinCost());
  806.                     $meyasuHistory->setExTypeId($meyasuData->getExTypeId());
  807.                     $meyasuHistory->setCommission($meyasuData->getCommission());
  808.                     $meyasuHistory->setEsDispPrice($meyasuData->getEsDispPrice());
  809.                     $this->entityManager->persist($meyasuHistory);
  810.                 }
  811.             }
  812.         } catch (\Exception $e) {
  813.             // エラーログを出力
  814.             error_log('PiaHistory: 目安価格履歴保存エラー - ' $e->getMessage());
  815.         }
  816.     }
  817.     /**
  818.      * PiaProductDispプラグインから表示開始日を取得
  819.      *
  820.      * @param Product $product
  821.      * @return string|null
  822.      */
  823.     private function getDisplayStartDate(Product $product)
  824.     {
  825.         try {
  826.             if (class_exists('Plugin\PiaProductDisp\Entity\ProductDisplayPeriod')) {
  827.                 $displayPeriodRepository $this->entityManager->getRepository('Plugin\PiaProductDisp\Entity\ProductDisplayPeriod');
  828.                 $displayPeriod $displayPeriodRepository->findOneBy(['Product' => $product]);
  829.                 
  830.                 if ($displayPeriod && $displayPeriod->getDisplayStartDate()) {
  831.                     return $displayPeriod->getDisplayStartDate()->format('Y-m-d H:i:s');
  832.                 }
  833.             }
  834.         } catch (\Exception $e) {
  835.             error_log('PiaHistory: 表示開始日取得エラー - ' $e->getMessage());
  836.         }
  837.         
  838.         return null;
  839.     }
  840.     /**
  841.      * PiaProductDispプラグインから表示終了日を取得
  842.      *
  843.      * @param Product $product
  844.      * @return string|null
  845.      */
  846.     private function getDisplayEndDate(Product $product)
  847.     {
  848.         try {
  849.             if (class_exists('Plugin\PiaProductDisp\Entity\ProductDisplayPeriod')) {
  850.                 $displayPeriodRepository $this->entityManager->getRepository('Plugin\PiaProductDisp\Entity\ProductDisplayPeriod');
  851.                 $displayPeriod $displayPeriodRepository->findOneBy(['Product' => $product]);
  852.                 
  853.                 if ($displayPeriod && $displayPeriod->getDisplayEndDate()) {
  854.                     return $displayPeriod->getDisplayEndDate()->format('Y-m-d H:i:s');
  855.                 }
  856.             }
  857.         } catch (\Exception $e) {
  858.             error_log('PiaHistory: 表示終了日取得エラー - ' $e->getMessage());
  859.         }
  860.         
  861.         return null;
  862.     }
  863. }