<?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);
}
}
}
}