vendor/ezsystems/ezcommerce-shop/src/Silversolutions/Bundle/EshopBundle/Service/CatalogHelper.php line 31

Open in your IDE?
  1. <?php
  2. /**
  3.  * @copyright Copyright (C) Ibexa AS. All rights reserved.
  4.  * @license For full copyright and license information view LICENSE file distributed with this source code.
  5.  */
  6. namespace Silversolutions\Bundle\EshopBundle\Service;
  7. use eZ\Publish\API\Repository\LocationService;
  8. use eZ\Publish\API\Repository\Values\Content\Field;
  9. use eZ\Publish\API\Repository\Values\Content\LocationQuery;
  10. use eZ\Publish\Core\FieldType\TextLine\Value as TextFieldValue;
  11. use eZ\Publish\Core\FieldType\Integer\Value as IntegerFieldValue;
  12. use eZ\Publish\API\Repository\ContentService;
  13. use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
  14. use eZ\Publish\API\Repository\Values\Content\Search\SearchHit;
  15. use eZ\Publish\Core\Helper\TranslationHelper;
  16. use eZ\Publish\Core\MVC\ConfigResolverInterface;
  17. use eZ\Publish\Core\MVC\Symfony\Routing\Generator\UrlAliasGenerator;
  18. use Monolog\Logger;
  19. use Silversolutions\Bundle\EshopBundle\Catalog\CatalogElement;
  20. use Silversolutions\Bundle\EshopBundle\Services\Catalog\CatalogDataProviderService;
  21. use Silversolutions\Bundle\ToolsBundle\Services\EzHelperService;
  22. use eZ\Publish\API\Repository\SearchService;
  23. use Stash\Interfaces\PoolInterface;
  24. use eZ\Publish\Core\MVC\Symfony\SiteAccess;
  25. /**
  26.  * Class CatalogHelper.
  27.  */
  28. class CatalogHelper
  29. {
  30.     const STASH_CACHE_IDENTIFIER 'ses_product_catalog';
  31.     /**
  32.      * This is used if the depth of the product catalog is not set in the backend,
  33.      * which means that we have to fetch and display all children elements.
  34.      * Since we need an int in the implementation, we need to set a high number here.
  35.      *
  36.      * This number is not meant to be changed.
  37.      */
  38.     const MAX_CATALOG_DEPTH 1000;
  39.     /**
  40.      * @var EzHelperService
  41.      */
  42.     protected $ezHelperService;
  43.     /**
  44.      * @var SearchService
  45.      */
  46.     protected $searchService;
  47.     /**
  48.      * @var TranslationHelper
  49.      */
  50.     protected $translationHelper;
  51.     /**
  52.      * @var CatalogDataProviderService
  53.      */
  54.     protected $catalogDataProviderService;
  55.     /**
  56.      * @var \eZ\Publish\Core\MVC\ConfigResolverInterface;
  57.      */
  58.     protected $configResolver;
  59.     /**
  60.      * @var UrlAliasGenerator
  61.      */
  62.     protected $urlAliasGenerator;
  63.     /**
  64.      * @var LocationService
  65.      */
  66.     protected $locationService;
  67.     /**
  68.      * @var PoolInterface
  69.      */
  70.     protected $cache;
  71.     /**
  72.      * @var SiteAccess
  73.      */
  74.     protected $siteAccess;
  75.     /**
  76.      * @var Logger
  77.      */
  78.     protected $logging;
  79.     /**
  80.      * @var int
  81.      */
  82.     protected $cacheTtl;
  83.     /**
  84.      * CatalogHelper constructor.
  85.      *
  86.      * @param ConfigResolverInterface $configResolver
  87.      * @param ContentService $contentService
  88.      * @param TranslationHelper $translationHelper
  89.      * @param EzHelperService $ezHelperService
  90.      * @param SearchService $searchService
  91.      * @param CatalogDataProviderService $catalogDataProviderService
  92.      * @param UrlAliasGenerator $urlAliasGenerator
  93.      * @param LocationService $locationService
  94.      * @param PoolInterface $cache
  95.      * @param SiteAccess $siteAccess
  96.      * @param Logger $logging
  97.      */
  98.     public function __construct(
  99.         ConfigResolverInterface $configResolver,
  100.         ContentService $contentService,
  101.         TranslationHelper $translationHelper,
  102.         EzHelperService $ezHelperService,
  103.         SearchService $searchService,
  104.         CatalogDataProviderService $catalogDataProviderService,
  105.         UrlAliasGenerator $urlAliasGenerator,
  106.         LocationService $locationService,
  107.         PoolInterface $cache,
  108.         SiteAccess $siteAccess,
  109.         Logger $logging
  110.     ) {
  111.         $this->configResolver $configResolver;
  112.         $this->contentService $contentService;
  113.         $this->translationHelper $translationHelper;
  114.         $this->ezHelperService $ezHelperService;
  115.         $this->searchService $searchService;
  116.         $this->catalogDataProviderService $catalogDataProviderService;
  117.         $this->urlAliasGenerator $urlAliasGenerator;
  118.         $this->locationService $locationService;
  119.         $this->cache $cache;
  120.         $this->siteAccess $siteAccess;
  121.         $this->logging $logging;
  122.     }
  123.     /**
  124.      * injects parameters.
  125.      */
  126.     public function setParams()
  127.     {
  128.         $this->cacheTtl $this->configResolver->getParameter('router_cache_ttl''siso_core');
  129.     }
  130.     /**
  131.      * returns true if catalog is enabled.
  132.      *
  133.      * @return bool
  134.      */
  135.     public function isProductCatalogEnabled()
  136.     {
  137.         return $this->configResolver->getParameter('product_catalog_enabled''siso_core');
  138.     }
  139.     /**
  140.      * returns the content type identifier for the root product catalog node.
  141.      *
  142.      * @return string
  143.      */
  144.     public function getProductCatalogIdentifier()
  145.     {
  146.         return $this->configResolver->getParameter('product_catalog_identifier''siso_core');
  147.     }
  148.     /**
  149.      * provides configuration for product catalog.
  150.      *
  151.      * @param SearchHit $searchHit
  152.      *
  153.      * @return array
  154.      */
  155.     public function getProductCatalogConfiguration(SearchHit $searchHit)
  156.     {
  157.         if ($this->isProductCatalogEnabled()) {
  158.             try {
  159.                 $content $this->contentService->loadContent($searchHit->valueObject->contentInfo->id);
  160.                 $productCatalogDepth $this->translationHelper->getTranslatedField($content'depth');
  161.                 $productCatalogRootNode $this->translationHelper->getTranslatedField($content'root_node');
  162.                 if ($productCatalogDepth instanceof Field
  163.                     && $productCatalogRootNode instanceof Field
  164.                 ) {
  165.                     $productCatalogDepth $productCatalogDepth->value;
  166.                     $productCatalogRootNode $productCatalogRootNode->value;
  167.                     if ($productCatalogDepth instanceof TextFieldValue
  168.                         && $productCatalogRootNode instanceof IntegerFieldValue
  169.                     ) {
  170.                         return [
  171.                             'depth' => $productCatalogDepth->text,
  172.                             'root_node' => $productCatalogRootNode->value,
  173.                         ];
  174.                     }
  175.                 }
  176.             } catch (\Exception $e) {
  177.                 $this->logging->warning(
  178.                     'Exception on providing configuration for product catalog: ' $e->getMessage()
  179.                 );
  180.                 return [
  181.                     'depth' => null,
  182.                     'root_node' => null,
  183.                 ];
  184.             }
  185.         }
  186.         return [
  187.             'depth' => null,
  188.             'root_node' => null,
  189.         ];
  190.     }
  191.     /**
  192.      * returns the root node id from the product catalog.
  193.      *
  194.      * @param SearchHit $searchHit
  195.      *
  196.      * @return int|null
  197.      */
  198.     public function getProductCatalogRootNodeId(SearchHit $searchHit)
  199.     {
  200.         if ($this->isProductCatalogEnabled()) {
  201.             $productCatalogConfig $this->getProductCatalogConfiguration($searchHit);
  202.             return (int)$productCatalogConfig['root_node'];
  203.         }
  204.         return null;
  205.     }
  206.     /**
  207.      * Returns the depth of the product catalog. This is set in the eZ backend.
  208.      *
  209.      * @param SearchHit $searchHit
  210.      */
  211.     public function getProductCatalogDepth(SearchHit $searchHit)
  212.     {
  213.         if ($this->isProductCatalogEnabled()) {
  214.             $productCatalogConfig $this->getProductCatalogConfiguration($searchHit);
  215.             return !empty($productCatalogConfig['depth'])
  216.                 ? (int)$productCatalogConfig['depth']
  217.                 : self::MAX_CATALOG_DEPTH;
  218.         }
  219.         return null;
  220.     }
  221.     /**
  222.      * returns the url for the catalog root node.
  223.      *
  224.      * @param $locationId
  225.      *
  226.      * @return string|null
  227.      */
  228.     public function getProductCatalogRootNodeUrl($locationId)
  229.     {
  230.         if ($this->isProductCatalogEnabled()) {
  231.             try {
  232.                 $catalogSearchHit $this->fetchProductCatalog($locationId);
  233.                 if ($catalogSearchHit instanceof SearchHit) {
  234.                     $rootId $this->getProductCatalogRootNodeId($catalogSearchHit);
  235.                     $catalogDataProvider $this->catalogDataProviderService->getDataProvider();
  236.                     $url $catalogDataProvider->getUrlByIdentifier($rootId);
  237.                     return $url;
  238.                 }
  239.             } catch (\Exception $e) {
  240.                 $this->logging->warning('Exception on providing url for the catalog root node: ' $e->getMessage());
  241.                 return null;
  242.             }
  243.         }
  244.         return null;
  245.     }
  246.     /**
  247.      * returns true if search hit is silver catalog node.
  248.      *
  249.      * @param SearchHit $searchHit
  250.      *
  251.      * @return bool
  252.      */
  253.     public function isProductCatalog(SearchHit $searchHit)
  254.     {
  255.         $contentTypeId $this->configResolver->getParameter('product_catalog_content_type_id''siso_core');
  256.         return $searchHit->valueObject->contentInfo->contentTypeId === $contentTypeId;
  257.     }
  258.     /**
  259.      * fetches the product catalog by the location id.
  260.      *
  261.      * @param $locationId
  262.      *
  263.      * @return SearchHit|null
  264.      */
  265.     public function fetchProductCatalog($locationId)
  266.     {
  267.         try {
  268.             $query = new LocationQuery();
  269.             $criteria = [
  270.                 new Criterion\ContentTypeIdentifier($this->getProductCatalogIdentifier()),
  271.                 new Criterion\LocationId($locationId),
  272.                 new Criterion\LanguageCode($this->configResolver->getParameter('languages')),
  273.             ];
  274.             $query->query = new Criterion\LogicalAnd(
  275.                 $criteria
  276.             );
  277.             $query->performCount false;
  278.             $searchHits $this->searchService->findLocations($query)->searchHits;
  279.             return isset($searchHits[0]) ? $searchHits[0] : null;
  280.         } catch (\Exception $e) {
  281.             $this->logging->warning(
  282.                 'Exception on fetching the product catalog by the location id: ' $e->getMessage()
  283.             );
  284.             return null;
  285.         }
  286.     }
  287.     /**
  288.      * fetches all product catalogs.
  289.      *
  290.      * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchHit[]
  291.      */
  292.     public function fetchAllProductCatalogs()
  293.     {
  294.         try {
  295.             $query = new LocationQuery();
  296.             $criteria = [
  297.                 new Criterion\ContentTypeIdentifier($this->getProductCatalogIdentifier()),
  298.                 new Criterion\LanguageCode($this->configResolver->getParameter('languages')),
  299.             ];
  300.             if ($this->configResolver->hasParameter('content.tree_root.location_id')) {
  301.                 $ezLocationRootId $this->configResolver->getParameter('content.tree_root.location_id');
  302.                 $ezLocationRoot $this->locationService->loadLocation($ezLocationRootId);
  303.                 $ezLocationRootPath $ezLocationRoot->pathString;
  304.                 $subtreeCriterion = new Criterion\Subtree($ezLocationRootPath);
  305.                 $criteria[] = $subtreeCriterion;
  306.             }
  307.             $query->query = new Criterion\LogicalAnd(
  308.                 $criteria
  309.             );
  310.             $query->performCount false;
  311.             $searchHits $this->searchService->findLocations($query)->searchHits;
  312.             return $searchHits;
  313.         } catch (\Exception $e) {
  314.             $this->logging->warning('Exception on fetching product catalogs: ' $e->getMessage());
  315.             return [];
  316.         }
  317.     }
  318.     /**
  319.      * returns a list of catalog element urls.
  320.      *
  321.      * array(
  322.      *  35 => '/Showtec'
  323.      *  48 => '/DMT'
  324.      *  62 => '/Bestsellers/Product-Catalog'
  325.      * )
  326.      *
  327.      * @return array|null
  328.      */
  329.     public function getCatalogElementUrls(): ?array
  330.     {
  331.         $catalogUrls $this->getCatalogUrlsFromCache();
  332.         if (!isset($catalogUrls)) {
  333.             $productCatalogs $this->fetchAllProductCatalogs();
  334.             foreach ($productCatalogs as $productCatalog) {
  335.                 $catalogUrl $this->getProductCatalogRootNodeUrl($productCatalog->valueObject->id);
  336.                 if ($catalogUrl != false) {
  337.                     $catalogRootId $this->getProductCatalogRootNodeId($productCatalog);
  338.                     if (null !== $catalogRootId) {
  339.                         $startingUrl $this->ezHelperService->getUrlForSiteAccess('/');
  340.                         $catalogUrl '/' str_replace($startingUrl''$catalogUrl);
  341.                         $catalogUrls[$productCatalog->valueObject->id] = [
  342.                             'url' => $catalogUrl,
  343.                             'id' => $catalogRootId,
  344.                         ];
  345.                     }
  346.                 }
  347.             }
  348.             $this->setCatalogUrlsToCache($catalogUrls);
  349.         }
  350.         return $catalogUrls;
  351.     }
  352.     /**
  353.      * fetches the parent product catalog element for the given catalog element.
  354.      *
  355.      * @param CatalogElement $catalogElement
  356.      *
  357.      * @return int|null
  358.      */
  359.     public function getParentProductCatalog(CatalogElement $catalogElement)
  360.     {
  361.         $catalogUrls $this->getCatalogElementUrls();
  362.         if (null !== $catalogUrls) {
  363.             foreach ($catalogElement->path as $parentId) {
  364.                 foreach ($catalogUrls as $catalogId => $catalogUrl) {
  365.                     if (isset($catalogUrl['id']) && $parentId == $catalogUrl['id']) {
  366.                         return $catalogId;
  367.                     }
  368.                 }
  369.             }
  370.         }
  371.         return null;
  372.     }
  373.     /**
  374.      * stores the url of the catalog root node in the cache.
  375.      *
  376.      * @param $urls
  377.      */
  378.     protected function setCatalogUrlsToCache($urls)
  379.     {
  380.         if (!$this->cache) {
  381.             return;
  382.         }
  383.         try {
  384.             $toCache = [
  385.                 'productCatalogUrl' => $urls,
  386.             ];
  387.             $cacheKey $this->generateCacheKey();
  388.             $this->cache->getItem($cacheKey)
  389.                 ->set($toCache)
  390.                 ->setTTL($this->cacheTtl)
  391.                 ->save();
  392.         } catch (\Exception $e) {
  393.             $this->logging->warning(
  394.                 'Exception on storing the url of the catalog root node in the cache: ' $e->getMessage()
  395.             );
  396.         }
  397.     }
  398.     /**
  399.      * Gets the url of the product catalog from cache.
  400.      *
  401.      * @return array|null
  402.      */
  403.     protected function getCatalogUrlsFromCache(): ?array
  404.     {
  405.         if (!$this->cache) {
  406.             return null;
  407.         }
  408.         try {
  409.             $cacheKey $this->generateCacheKey();
  410.             $cachedItem $this->cache->getItem($cacheKey);
  411.             if ($cachedItem->isMiss()) {
  412.                 return null;
  413.             }
  414.             $cachedData $cachedItem->get();
  415.             if (!is_array($cachedData) || !isset($cachedData['productCatalogUrl'])) {
  416.                 return null;
  417.             }
  418.             return $cachedData['productCatalogUrl'];
  419.         } catch (\Exception $e) {
  420.             $this->logging->warning(
  421.                 'Exception on retrieving the url of the product catalog from cache: ' $e->getMessage()
  422.             );
  423.             return null;
  424.         }
  425.     }
  426.     /**
  427.      * Assembles a unified cache key.
  428.      *
  429.      * @return string
  430.      */
  431.     protected function generateCacheKey()
  432.     {
  433.         $keyElements = [
  434.             self::STASH_CACHE_IDENTIFIER,
  435.             $this->ezHelperService->getCurrentLanguageCode(),
  436.             $this->siteAccess->name,
  437.         ];
  438.         return implode('/'$keyElements);
  439.     }
  440. }