<?php declare(strict_types=1); 
 
namespace Shopware\Core\Content\Seo\SalesChannel; 
 
use Shopware\Core\Content\Category\CategoryEntity; 
use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity; 
use Shopware\Core\Content\Seo\SeoUrl\SeoUrlCollection; 
use Shopware\Core\Content\Seo\SeoUrl\SeoUrlEntity; 
use Shopware\Core\Content\Seo\SeoUrlRoute\SeoUrlRouteInterface as SeoUrlRouteConfigRoute; 
use Shopware\Core\Content\Seo\SeoUrlRoute\SeoUrlRouteRegistry; 
use Shopware\Core\Framework\DataAbstractionLayer\DefinitionInstanceRegistry; 
use Shopware\Core\Framework\DataAbstractionLayer\Entity; 
use Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\AggregationResultCollection; 
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; 
use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult; 
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter; 
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter; 
use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting; 
use Shopware\Core\Framework\Struct\Collection; 
use Shopware\Core\Framework\Struct\Struct; 
use Shopware\Core\PlatformRequest; 
use Shopware\Core\System\SalesChannel\Entity\SalesChannelDefinitionInstanceRegistry; 
use Shopware\Core\System\SalesChannel\Entity\SalesChannelRepositoryInterface; 
use Shopware\Core\System\SalesChannel\SalesChannelContext; 
use Shopware\Core\System\SalesChannel\StoreApiResponse; 
use Symfony\Component\EventDispatcher\EventSubscriberInterface; 
use Symfony\Component\HttpKernel\Event\ResponseEvent; 
use Symfony\Component\HttpKernel\KernelEvents; 
 
class StoreApiSeoResolver implements EventSubscriberInterface 
{ 
    /** 
     * @var SalesChannelRepositoryInterface 
     */ 
    private $salesChannelRepository; 
 
    /** 
     * @var DefinitionInstanceRegistry 
     */ 
    private $definitionInstanceRegistry; 
 
    /** 
     * @var SeoUrlRouteRegistry 
     */ 
    private $seoUrlRouteRegistry; 
 
    /** 
     * @var SalesChannelDefinitionInstanceRegistry 
     */ 
    private $salesChannelDefinitionInstanceRegistry; 
 
    /** 
     * @internal 
     */ 
    public function __construct( 
        SalesChannelRepositoryInterface $salesChannelRepository, 
        DefinitionInstanceRegistry $definitionInstanceRegistry, 
        SalesChannelDefinitionInstanceRegistry $salesChannelDefinitionInstanceRegistry, 
        SeoUrlRouteRegistry $seoUrlRouteRegistry 
    ) { 
        $this->salesChannelRepository = $salesChannelRepository; 
        $this->definitionInstanceRegistry = $definitionInstanceRegistry; 
        $this->seoUrlRouteRegistry = $seoUrlRouteRegistry; 
        $this->salesChannelDefinitionInstanceRegistry = $salesChannelDefinitionInstanceRegistry; 
    } 
 
    public static function getSubscribedEvents(): array 
    { 
        return [ 
            KernelEvents::RESPONSE => ['addSeoInformation', 10000], 
        ]; 
    } 
 
    public function addSeoInformation(ResponseEvent $event): void 
    { 
        $response = $event->getResponse(); 
 
        if (!$response instanceof StoreApiResponse) { 
            return; 
        } 
 
        if (!$event->getRequest()->headers->has(PlatformRequest::HEADER_INCLUDE_SEO_URLS)) { 
            return; 
        } 
 
        $dataBag = new SeoResolverData(); 
 
        $this->find($dataBag, $response->getObject()); 
        $this->enrich($dataBag, $event->getRequest()->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT)); 
    } 
 
    private function find(SeoResolverData $data, Struct $struct): void 
    { 
        if ($struct instanceof AggregationResultCollection) { 
            foreach ($struct as $item) { 
                $this->findStruct($data, $item); 
            } 
        } 
 
        if ($struct instanceof EntitySearchResult) { 
            foreach ($struct->getEntities() as $entity) { 
                $this->findStruct($data, $entity); 
            } 
        } 
 
        if ($struct instanceof Collection) { 
            foreach ($struct as $item) { 
                $this->findStruct($data, $item); 
            } 
        } 
 
        $this->findStruct($data, $struct); 
    } 
 
    private function findStruct(SeoResolverData $data, Struct $struct): void 
    { 
        if ($struct instanceof Entity) { 
            $definition = $this->definitionInstanceRegistry->getByEntityClass($struct) ?? $this->salesChannelDefinitionInstanceRegistry->getByEntityClass($struct); 
            if ($definition && $definition->isSeoAware()) { 
                $data->add($definition->getEntityName(), $struct); 
            } 
        } 
 
        foreach ($struct->getVars() as $item) { 
            if ($item instanceof Collection) { 
                foreach ($item as $collectionItem) { 
                    if ($collectionItem instanceof Struct) { 
                        $this->findStruct($data, $collectionItem); 
                    } 
                } 
            } elseif ($item instanceof Struct) { 
                $this->findStruct($data, $item); 
            } 
        } 
    } 
 
    private function enrich(SeoResolverData $data, SalesChannelContext $context): void 
    { 
        foreach ($data->getEntities() as $definition) { 
            $definition = (string) $definition; 
 
            $ids = $data->getIds($definition); 
            $routes = $this->seoUrlRouteRegistry->findByDefinition($definition); 
            if (\count($routes) === 0) { 
                continue; 
            } 
 
            $routes = array_map(static function (SeoUrlRouteConfigRoute $seoUrlRoute) { 
                return $seoUrlRoute->getConfig()->getRouteName(); 
            }, $routes); 
 
            $criteria = new Criteria(); 
            $criteria->addFilter(new EqualsFilter('isCanonical', true)); 
            $criteria->addFilter(new EqualsAnyFilter('routeName', $routes)); 
            $criteria->addFilter(new EqualsAnyFilter('foreignKey', $ids)); 
            $criteria->addFilter(new EqualsFilter('languageId', $context->getContext()->getLanguageId())); 
            $criteria->addSorting(new FieldSorting('salesChannelId')); 
 
            /** @var SeoUrlEntity $url */ 
            foreach ($this->salesChannelRepository->search($criteria, $context) as $url) { 
                /** @var SalesChannelProductEntity|CategoryEntity $entity */ 
                $entity = $data->get($definition, $url->getForeignKey()); 
 
                if ($entity->getSeoUrls() === null) { 
                    $entity->setSeoUrls(new SeoUrlCollection()); 
                } 
 
                /** @phpstan-ignore-next-line - will complain that 'getSeoUrls' might be null, but we will set it if it is null */ 
                $entity->getSeoUrls()->add($url); 
            } 
        } 
    } 
}