345 lines
12 KiB
PHP
345 lines
12 KiB
PHP
<?php
|
|
|
|
namespace KupShop\CatalogBundle\Util;
|
|
|
|
use Doctrine\DBAL\Connection;
|
|
use KupShop\CatalogBundle\Section\SectionTree;
|
|
use KupShop\ContentBundle\Util\SliderUtil;
|
|
use KupShop\I18nBundle\Translations\SectionsTranslation;
|
|
use KupShop\KupShopBundle\Context\LanguageContext;
|
|
use KupShop\KupShopBundle\Exception\PermanentRedirectException;
|
|
use KupShop\KupShopBundle\Util\StringUtil;
|
|
use Query\Filter;
|
|
use Query\Operator;
|
|
use Query\QueryBuilder;
|
|
use Query\Translation;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Contracts\Service\Attribute\Required;
|
|
|
|
class SectionUtil
|
|
{
|
|
private $languageContext;
|
|
|
|
private $sectionsTranslation;
|
|
|
|
private $productsFilterSpecs;
|
|
|
|
private ?SliderUtil $sliderUtil;
|
|
|
|
private ?SectionTree $sectionTree;
|
|
|
|
public function __construct(LanguageContext $languageContext, ?SectionsTranslation $sectionsTranslation = null)
|
|
{
|
|
$this->languageContext = $languageContext;
|
|
$this->sectionsTranslation = $sectionsTranslation;
|
|
}
|
|
|
|
#[Required]
|
|
final public function setSliderUtil(SliderUtil $sliderUtil): void
|
|
{
|
|
$this->sliderUtil = $sliderUtil;
|
|
}
|
|
|
|
#[Required]
|
|
final public function setSectionTree(SectionTree $sectionTree): void
|
|
{
|
|
$this->sectionTree = $sectionTree;
|
|
}
|
|
|
|
/**
|
|
* @return $this
|
|
*
|
|
* @required
|
|
*/
|
|
public function setProductsFilterSpecs(ProductsFilterSpecs $productsFilterSpecs)
|
|
{
|
|
$this->productsFilterSpecs = $productsFilterSpecs;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function fetchSlidersIntoSectionsCache(?array $ids = null): void
|
|
{
|
|
$ids = $this->getDescendantCategories($ids);
|
|
|
|
$sliders = $this->sliderUtil->fetchSliders(
|
|
function (QueryBuilder $qb) use ($ids) {
|
|
$sectionPositions = sqlQueryBuilder()
|
|
->select('DISTINCT sis.id_slider', 'sis.position', 'sis.id_section')
|
|
->from('sliders_in_sections', 'sis')
|
|
->andWhere(Operator::inIntArray($ids, 'sis.id_section'));
|
|
|
|
$qb->addSelect('JSON_OBJECT("id_section", sis.id_section, "position", sis.position) as slider_key')
|
|
->joinSubQuery('sl', $sectionPositions, 'sis', 'sl.id = sis.id_slider');
|
|
},
|
|
'slider_key'
|
|
);
|
|
|
|
$data = [];
|
|
foreach ($sliders as $key => $slider) {
|
|
$info = json_decode($key, true);
|
|
$data[$info['id_section']][$info['position']] = $slider;
|
|
}
|
|
|
|
foreach ($data as $sectionId => $sliders) {
|
|
$this->sectionTree->getSectionById($sectionId)?->setSliders($sliders);
|
|
}
|
|
}
|
|
|
|
public function generateVirtualSections(?array $ids = null)
|
|
{
|
|
$virtualSections = sqlQueryBuilder()->select('*')
|
|
->from('sections')
|
|
->where(Operator::equals(['virtual' => 'Y']));
|
|
|
|
if ($ids) {
|
|
$virtualSections->andWhere(Operator::inIntArray($ids, 'id'));
|
|
}
|
|
|
|
try {
|
|
$nonVirtualSectionsIds = sqlQueryBuilder()->select('id')
|
|
->from('sections')
|
|
->where(Operator::equals(['virtual' => 'N']));
|
|
|
|
sqlQueryBuilder()->delete('products_in_sections')
|
|
->where(Operator::equals(['generated' => 1]))
|
|
->andWhere(Operator::inSubQuery('id_section', $nonVirtualSectionsIds))
|
|
->execute();
|
|
} catch (\Exception $e) {
|
|
getRaven()->captureException($e);
|
|
}
|
|
|
|
$dbcfg = \Settings::getDefault();
|
|
|
|
foreach ($virtualSections->execute() as $section) {
|
|
$data = json_decode($section['data'], true) ?? [];
|
|
$filter = $data['virtual_settings'] ?? [];
|
|
|
|
try {
|
|
sqlGetConnection()->transactional(function () use ($section, $filter, $dbcfg) {
|
|
$existingProducts = sqlQueryBuilder()->select('old_p.id_product id')->from('products_in_sections', 'old_p')
|
|
->where(Operator::equals(['old_p.id_section' => $section['id'], 'old_p.generated' => 1]));
|
|
|
|
$newProducts = sqlQueryBuilder()->select('p.id')
|
|
->from('products', 'p')
|
|
->leftJoin('p', 'products_in_sections', 'ps_filter', 'p.id = ps_filter.id_product AND ps_filter.id_section = :id_section AND ps_filter.generated = 0')
|
|
->setParameter('id_section', $section['id'])
|
|
->where('ps_filter.id_product IS NULL') // odfiltrovat produkty, ktere v te sekci uz zarazeny jsou
|
|
->andWhere($this->productsFilterSpecs->getSpecs($filter))
|
|
->groupBy('p.id');
|
|
$newProducts->andWhere(Filter::hasCategory($dbcfg->prod_show_not_in_category == 'N', false));
|
|
|
|
$deleteProductsIds = (clone $existingProducts)
|
|
->andWhere(Operator::not(Operator::inSubQuery('old_p.id_product', $newProducts)))
|
|
->execute()->fetchFirstColumn();
|
|
|
|
if (!empty($deleteProductsIds)) {
|
|
sqlQueryBuilder()->delete('products_in_sections')
|
|
->where(Operator::equals(['id_section' => $section['id'], 'generated' => 1]))
|
|
->andWhere(Operator::inIntArray($deleteProductsIds, 'id_product'))
|
|
->execute();
|
|
}
|
|
|
|
$addProductsIds = $newProducts
|
|
->select('p.id')
|
|
->andWhere(Operator::not(Operator::inSubQuery('p.id', $existingProducts)))
|
|
->execute()->fetchFirstColumn();
|
|
|
|
if (!empty($addProductsIds)) {
|
|
$insertQuery = sqlQueryBuilder()->insert('products_in_sections');
|
|
foreach ($addProductsIds as $id) {
|
|
$insertQuery->multiDirectValues([
|
|
'id_product' => $id,
|
|
'id_section' => $section['id'],
|
|
'generated' => 1,
|
|
]);
|
|
}
|
|
|
|
$insertQuery->execute();
|
|
}
|
|
});
|
|
} catch (\Exception $e) {
|
|
getRaven()->captureException($e);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function matchSectionPath($sectionPath)
|
|
{
|
|
return sqlQueryBuilder()->select('s.id, s.url')
|
|
->from('sections', 's')
|
|
->andWhere(
|
|
Translation::joinTranslatedFields(
|
|
SectionsTranslation::class,
|
|
function (QueryBuilder $qb, $columnName, $translatedField) use ($sectionPath) {
|
|
if ($columnName == 'url') {
|
|
$qb->andWhere(Operator::equals([Operator::coalesce($translatedField, 's.url') => $sectionPath]));
|
|
|
|
return true;
|
|
}
|
|
},
|
|
['url']
|
|
)
|
|
)
|
|
->execute()->fetch();
|
|
}
|
|
|
|
/**
|
|
* @return array|bool
|
|
*
|
|
* @throws PermanentRedirectException
|
|
*/
|
|
public function resolve($uri)
|
|
{
|
|
$originalUri = strtok($uri, '?');
|
|
$partToTest = trim($originalUri, '/');
|
|
|
|
$match = $this->matchSectionPath($partToTest);
|
|
|
|
if ($match) {
|
|
if (StringUtil::startsWith(ltrim($originalUri, '/'), $match['url'])) {
|
|
$parameters = str_replace($match['url'].'/', '', trim($originalUri, '/').'/');
|
|
|
|
return [
|
|
'id' => $match['id'],
|
|
'parameters' => empty($parameters) ? null : $parameters,
|
|
];
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function getCustomUrls(array $languages, int $sectionID): array
|
|
{
|
|
if (!findModule(\Modules::PRODUCTS_SECTIONS, 'custom_url')) {
|
|
return [];
|
|
}
|
|
|
|
$translationAlias = $this->sectionsTranslation->getTableAlias();
|
|
$urls = sqlQueryBuilder()->select('s.id, s.url')->from('sections', 's')
|
|
->where(Operator::equals(['s.id' => $sectionID]))
|
|
->andWhere(
|
|
Translation::joinTranslations(
|
|
$languages,
|
|
$this->sectionsTranslation,
|
|
function (QueryBuilder $qb, $columnName, $translatedColumn, $langID) use ($translationAlias) {
|
|
if ($columnName == 'url') {
|
|
$qb->addSelect("COALESCE({$translatedColumn}, {$translationAlias}.{$columnName}) as url_{$langID}");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
)
|
|
)
|
|
->execute()->fetch();
|
|
|
|
$return = [];
|
|
foreach ($languages as $language) {
|
|
if (isset($urls['url_'.$language])) {
|
|
$return[$language] = '/'.trim($urls['url_'.$language], '/').'/';
|
|
}
|
|
}
|
|
|
|
return $return;
|
|
}
|
|
|
|
public function removeFilterFromSectionUrl(Request $request, string $url): string
|
|
{
|
|
// odetekuju lomitko na konci URL, abych zpatky vracel stejnou URL
|
|
$isSlashAtTheEnd = StringUtil::endsWith($url, '/');
|
|
|
|
if ($parameters = $request->attributes->get('parameters')) {
|
|
$url = substr(rtrim($url, '/'), 0, -strlen(rtrim($parameters, '/')));
|
|
}
|
|
|
|
$parts = explode('/f/', $url);
|
|
|
|
return rtrim(reset($parts), '/').($isSlashAtTheEnd ? '/' : '');
|
|
}
|
|
|
|
public function getDescendantCategories($id_topsections, bool $onlyVisible = true): array
|
|
{
|
|
$visibilityCondition = static function (string $alias) use ($onlyVisible) {
|
|
return $onlyVisible ? 'AND '.$alias.'.figure != "N"' : '';
|
|
};
|
|
|
|
$sections = (array) $id_topsections;
|
|
$sections = array_merge($sections, sqlFetchAll(sqlQuery('WITH RECURSIVE cte (level, id, position) AS (
|
|
SELECT 0, SR1.id_section, SR1.position
|
|
FROM sections_relation SR1
|
|
INNER JOIN sections AS s ON s.id = SR1.id_section '.$visibilityCondition('s').'
|
|
'.($id_topsections !== null ? 'WHERE SR1.id_topsection IN (:id_topsections)' : '').'
|
|
UNION ALL
|
|
SELECT cte.level + 1, SR2.id_section, SR2.position
|
|
FROM sections_relation SR2
|
|
INNER JOIN sections AS s2 ON s2.id = SR2.id_section '.$visibilityCondition('s2').'
|
|
INNER JOIN cte ON (cte.id = SR2.id_topsection)
|
|
WHERE cte.level < 50
|
|
)
|
|
SELECT id
|
|
FROM cte ORDER BY level, position', ['id_topsections' => $sections], ['id_topsections' => Connection::PARAM_INT_ARRAY]), ['id' => 'id']));
|
|
|
|
return $sections;
|
|
}
|
|
|
|
public static function recurseDeleteSections(&$sections, $sectionIds)
|
|
{
|
|
$found = false;
|
|
|
|
foreach ($sections as $id => &$section) {
|
|
$foundLocal = isset($sectionIds[$section->getId()]);
|
|
|
|
if ($section['submenu']) {
|
|
$foundLocal |= self::recurseDeleteSections($section['submenu'], $sectionIds);
|
|
}
|
|
|
|
if (!$foundLocal && !($section->getRedirectUrl() || $section->isVirtual())) {
|
|
unset($sections[$id]);
|
|
continue;
|
|
}
|
|
|
|
$found |= $foundLocal;
|
|
}
|
|
|
|
return $found;
|
|
}
|
|
|
|
public function getBadges(?string $figure = 'Y', ?string $virtual = 'N', ?string $showInSearch = 'Y'): array
|
|
{
|
|
$result = [];
|
|
|
|
if ($figure === 'N') {
|
|
$result[] = [
|
|
'translate_key' => 'figureN',
|
|
'class' => 'badge-pastel-default',
|
|
'icon' => 'eye-slash-fill',
|
|
];
|
|
} elseif ($figure === 'O') {
|
|
$result[] = [
|
|
'translate_key' => 'figureO',
|
|
'class' => 'badge-pastel-default',
|
|
'icon' => 'clipboard-x',
|
|
];
|
|
}
|
|
|
|
if ($virtual === 'Y') {
|
|
$result[] = [
|
|
'translate_key' => 'virtualY',
|
|
'class' => 'badge-pastel-default',
|
|
'icon' => 'gear-wide-connected',
|
|
];
|
|
}
|
|
|
|
if ($showInSearch === 'N') {
|
|
$result[] = [
|
|
'translate_key' => 'showInSearchN',
|
|
'class' => 'badge-pastel-default',
|
|
'icon' => 'incognito',
|
|
];
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
}
|