custom/plugins/AbmAdjustments/src/Subscriber/ProductRedirectionSubscriber.php line 303

Open in your IDE?
  1. <?php
  2. namespace AbmAdjustments\Subscriber;
  3.  
  4. use Doctrine\DBAL\Connection;
  5. use Shopware\Core\Content\Product\Exception\ProductNotFoundException;
  6. use Shopware\Core\Framework\Context;
  7. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  10. use Shopware\Core\Framework\Event\BeforeSendRedirectResponseEvent;
  11. use Shopware\Core\Framework\Event\BeforeSendResponseEvent;
  12. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  13. use Symfony\Component\HttpFoundation\RedirectResponse;
  14. use Symfony\Component\HttpFoundation\RequestStack;
  15. use Symfony\Component\HttpKernel\Event\ExceptionEvent;
  16. use Symfony\Component\HttpKernel\KernelEvents;
  17. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  18. use Symfony\Component\Routing\RouterInterface;
  19. use Shopware\Core\Content\Cms\Exception\PageNotFoundException;
  20. class ProductRedirectionSubscriber implements EventSubscriberInterface
  21. {
  22.     private $router;
  23.     private $productRepository;
  24.     private $seoUrlRepository;
  25.     private $context;
  26.     private $requestStack;
  27.     private $connection;
  28.     private $slugify;
  29.     /**
  30.      * Undocumented function
  31.      *
  32.      * @param RouterInterface $router
  33.      * @param EntityRepositoryInterface $productRepository
  34.      */
  35.     public function __construct(
  36.         RouterInterface           $router,
  37.         EntityRepositoryInterface $productRepository,
  38.         RequestStack              $requestStack,
  39.         Connection                $connection,
  40.         EntityRepositoryInterface $seoUrlRepository
  41.     )
  42.     {
  43.         $this->router $router;
  44.         $this->productRepository $productRepository;
  45.         $this->context Context::createDefaultContext();
  46.         $this->requestStack $requestStack;
  47.         $this->connection $connection;
  48.         $this->seoUrlRepository $seoUrlRepository;
  49.         $this->slugify = new \Cocur\Slugify\Slugify();
  50.     }
  51.     /**
  52.      * @return string[]
  53.      */
  54.     public static function getSubscribedEvents()
  55.     {
  56.         return [
  57.             KernelEvents::EXCEPTION => 'onKernelException',
  58.             BeforeSendRedirectResponseEvent::class => ['onBeforeSendRedirectResponseEvent'1000001],
  59.         ];
  60.     }
  61.     public function onBeforeSendRedirectResponseEvent(BeforeSendResponseEvent $event)
  62.     {
  63.         $request $event->getRequest();
  64.         $pathInfo $request->getPathInfo();
  65.         $statusCode $event->getResponse()->getStatusCode();
  66.         if (strpos($pathInfo'detail') !== false && $statusCode == 301) {
  67.             $urlRedirect301 str_replace($request->attributes->get('sw-storefront-url'), ''$request->attributes->get('sw-canonical-link'));
  68.             $productId str_replace('/detail/'''$pathInfo);
  69.             if(!empty($productId)){
  70.                 $bResponse $this->processRedirectProductDeleted($request$event$urlRedirect301$productId);
  71.                 if (empty($bResponse)) {
  72.                     $aCandidateMatches = [];
  73.                     $urlChunks explode('/'str_replace('/a/'''rtrim($request->attributes->get('sw-original-request-uri'), '/')));
  74.                     $sSubject =   $urlChunks[0];
  75.                     $sFinalMatch '';
  76.                     foreach (array_values($this->getAllFrontNavigationRoutes()) as $aCandidateRoute) {
  77.                         $sCandidateRoute $aCandidateRoute['seo_path_info'];
  78.                         $sMatch $this->longestConsecutiveSubstring($sSubject$sCandidateRoute);
  79.                         if (!empty($sMatch)) {
  80.                              $sFinalMatch strlen($sMatch) > strlen($sFinalMatch) ? $sCandidateRoute $sFinalMatch;
  81.                         }
  82.                     }
  83.                     if (!empty($sFinalMatch)) {
  84.                         $event->setResponse(new RedirectResponse($request->server->get('APP_URL') . '/' $sFinalMatch301));
  85.                     }
  86.                  }
  87.             }
  88.         }
  89.     }
  90.     private function processRedirectProductDeleted($request$event$urlRedirect301 ''$productId '')
  91.     {
  92.         $product $this->getProductById($productId);
  93.         //Product deactivaton block
  94.         if ($product) {
  95.             if (!$product->getActive()) {
  96.                 $categoryRoute $this->getCategoryRoute($productId);
  97.                 if ($categoryRoute) {
  98.                     $event->setResponse(new RedirectResponse($categoryRoute));
  99.                     return true;
  100.                 } else {
  101.                     $homeLink $this->router->generate('frontend.home.page', [], UrlGeneratorInterface::ABSOLUTE_URL);
  102.                     $event->setResponse(new RedirectResponse($homeLink));
  103.                     return true;
  104.                 }
  105.             }
  106.         } else {
  107.             //Product deleted block
  108.             $url $request->attributes->get('sw-original-request-uri');
  109.             //Case access url old of product
  110.             if (!empty($urlRedirect301)) {
  111.                 $url $urlRedirect301//this is canonical url
  112.             }
  113.             if ($url) {
  114.                 $appUrl getenv('APP_URL');
  115.                 if (strpos($appUrl'/stage') !== false && strpos($url'/stage') !== false) {
  116.                     // If the app URL contains '/stage'
  117.                     $secondSlashPosition strpos($url'/'strpos($url'/') + 3);
  118.                     $valueAfterSecondSlash substr($url$secondSlashPosition 3);
  119.                 } else {
  120.                     // If the app URL does not contain '/stage'
  121.                     $secondSlashPosition strpos($url'/'strpos($url'/') + 1);
  122.                     $valueAfterSecondSlash substr($url$secondSlashPosition 1);
  123.                 }
  124.                 //url into substring
  125.                 $parts explode('-'$valueAfterSecondSlash);
  126.                 $manufacturer $parts[0];
  127.                 $baseUrl $this->router->generate('frontend.home.page', [], UrlGeneratorInterface::ABSOLUTE_URL);
  128.                 // searching from category
  129.                 if ($parts !== null) {
  130.                     // Initialize an empty array to store LIKE conditions
  131.                     $sqlCondition = array();
  132.                     $parameters = array();
  133.                     // Loop through $parts and build LIKE conditions
  134.                     foreach ($parts as $index => $part) {
  135.                         if (isset($part)) {
  136.                             $placeholder ":part$index";
  137.                             $sqlCondition[] = 'name LIKE ' $placeholder;
  138.                             $parameters[$placeholder] = $part '%';
  139.                         }
  140.                     }
  141.                     // Combine the LIKE conditions using OR
  142.                     $finalSQLClause implode(' OR '$sqlCondition);
  143.                     // Build the final SQL statement with placeholders
  144.                     $sql = <<<SQL
  145.                           SELECT name
  146.                           FROM category_translation
  147.                           WHERE $finalSQLClause
  148.                           LIMIT 1
  149.                       SQL;
  150.                     // Prepare the statement
  151.                     $stmt $this->connection->prepare($sql);
  152.                     // Bind parameters
  153.                     foreach ($parameters as $placeholder => $value) {
  154.                         $stmt->bindValue($placeholder$value);
  155.                     }
  156.                     // Execute the statement
  157.                     $stmt->execute();
  158.                     // Fetch the result
  159.                     $aResults $stmt->fetchColumn();
  160.                     //dd($parts,$parameters,$sqlCondition,$finalSQLClause,$sql,$aResults);
  161.                     if ($aResults) {
  162.                         $slug $this->slugify->slugify($aResults);
  163.                         $manufacturerCategoryLink $baseUrl "$slug/";
  164.                         if ($this->checkUrlStatus($manufacturerCategoryLink) == 200) {
  165.                             $event->setResponse(new RedirectResponse($manufacturerCategoryLink));
  166.                             return true;
  167.                         }
  168.                     }
  169.                 }
  170.                 //search from manufacture
  171.                 $manufacturerCategoryLink $baseUrl "hersteller/$manufacturer/";
  172.                 //if manufacturer is a single name
  173.                 if ($this->checkUrlStatus($manufacturerCategoryLink) == 200) {
  174.                     $event->setResponse(new RedirectResponse($manufacturerCategoryLink));
  175.                     return true;
  176.                 }
  177.                 //if manufacturer has two words in his name e.g black-decker
  178.                 $sql = <<<SQL
  179.                     SELECT NAME
  180.                           FROM product_manufacturer_translation
  181.                           WHERE name LIKE :manufacturer OR name LIKE :part1
  182.                           LIMIT 1
  183.                     SQL;
  184.                 // Prepare the statement
  185.                 $stmt $this->connection->prepare($sql);
  186.                 // Bind parameters
  187.                 $stmt->bindValue(':manufacturer'$manufacturer '%');
  188.                 $stmt->bindValue(':part1'$parts[1] . '%');
  189.                 // Execute the statement
  190.                 $stmt->execute();
  191.                 // Fetch the result
  192.                 $aResults $stmt->fetchOne();
  193.                 if ($aResults) {
  194.                     $productManufacturerLink $baseUrl "hersteller/$manufacturer-$parts[1]/";
  195.                     if ($this->checkUrlStatus($productManufacturerLink) == 200) {
  196.                         $event->setResponse(new RedirectResponse($productManufacturerLink));
  197.                         return true;
  198.                     }
  199.                 }
  200.                 // Assuming $manufacturerCategoryLink is the variable containing the error
  201.                 $error_message "Error with manufacturer category link: " $manufacturerCategoryLink;
  202.                 // Logging the error message to a file
  203.                 error_log($error_message3"/var/tmp/my-errors.log");
  204.                 return false;
  205.                 // throw new ProductNotFoundException($productId);
  206.                 $response = new RedirectResponse($request->server->get('APP_URL') . '/404');
  207.                 $response->send();
  208.                 exit;
  209.             }
  210.         }
  211.     }
  212.     private function getProductById(string $productId)
  213.     {
  214.         return $this->productRepository->search(
  215.             (new Criteria())->addFilter(new EqualsFilter('id'$productId)),
  216.             $this->context
  217.         )->first();
  218.     }
  219.     private function getCategoryRoute(string $productId): ?string
  220.     {
  221.         $product $this->getProductById($productId);
  222.         if ($product) {
  223.             $categories $product->getCategoryIds();
  224.             if (!empty($categories)) {
  225.                 $sCatId $categories[0];
  226.                 $criteria = new Criteria();
  227.                 $criteria->addFilter(new EqualsFilter('foreignKey'$sCatId));
  228.                 $seoPathInfo $this->seoUrlRepository->search($criteriaContext::createDefaultContext())->first();
  229.                 $sCatUrl $seoPathInfo->getSeoPathInfo();
  230.                 $fullUrl rtrim(getenv('APP_URL'), '/') . '/' ltrim($sCatUrl'/');
  231.                 return $fullUrl;
  232.             }
  233.         }
  234.         return null;
  235.     }
  236.     private function checkUrlStatus($url)
  237.     {
  238.         // Initialize cURL session
  239.         $ch curl_init($url);
  240.         // Set cURL options
  241.         curl_setopt($chCURLOPT_RETURNTRANSFERtrue);
  242.         curl_setopt($chCURLOPT_NOBODYtrue);
  243.         curl_setopt($chCURLOPT_FOLLOWLOCATIONtrue);
  244.         curl_setopt($chCURLOPT_CONNECTTIMEOUT3);
  245.         // Execute cURL session
  246.         curl_exec($ch);
  247.         // Get HTTP status code
  248.         $statusCode curl_getinfo($chCURLINFO_HTTP_CODE);
  249.         // Close cURL session
  250.         curl_close($ch);
  251.         return $statusCode;
  252.     }
  253.     public function onKernelException(ExceptionEvent $event)
  254.     {
  255.         $request $event->getRequest();
  256.         $productId $request->attributes->get('productId');
  257.         $exception $event->getThrowable();
  258.         if ($exception instanceof ProductNotFoundException || $exception instanceof NotFoundHttpException) {
  259.             $product null;
  260.             if ($productId != null) {
  261.                 $product $this->getProductById($productId);
  262.             }
  263.             //Product deactivaton block
  264.             if ($product) {
  265.                 if (!$product->getActive()) {
  266.                     $categoryRoute $this->getCategoryRoute($productId);
  267.                     if ($categoryRoute) {
  268.                         $event->setResponse(new RedirectResponse($categoryRoute301));
  269.                         return;
  270.                     } else {
  271.                         $homeLink $this->router->generate('frontend.home.page', [], UrlGeneratorInterface::ABSOLUTE_URL);
  272.                         $event->setResponse(new RedirectResponse($homeLink301));
  273.                         return;
  274.                     }
  275.                 }
  276.             } else {
  277.                 //Product deleted block
  278.                 $url $request->attributes->get('sw-original-request-uri');
  279.                 if ($url) {
  280.                     $appUrl getenv('APP_URL');
  281.                     if (strpos($appUrl'/stage') !== false) {
  282.                         // If the app URL contains '/stage'
  283.                         $secondSlashPosition strpos($url'/'strpos($url'/') + 3);
  284.                         $valueAfterSecondSlash substr($url$secondSlashPosition 3);
  285.                     } else {
  286.                         // If the app URL does not contain '/stage'
  287.                         $secondSlashPosition strpos($url'/'strpos($url'/') + 1);
  288.                         $valueAfterSecondSlash substr($url$secondSlashPosition 1);
  289.                     }
  290.                     //url into substring
  291.                     $parts explode('-'$valueAfterSecondSlash);
  292.                     $manufacturer $parts[0];
  293.                     $baseUrl $this->router->generate('frontend.home.page', [], UrlGeneratorInterface::ABSOLUTE_URL);
  294.                     // searching from category
  295.                     if ($parts !== null) {
  296.                         // Initialize an empty array to store LIKE conditions
  297.                         $sqlCondition = array();
  298.                         $parameters = array();
  299.                         // Loop through $parts and build LIKE conditions
  300.                         foreach ($parts as $index => $part) {
  301.                             if (isset($part)) {
  302.                                 $placeholder ":part$index";
  303.                                 $sqlCondition[] = 'name LIKE ' $placeholder;
  304.                                 $parameters[$placeholder] = $part '%';
  305.                             }
  306.                         }
  307.                         // Combine the LIKE conditions using OR
  308.                         $finalSQLClause implode(' OR '$sqlCondition);
  309.                         // Build the final SQL statement with placeholders
  310.                         $sql = <<<SQL
  311.                           SELECT name
  312.                           FROM category_translation
  313.                           WHERE $finalSQLClause
  314.                           LIMIT 1
  315.                       SQL;
  316.                         // Prepare the statement
  317.                         $stmt $this->connection->prepare($sql);
  318.                         // Bind parameters
  319.                         foreach ($parameters as $placeholder => $value) {
  320.                             $stmt->bindValue($placeholder$value);
  321.                         }
  322.                         // Execute the statement
  323.                         $stmt->execute();
  324.                         $aResults $stmt->fetchColumn();
  325.                         if ($aResults) {
  326.                             $slug $this->slugify->slugify($aResults);
  327.                             $manufacturerCategoryLink $baseUrl "$slug/";
  328.                             if ($this->checkUrlStatus($manufacturerCategoryLink) == 200) {
  329.                                 $event->setResponse(new RedirectResponse($manufacturerCategoryLink));
  330.                                 return;
  331.                             }
  332.                         }
  333.                     }
  334.                     //search from manufacture
  335.                     $manufacturerCategoryLink $baseUrl "marken/$manufacturer/";
  336.                     //if manufacturer is a single name
  337.                     if ($this->checkUrlStatus($manufacturerCategoryLink) == 200) {
  338.                         $event->setResponse(new RedirectResponse($manufacturerCategoryLink));
  339.                         return;
  340.                     }
  341.                     //if manufacturer has two words in his name e.g black-decker
  342.                     $sql = <<<SQL
  343.                     SELECT NAME
  344.                           FROM product_manufacturer_translation
  345.                           WHERE name LIKE :manufacturer OR name LIKE :part1
  346.                           LIMIT 1
  347.                     SQL;
  348.                     // Prepare the statement
  349.                     $stmt $this->connection->prepare($sql);
  350.                     // Bind parameters
  351.                     $stmt->bindValue(':manufacturer'$manufacturer '%');
  352.                     $stmt->bindValue(':part1'$parts[1] . '%');
  353.                     // Execute the statement
  354.                     $stmt->execute();
  355.                     // Fetch the result
  356.                     $aResults $stmt->fetchOne();
  357.                     if ($aResults) {
  358.                         $productManufacturerLink $baseUrl "marken/$manufacturer-$parts[1]/";
  359.                         if ($this->checkUrlStatus($productManufacturerLink) == 200) {
  360.                             $event->setResponse(new RedirectResponse($productManufacturerLink));
  361.                             return;
  362.                         }
  363.                     }
  364.                     $error_message "Error with manufacturer category link: " $manufacturerCategoryLink;
  365.                     error_log($error_message3"/var/tmp/my-errors.log");
  366.                     return;
  367.                 }
  368.             }
  369.         }
  370.     }
  371.     /**
  372.      * @param $str1
  373.      * @param $str2
  374.      * @return false|string
  375.      */
  376.     public function longestConsecutiveSubstring($str1$str2) {
  377.         $len1 strlen($str1);
  378.         $len2 strlen($str2);
  379.         $dp = [];
  380.         $maxLen 0;
  381.         $endIndex 0;
  382.         for ($i 0$i <= $len1$i++) {
  383.             for ($j 0$j <= $len2$j++) {
  384.                 if ($i == || $j == 0) {
  385.                     $dp[$i][$j] = 0;
  386.                 } elseif ($str1[$i 1] == $str2[$j 1]) {
  387.                     $dp[$i][$j] = $dp[$i 1][$j 1] + 1;
  388.                     if ($dp[$i][$j] > $maxLen) {
  389.                         $maxLen $dp[$i][$j];
  390.                         $endIndex $i 1;
  391.                     }
  392.                 } else {
  393.                     $dp[$i][$j] = 0;
  394.                 }
  395.             }
  396.         }
  397.         if ($maxLen == 0) {
  398.             return '';
  399.         } else {
  400.             return substr($str1$endIndex $maxLen 1$maxLen);
  401.         }
  402.     }
  403.     /**
  404.      * @return array|\mixed[][]
  405.      * @throws \Doctrine\DBAL\Driver\Exception
  406.      * @throws \Doctrine\DBAL\Exception
  407.      */
  408.     private function getAllFrontNavigationRoutes()
  409.     {
  410.         $sql = <<<SQL
  411.                  SELECT
  412.                     su.*
  413.                 FROM
  414.                     seo_url su,
  415.                     `language` l
  416.                 WHERE
  417.                     su.route_name = 'frontend.navigation.page'
  418.                     AND su.language_id = l.id
  419.                 AND l.name = 'Deutsch'
  420. SQL;
  421.         return $this->connection->executeQuery($sql)->fetchAllAssociative();
  422.     }
  423. }