<?php declare(strict_types=1);
namespace Acris\Faq\Storefront\Subscriber;
use Acris\Faq\Components\Faq\FaqCmsPageService;
use Acris\Faq\Components\Faq\FaqGateway;
use Acris\Faq\Components\Faq\FaqService;
use Acris\Faq\Core\Content\Cms\SalesChannel\Struct\FaqSingleStruct;
use Acris\Faq\Core\Content\Cms\SalesChannel\Struct\FaqStruct;
use Acris\Faq\Core\Framework\DataAbstractionLayer\Cache\FaqEntityCacheKeyGenerator;
use Acris\Faq\Custom\FaqDefinition;
use Acris\Faq\Custom\FaqEntity;
use Acris\Faq\Custom\FaqGroupCollection;
use Acris\Faq\Custom\FaqGroupEntity;
use Shopware\Core\Content\Category\Event\CategoryRouteCacheTagsEvent;
use Shopware\Core\Content\Category\SalesChannel\CategoryRouteResponse;
use Shopware\Core\Content\Cms\Aggregate\CmsSlot\CmsSlotEntity;
use Shopware\Core\Content\Cms\CmsPageCollection;
use Shopware\Core\Content\Cms\CmsPageEntity;
use Shopware\Core\Content\Cms\DataResolver\ResolverContext\EntityResolverContext;
use Shopware\Core\Content\Cms\SalesChannel\SalesChannelCmsPageLoaderInterface;
use Shopware\Core\Content\Product\SalesChannel\Detail\CachedProductDetailRoute;
use Shopware\Core\Framework\Adapter\Cache\CacheInvalidator;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
use Shopware\Core\Framework\Struct\ArrayEntity;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Storefront\Page\Product\ProductPageLoadedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
class ProductLoadedSubscriber implements EventSubscriberInterface
{
public const ACRIS_STREAM_IDS_EXTENSION = 'acrisStreamIds';
/**
* @var FaqService
*/
private FaqService $faqService;
/**
* @var FaqGateway
*/
private FaqGateway $faqGateway;
/**
* @var CacheInvalidator
*/
private CacheInvalidator $logger;
private FaqCmsPageService $faqCmsPageService;
public function __construct(
FaqService $faqService,
FaqGateway $faqGateway,
CacheInvalidator $logger,
FaqCmsPageService $faqCmsPageService
)
{
$this->faqService = $faqService;
$this->faqGateway = $faqGateway;
$this->logger = $logger;
$this->faqCmsPageService = $faqCmsPageService;
}
public static function getSubscribedEvents()
{
return [
ProductPageLoadedEvent::class => [
['productLoaded', 200]
],
'acris_faq.written' => [
['onFaqWritten', 200]
],
'acris_faq_group.written' => [
['onFaqGroupWritten', 200]
],
CategoryRouteCacheTagsEvent::class => [
['onCategoryRouteCacheTags', 200]
]
];
}
public function productLoaded(ProductPageLoadedEvent $event): void
{
$request = $event->getRequest();
if(empty($request) === true) {
return;
}
if (empty($event->getPage()) || empty($event->getPage()->getProduct()) || empty($event->getContext()->getLanguageId())) return;
$languageId = $event->getContext()->getLanguageId();
$product = $event->getPage()->getProduct();
if ($product->hasExtension(self::ACRIS_STREAM_IDS_EXTENSION) && !empty($product->getExtension(self::ACRIS_STREAM_IDS_EXTENSION))) {
$productStreamIds = $product->getExtension(self::ACRIS_STREAM_IDS_EXTENSION)->get('ids');
} else {
$productStreamIds = $this->faqGateway->getProductStreamIds($product->getId(), $event->getSalesChannelContext()->getContext());
$product->addExtension(self::ACRIS_STREAM_IDS_EXTENSION, new ArrayEntity(['ids' => $productStreamIds]));
}
if (empty($productStreamIds)) return;
$faqGroupResult = $this->faqGateway->getProductDetailFaqGroup($productStreamIds, $event->getContext());
if ($faqGroupResult->count() === 0) {
return;
}
$faqGroupCollection = $faqGroupResult->getEntities();
$faqGroups = $this->faqService->checkLanguage($faqGroupCollection->getElements(), $languageId);
$this->assignFaqsVideos($faqGroupCollection, $event->getSalesChannelContext());
$this->assignFaqsCmsPage($request, $faqGroupCollection, $event->getSalesChannelContext());
$this->faqService->assignPreviewImage($faqGroupCollection, $event->getSalesChannelContext());
$product->addExtension('acrisFaq', $faqGroupCollection);
}
public function onFaqWritten(EntityWrittenEvent $event): void
{
$results = $event->getWriteResults();
$faqIds = [];
foreach ($results as $result) {
$payload = $result->getPayload();
if (!empty($payload) && array_key_exists('id', $payload) && !empty($payload['id'])) {
$faqIds[] = $payload['id'];
}
}
if (!empty($faqIds)) {
$this->clearCacheForFaq($faqIds, $event->getContext());
$this->upsertMetaDataWithFaqIds($faqIds, $event->getContext());
}
}
public function onFaqGroupWritten(EntityWrittenEvent $event): void
{
$results = $event->getWriteResults();
$faqIds = [];
$faqGroupIds = [];
foreach ($results as $result) {
$payload = $result->getPayload();
if (!empty($payload) && array_key_exists('id', $payload) && !empty($payload['id'])) {
$faqGroupIds[] = $payload['id'];
}
}
if (!empty($faqGroupIds)) {
$faqSearchResult = $this->faqService->getFaqGroups($faqGroupIds, $event->getContext());
if ($faqSearchResult->count() === 0) return;
$faqIds = $this->clearCacheForFaqGroup($faqSearchResult->getEntities(), $event->getContext());
}
if (!empty($faqIds)) {
// invalidates all routes which loads faqs
$this->logger->invalidate(
array_map([FaqEntityCacheKeyGenerator::class, 'buildFaqTag'], $faqIds)
);
}
}
public function onCategoryRouteCacheTags(CategoryRouteCacheTagsEvent $event): void
{
$event->addTags(
$this->extractFaqIds($event->getResponse())
);
}
public function extractFaqIds(CategoryRouteResponse $response): array
{
$page = $response->getCategory()->getCmsPage();
if ($page === null) {
return [];
}
$ids = [];
$slots = $page->getElementsOfType('acris-faq');
/** @var CmsSlotEntity $slot */
foreach ($slots as $slot) {
$faqStruct = $slot->getData();
if (!$faqStruct instanceof FaqStruct) {
continue;
}
if ($faqStruct->getFaq()->count() === 0) {
continue;
}
foreach ($faqStruct->getFaq()->getElements() as $faqGroup) {
if ($faqGroup->getFaqs()->count() === 0) continue;
foreach ($faqGroup->getFaqs()->getElements() as $faq) {
$ids[] = $faq->getId();
}
}
}
$slots = $page->getElementsOfType('acris-faq-single');
/** @var CmsSlotEntity $slot */
foreach ($slots as $slot) {
$faqSingleStruct = $slot->getData();
if (!$faqSingleStruct instanceof FaqSingleStruct) {
continue;
}
if ($faqSingleStruct->getFaqSingle()->count() === 0) {
continue;
}
foreach ($faqSingleStruct->getFaqSingle()->getElements() as $faq) {
$ids[] = $faq->getId();
}
}
$ids = array_values(array_unique(array_filter($ids)));
return array_merge(
array_map([FaqEntityCacheKeyGenerator::class, 'buildFaqTag'], $ids),
);
}
private function assignFaqsVideos(FaqGroupCollection $faqGroupCollection, SalesChannelContext $context): void
{
if ($faqGroupCollection->count() === 0) return;
/** @var FaqGroupEntity $faqGroup */
foreach ($faqGroupCollection->getElements() as $faqGroup) {
if (empty($faqGroup->getFaqs()) || $faqGroup->getFaqs()->count() === 0) continue;
foreach ($faqGroup->getFaqs()->getElements() as $faq) {
if (!empty($faq->getTranslation('embedCode')) && $faq->getVideoType() === FaqService::DEFAULT_FAQ_VIDEO_YOUTUBE_TYPE) {
$this->faqService->loadFaqVideo($faq, $context);
}
}
}
}
private function assignFaqsCmsPage(Request $request, FaqGroupCollection $faqGroupCollection, SalesChannelContext $context): void
{
if ($faqGroupCollection->count() === 0) return;
/** @var FaqGroupEntity $faqGroup */
foreach ($faqGroupCollection->getElements() as $faqGroup) {
if (empty($faqGroup->getFaqs()) || $faqGroup->getFaqs()->count() === 0) continue;
foreach ($faqGroup->getFaqs()->getElements() as $faq) {
if (!empty($faq->getLayout()) && $faq->getLayout() === 'cms') {
$cmsPages = $this->faqCmsPageService->getCmsPages($request, $context, $faq);
$faq->setCmsPage($cmsPages->first());
}
}
}
}
private function clearCacheForFaq(array $faqIds, Context $context): void
{
// invalidates all routes which loads faqs
$this->logger->invalidate(
array_map([FaqEntityCacheKeyGenerator::class, 'buildFaqTag'], $faqIds)
);
$faqSearchResult = $this->faqService->getFaqs($faqIds, $context);
if ($faqSearchResult->count() === 0) return;
$productIds = [];
$faqGroupIds = [];
/** @var FaqEntity $faq */
foreach ($faqSearchResult->getEntities()->getElements() as $faq) {
if (empty($faq->getGroups()) || $faq->getGroups()->count() === 0) continue;
foreach ($faq->getGroups()->getElements() as $faqGroup) {
if (empty($faqGroup->getProductStreams()) || $faqGroup->getProductStreams()->count() === 0 || in_array($faqGroup->getId(), $faqGroupIds)) continue;
$faqGroupIds[] = $faqGroup->getId();
$productStreamIds = [];
foreach ($faqGroup->getProductStreams()->getElements() as $productStream) {
$productStreamIds[] = $productStream->getId();
}
if (!empty($productStreamIds)) {
$productIds = array_unique(array_merge($productIds, $this->faqService->getProductIdsFromProductStreams($productStreamIds, $context)));
}
}
}
if (!empty($productIds)) {
$this->logger->invalidate(
array_map([CachedProductDetailRoute::class, 'buildName'], $productIds)
);
}
}
private function clearCacheForFaqGroup(FaqGroupCollection $faqGroupCollection, Context $context): array
{
$faqIds = [];
$productIds = [];
foreach ($faqGroupCollection->getElements() as $faqGroup) {
if (!empty($faqGroup->getFaqs()) && $faqGroup->getFaqs()->count() > 0) {
foreach ($faqGroup->getFaqs()->getElements() as $faq) {
if (in_array($faq->getId(), $faqIds)) continue;
$faqIds[] = $faq->getId();
}
}
if (empty($faqGroup->getProductStreams()) || $faqGroup->getProductStreams()->count() === 0) continue;
$productStreamIds = [];
foreach ($faqGroup->getProductStreams()->getElements() as $productStream) {
$productStreamIds[] = $productStream->getId();
}
if (!empty($productStreamIds)) {
$productIds = array_unique(array_merge($productIds, $this->faqService->getProductIdsFromProductStreams($productStreamIds, $context)));
}
}
if (!empty($productIds)) {
$this->logger->invalidate(
array_map([CachedProductDetailRoute::class, 'buildName'], $productIds)
);
}
return $faqIds;
}
private function upsertMetaDataWithFaqIds(array $faqIds, Context $context): void
{
$criteria = $this->faqService->loadCriteria(new Criteria([$faqIds]));
$this->faqService->upsertYoutubeMetaDataWithFaqIds($criteria, $context);
}
}