<?php
namespace App\Widget;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\UnexpectedResultException;
use MLDev\BaseBundle\Entity\Page;
use MLDev\BaseBundle\Paginator\Pagination\PaginationInterface;
use MLDev\BaseBundle\Paginator\Paginator;
use MLDev\BaseBundle\Repository\PageRepository;
use MLDev\BaseBundle\Widget\AbstractWidget;
use MLDev\CatalogBundle\Entity\Product;
use MLDev\CatalogBundle\Entity\ProductItem;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class CatalogWidget extends AbstractWidget
{
/**
* @var array
*/
private array $availableSortingRules = [
'relevance_asc' => 'По популярности',
'name_asc' => 'По наименованию',
'price_asc' => 'По возрастанию цены',
'price_desc' => 'По убыванию цены',
];
private array $optionNameList = [];
private array $optionLabelList = [];
public function execute(array $options): string
{
/**
* @var Page $page
*/
$page = $this->getContextValue('page');
$queryBuilder = $this->getProductItemQueryByPages(
$this->getChildPagesId($page),
$this->getSortingRules()
);
if (($prices = $this->getPricesFromQuery())) {
if ($prices['from_price_value'] && $prices['to_price_value']) {
$queryBuilder->andWhere(
$queryBuilder->expr()->between(
'Price.value',
$prices['from_price_value'],
$prices['to_price_value']
)
);
}
}
foreach ($this->optionNameList as $fieldName) {
if (($filterValue = $this->getRequest()->query->get($fieldName, [])) && !empty($filterValue)) {
$queryBuilder->andWhere(
$queryBuilder->expr()->in('ProductItem.' . $fieldName, $filterValue)
);
}
}
return $this->render('widget/catalog/default.html.twig', [
'page' => $page,
'product_item_list' => $queryBuilder->getQuery()->getResult(),
'available_sorting_rules' => $this->availableSortingRules,
'statistics_on_prices' => $this->getStatisticsOnPrices($page),
'statistics_on_options' => $this->getStatisticsOnOptions($page),
]);
}
public function getName(): string
{
return 'Список товаров';
}
public function configure(FormBuilderInterface $builder, array $options): FormBuilderInterface
{
$builder
->add('title', TextType::class, [
'label' => 'Заголовок',
'required' => false,
])
->add('is_show_children_category', CheckboxType::class, [
'label' => 'Показывать подкатегории',
'required' => false,
'label_attr' => ['class' => 'switch-custom'],
]);
return $builder;
}
private function getPricesFromQuery(): array
{
$prices = $this->getRequest()->query->get('prices', '');
if (($prices = explode(';', $prices)) && is_array($prices)) {
$prices = array_filter($prices);
} else {
$prices = [];
}
return array_combine(['from_price_value', 'to_price_value'], array_pad($prices, 2, null));
}
private function getStatisticsOnOptions($page): array
{
$statisticsOnOptions = [];
foreach ($this->optionNameList as $fieldName) {
$queryBuilder = $this->getProductItemQueryByPages(
$this->getChildPagesId($page),
$this->getSortingRules()
);
$queryBuilder->select('ProductItem.' . $fieldName)
->andWhere(
$queryBuilder->expr()->isNotNull('ProductItem.' . $fieldName)
)
->groupBy('ProductItem.' . $fieldName)
->orderBy('ProductItem.' . $fieldName, 'ASC');
$statisticsOnOptions[$fieldName] = $queryBuilder->getQuery()->getResult();
}
return $statisticsOnOptions;
}
private function getStatisticsOnPrices($page): array
{
$queryBuilder = $this->getProductItemQueryByPages(
$this->getChildPagesId($page),
$this->getSortingRules()
);
$queryBuilder->select('MIN(Price.value) as min_price_value, MAX(Price.value) as max_price_value');
try {
$statisticsOnPrices = $queryBuilder->getQuery()->getSingleResult();
} catch (UnexpectedResultException $exception) {
$statisticsOnPrices = [
'min_price_value' => null,
'max_price_value' => null,
];
}
return array_merge($statisticsOnPrices, $this->getPricesFromQuery());
}
/**
* @param mixed $queryBuilder
* @return PaginationInterface
*/
private function getPagination($queryBuilder): PaginationInterface
{
/** @var integer $pageLimit */
$pageLimit = $this->getRequest()->query->get('page', 1);
$paginator = new Paginator();
$pagination = $paginator->paginate(
$queryBuilder, $pageLimit, $this->getPageLimit(), ['wrap-queries' => true]
);
$pagination->renderer = function ($data) {
return $this->render('@MLDevCatalog/pagination.html.twig', $data);
};
return $pagination;
}
/**
* @param Page $entity
* @return array
*/
private function getChildPagesId(Page $entity): array
{
/**
* @var PageRepository $repository
*/
$repository = $this->getDoctrine()->getRepository(Page::class);
$children = $repository->getChildrenIds($entity);
$children[] = $entity->getId();
return $children;
}
/**
* @return mixed|string
*/
private function getViewMode(): mixed
{
if (($viewMode = $this->getRequest()->query->get('view'))) {
if (!in_array($viewMode, ['row', 'card'])) {
$viewMode = 'card';
}
if ($viewMode !== $this->getRequest()->getSession()->get('mldev-catalog-view')) {
$this->getRequest()->getSession()->set('mldev-catalog-view', $viewMode);
}
} else {
$viewMode = $this->getRequest()->getSession()->get('mldev-catalog-view', 'card');
}
return $viewMode;
}
/**
* @return mixed|string
*/
private function getSortingRules(): mixed
{
if (($sortingRule = $this->getRequest()->query->get('sorting'))) {
if (!in_array($sortingRule, array_keys($this->availableSortingRules))) {
$sortingRule = Product::ORDER_BY_PRIORITY;
}
if ($sortingRule !== $this->getRequest()->getSession()->get('mldev-catalog-sorting')) {
$this->getRequest()->getSession()->set('mldev-catalog-sorting', $sortingRule);
}
} else {
$sortingRule = $this->getRequest()->getSession()->get('mldev-catalog-sorting', 'relevance_asc');
}
return $sortingRule;
}
/**
* @return int
*/
private function getPageLimit(): int
{
if (($pageLimit = $this->getRequest()->query->get('limit'))) {
$pageLimit = $pageLimit > 48 ? 48 : $pageLimit;
if ($pageLimit !== $this->getRequest()->getSession()->get('mldev-catalog-page-limit')) {
$this->getRequest()->getSession()->set('mldev-catalog-page-limit', $pageLimit);
}
} else {
$pageLimit = $this->getRequest()->getSession()->get('mldev-catalog-page-limit', 12);
}
return $pageLimit;
}
/**
* @param array $pages
* @param string|null $orderBy
* @return QueryBuilder
*/
private function getProductItemQueryByPages(array $pages, ?string $orderBy = null): QueryBuilder
{
$predicates = sprintf('Page.id in (%s)', implode(',', $pages));
$qb = $this->getDoctrine()->createQueryBuilder();
$qb = $qb
->select(['ProductItem', 'Product'])
->from(ProductItem::class, 'ProductItem')
->join('ProductItem.product', 'Product')
->join('Product.pages', 'Page')
->leftJoin('ProductItem.prices', 'Price')
->where($predicates)
->andWhere('Product.isActive = true')
->andWhere('ProductItem.isActive = true');
if (true === (bool)$this->getRequest()->query->get('available', false)) {
$qb->andWhere(
$qb->expr()->gt('ProductItem.amount', 0)
);
}
if ($orderBy == 'price_asc') {
$qb->addOrderBy('Price.value', 'ASC');
}
if ($orderBy == 'price_desc') {
$qb->addOrderBy('Price.value', 'DESC');
}
if ($orderBy == 'name_asc') {
$qb->addOrderBy('ProductItem.name', 'ASC');
}
if ($orderBy == 'relevance_asc') {
$qb->addOrderBy('ProductItem.amount', 'DESC');
}
$qb->addOrderBy('Product.priority', 'DESC');
$qb->addOrderBy('Product.id', 'ASC');
return $qb;
}
}