<?php
declare(strict_types=1);
/**
* @copyright 2020 Crehler Sp. z o. o.
* @link https://crehler.com/
* @support support@crehler.com
*
* @author Mateusz FlasiĆski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Crehler\Catalog\Service\CatalogReader\Loader;
use Crehler\Catalog\Entity\Catalog\Aggregate\CatalogBookmarks\CatalogBookmarksEntity;
use Crehler\Catalog\Entity\Catalog\Aggregate\CatalogPage\CatalogPageEntity;
use Crehler\Catalog\Entity\Catalog\CatalogDefinition;
use Crehler\Catalog\Entity\Catalog\CatalogEntity;
use Crehler\Catalog\Service\CatalogReader\UrlGenerator;
use Crehler\Catalog\Service\Media\MediaLoaderInterface;
use Crehler\Catalog\Struct\CatalogDetail\CatalogDetailDotStruct;
use Crehler\Catalog\Struct\CatalogDetail\CatalogDetailPageStruct;
use Crehler\Catalog\Struct\CatalogDetail\CatalogDetailSeoStruct;
use Crehler\Catalog\Struct\CatalogDetail\CatalogDetailStruct;
use Crehler\Catalog\Struct\CatalogDetail\CatalogPageNavigationStruct;
use Crehler\Catalog\Struct\CatalogPageStruct;
use Doctrine\DBAL\Connection;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class CatalogPageLoader
{
private EntityRepositoryInterface $catalogRepository;
private EntityRepositoryInterface $catalogPageRepository;
private EntityRepositoryInterface $catalogBookmarkRepository;
private EntityRepositoryInterface $catalogProductDotRepository;
private Connection $connection;
private UrlGenerator $urlGenerator;
private MediaLoaderInterface $mediaLoaderService;
public function __construct(EntityRepositoryInterface $catalogRepository,
EntityRepositoryInterface $catalogPageRepository,
EntityRepositoryInterface $catalogBookmarkRepository,
Connection $connection,
EntityRepositoryInterface $catalogProductDotRepository,
UrlGenerator $urlGenerator,
MediaLoaderInterface $mediaLoaderService)
{
$this->catalogRepository = $catalogRepository;
$this->catalogPageRepository = $catalogPageRepository;
$this->catalogBookmarkRepository = $catalogBookmarkRepository;
$this->connection = $connection;
$this->catalogProductDotRepository = $catalogProductDotRepository;
$this->urlGenerator = $urlGenerator;
$this->mediaLoaderService = $mediaLoaderService;
}
public function load(Request $request, SalesChannelContext $context): CatalogPageStruct
{
$catalogId = $request->get('catalogId', null);
$pageId = $request->get('pageId', null);
$bookmarkId = $request->get('bookmarkId', null);
if ($catalogId === null && $pageId !== null) {
$catalogId = $this->getCatalogIdByPage($pageId, $context->getContext());
} elseif ($catalogId === null && $bookmarkId !== null) {
$bookmark = $this->getCatalogBookmark($bookmarkId, $context->getContext());
if ($bookmark !== null) {
$catalogId = $bookmark->getCatalogId();
$pageId = $bookmark->getCatalogPageId();
}
}
if ($catalogId === null) {
throw new NotFoundHttpException();
}
$criteria = new Criteria([$catalogId]);
$criteria->addAssociations(['pages', 'productBoxImageMedia']);
$criteria->addFilter(new EqualsFilter('status', CatalogDefinition::STATUS_PUBLISHED));
/** @var CatalogEntity $catalogEntity */
$catalogEntity = $this->catalogRepository->search($criteria, $context->getContext())->first();
if ($catalogEntity === null) {
throw new NotFoundHttpException();
}
$struct = new CatalogPageStruct();
$struct->setCatalog($catalogEntity);
if ($pageId !== null) {
$currentPage = $catalogEntity->getPages()->get($pageId);
} else {
$currentPage = $catalogEntity->getPages()->getByPageNumber(1);
}
$struct->setCatalogPageEntity($currentPage)
->setCatalogPages($catalogEntity->getPages());
$products = $this->getProducts($currentPage->getId());
$struct->setCatalogProductsIds($products);
return $struct;
}
public function getProducts(string $catalogPageId): array
{
$qb = $this->connection->createQueryBuilder();
$data = $qb->select('id as mappingId, product_id as productId')
->from('catalog_page_product')
->where('catalog_page_id = :page')
->setParameter('page', Uuid::fromHexToBytes($catalogPageId))
->orderBy('auto_increment', 'ASC')
->execute()
->fetchAll();
return array_map(function ($item) {
return [
'mappingId' => Uuid::fromBytesToHex($item['mappingId']),
'productId' => Uuid::fromBytesToHex($item['productId']),
];
}, $data);
}
public function getPageDetail(string $pageId, SalesChannelContext $salesChannelContext, int $displayType = 1): CatalogDetailStruct
{
$struct = new CatalogDetailStruct();
$struct->setRequestedId($pageId);
$firstPage = $this->loadDetailStruct($pageId, $salesChannelContext);
if ($displayType === 1) {
$struct->setLeft($firstPage);
$this->loadAdditionalDetailData($struct, $salesChannelContext);
return $struct;
} elseif ($firstPage->getPage()->getPageNumber() % 2 !== 0) { // Nieparzysta
$struct->setRight($firstPage);
$secondPageNumber = $firstPage->getPage()->getPageNumber() - 1;
$secondIsRight = false;
} else {
$struct->setLeft($firstPage);
$secondPageNumber = $firstPage->getPage()->getPageNumber() + 1;
$secondIsRight = true;
}
$criteria = new Criteria();
$criteria->addFilter(
new MultiFilter(
MultiFilter::CONNECTION_AND,
[
new EqualsFilter('pageNumber', $secondPageNumber),
new EqualsFilter('catalogId', $firstPage->getPage()->getCatalogId()),
]
)
);
$id = $this->catalogPageRepository->searchIds($criteria, $salesChannelContext->getContext())->firstId();
if ($id === null) {
$this->loadAdditionalDetailData($struct, $salesChannelContext);
return $struct;
}
$secondPage = $this->loadDetailStruct($id, $salesChannelContext);
if ($secondIsRight) {
$struct->setRight($secondPage);
} else {
$struct->setLeft($secondPage);
}
$this->loadAdditionalDetailData($struct, $salesChannelContext);
return $struct;
}
private function getCatalogIdByPage(string $pageId, Context $context): ?string
{
$criteria = new Criteria([$pageId]);
/** @var CatalogPageEntity $page */
$page = $this->catalogPageRepository->search($criteria, $context)->first();
return $page->getCatalogId();
}
private function getCatalogBookmark(string $bookmarkId, Context $context): ?CatalogBookmarksEntity
{
$criteria = new Criteria([$bookmarkId]);
return $this->catalogBookmarkRepository->search($criteria, $context)->first();
}
private function loadAdditionalDetailData(CatalogDetailStruct $catalogDetailStruct, SalesChannelContext $salesChannelContext)
{
$this->loadNextAndPrevPages($catalogDetailStruct, $salesChannelContext);
$this->loadSeoMeta($catalogDetailStruct, $salesChannelContext);
}
private function loadSeoMeta(CatalogDetailStruct $catalogDetailStruct, SalesChannelContext $salesChannelContext)
{
$page = null;
if ($catalogDetailStruct->getLeft() !== null && $catalogDetailStruct->getLeft()->getPage()->getId() === $catalogDetailStruct->getRequestedId()) {
$page = $catalogDetailStruct->getLeft();
} elseif ($catalogDetailStruct->getRight() !== null && $catalogDetailStruct->getRight()->getPage()->getId() === $catalogDetailStruct->getRequestedId()) {
$page = $catalogDetailStruct->getRight();
}
if ($page === null) {
return;
}
$struct = new CatalogDetailSeoStruct();
$url = $this->urlGenerator->generateDetailUrl($page->getPage(), $salesChannelContext);
$struct->setSeoDescription($page->getSeoDescription())
->setSeoTitle($page->getSeoTitle())
->setSeoThumbnail($page->getMediaEntity()->getUrl())
->setSeoUrl($url);
$catalogDetailStruct->setSeo($struct);
}
private function loadNextAndPrevPages(CatalogDetailStruct $catalogDetailStruct, SalesChannelContext $salesChannelContext)
{
if ($catalogDetailStruct->getLeft() !== null) {
$prevPageNumber = $catalogDetailStruct->getLeft()->getPage()->getPageNumber() - 1;
$catalogId = $catalogDetailStruct->getLeft()->getPage()->getCatalogId();
} else {
$prevPageNumber = $catalogDetailStruct->getRight()->getPage()->getPageNumber() - 1;
$catalogId = $catalogDetailStruct->getRight()->getPage()->getCatalogId();
}
if ($catalogDetailStruct->getRight() !== null) {
$nextPageNumber = $catalogDetailStruct->getRight()->getPage()->getPageNumber() + 1;
} else {
$nextPageNumber = $catalogDetailStruct->getLeft()->getPage()->getPageNumber() + 1;
}
$catalogDetailStruct->setPrev($this->findPage($prevPageNumber, $catalogId, $salesChannelContext));
$catalogDetailStruct->setNext($this->findPage($nextPageNumber, $catalogId, $salesChannelContext));
}
private function findPage(int $pageNumber, string $catalogId, SalesChannelContext $salesChannelContext): CatalogPageNavigationStruct
{
$struct = new CatalogPageNavigationStruct();
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('catalogId', $catalogId));
$criteria->addFilter(new EqualsFilter('pageNumber', $pageNumber));
$page = $this->catalogPageRepository->search($criteria, $salesChannelContext->getContext())->first();
if ($page === null) {
return $struct;
}
$url = $this->urlGenerator->generateDetailUrl($page, $salesChannelContext);
$struct->setUrl($url)
->setId($page->getId())
->setAvailable(true);
return $struct;
}
private function loadDetailStruct(string $pageId, SalesChannelContext $salesChannelContext): CatalogDetailPageStruct
{
// $timer = Timer::start('loadDetailStruct');
// $partTimer = Timer::start('loadDetailStructPart');
$times = [];
$struct = new CatalogDetailPageStruct();
// $times['structCreate'] = $partTimer->stop()['duration'];
// $partTimer = Timer::start('loadDetailStructPart');
$criteria = new Criteria([$pageId]);
$criteria->addAssociations(['media', 'pdf', 'catalog', 'bookmarks']);
// $times['criteriaCreate'] = $partTimer->stop()['duration'];
// $partTimer = Timer::start('loadDetailStructPart');
/** @var CatalogPageEntity $page */
$page = $this->catalogPageRepository->search($criteria, $salesChannelContext->getContext())->first();
// $times['pageSearch'] = $partTimer->stop()['duration'];
// $partTimer = Timer::start('loadDetailStructPart');
$catalog = $page->getCatalog();
$bookmark = $page->getBookmarks()->first();
if ($page->getSeoTitle() !== null) {
$struct->setSeoTitle($page->getSeoTitle());
} elseif ($bookmark !== null && $bookmark['seoTitle'] !== null) {
$struct->setSeoTitle($bookmark['seoTitle']);
} elseif ($catalog->getSeoTitle() !== null) {
$struct->setSeoTitle($catalog->getSeoTitle());
}
if ($page->getSeoDescription() !== null) {
$struct->setSeoDescription($page->getSeoDescription());
} elseif ($bookmark !== null && $bookmark['seoDescription'] !== null) {
$struct->setSeoDescription($bookmark['seoDescription']);
} elseif ($catalog->getSeoDescription() !== null) {
$struct->setSeoDescription($catalog->getSeoDescription());
}
// $times['seo'] = $partTimer->stop()['duration'];
// $partTimer = Timer::start('loadDetailStructPart');
$dotsCriteria = new Criteria();
$dotsCriteria->addFilter(
new MultiFilter(
MultiFilter::CONNECTION_AND,
[
new EqualsFilter('catalogPageId', $pageId),
new EqualsFilter('enabled', true),
]
)
);
// $times['dotsCriteria'] = $partTimer->stop()['duration'];
// $partTimer = Timer::start('loadDetailStructPart');
$dots = $this->catalogProductDotRepository->search($dotsCriteria, $salesChannelContext->getContext());
// $times['dotsSearch'] = $partTimer->stop()['duration'];
// $partTimer = Timer::start('loadDetailStructPart');
foreach ($dots->getElements() as $dot) {
$dotStruct = new CatalogDetailDotStruct();
$dotStruct->setX($dot['xPosition'])
->setY($dot['yPosition'])
->setProductId($dot['catalogProductId'])
->setRadius($dot['radius'])
->setHideOnMobile($dot['hideOnMobile']);
$struct->getDot()->append($dotStruct);
}
// $times['dotsStruct'] = $partTimer->stop()['duration'];
// $partTimer = Timer::start('loadDetailStructPart');
$struct->setMediaEntity($page->getMedia())
->setPdf($page->getPdf()->getUrl())
->setPage($page);
// $times['mediaPdfAndPage'] = $partTimer->stop()['duration'];
// $partTimer = Timer::start('loadDetailStructPart');
$mediaCollection = $this->mediaLoaderService->loadMediaStruct($page->getMedia());
// $times['mediaCollection'] = $partTimer->stop()['duration'];
// $partTimer = Timer::start('loadDetailStructPart');
$struct->setMedia($mediaCollection);
// $times['all'] = $timer->stop()['duration'];
//
// $struct->setTimer($times);
return $struct;
}
}