's.id', 'fields' => [ 'Popis' => ['field' => 'descr', 'render' => 'renderHTML', 'class' => 'alignLeft', 'size' => 3], 'visibility' => ['translate' => true, 'field' => 'figure', 'render' => 'renderVisibility', 'visible' => 'N', 'fieldType' => SectionsList::TYPE_LIST], 'Feed H/G/S/GL' => ['field' => 'join_feed_heureka', 'render' => 'getFeeds', 'class' => 'columnFeeds', 'size' => 2], 'priority' => ['translate' => true, 'field' => 'priority', 'render' => 'renderPriority', 'size' => 1, 'visible' => 'N', 'fieldType' => SectionsList::TYPE_LIST], 'Heureka' => ['field' => 'join_feed_heureka', 'render' => 'renderBoolean', 'visible' => 'N', 'fieldType' => SectionsList::TYPE_LIST_AUTOCOMPLETE, 'fieldOptions' => ['autocomplete' => 'feed_heureka', 'table' => 'kupshop_shared.feed_heureka', 'field' => "CONCAT(name, ' - ' ,category_text)"]], 'Heureka SK' => ['field' => 'join_feed_heureka_sk', 'render' => 'renderBoolean', 'visible' => 'N', 'fieldType' => SectionsList::TYPE_LIST_AUTOCOMPLETE, 'fieldOptions' => ['autocomplete' => 'feed_heureka_sk', 'table' => 'kupshop_shared.feed_heureka_sk', 'field' => "CONCAT(name, ' - ' ,category_text)"]], 'Google' => ['field' => 'join_feed_google', 'render' => 'renderBoolean', 'visible' => 'N', 'fieldType' => SectionsList::TYPE_LIST_AUTOCOMPLETE, 'fieldOptions' => ['autocomplete' => 'feed_google', 'table' => 'kupshop_shared.feed_google', 'field' => "CONCAT(name, ' - ' ,category_text)"]], 'Seznam' => ['field' => 'join_feed_seznam', 'render' => 'renderBoolean', 'visible' => 'N', 'fieldType' => SectionsList::TYPE_LIST_AUTOCOMPLETE, 'fieldOptions' => ['autocomplete' => 'feed_seznam', 'table' => 'kupshop_shared.feed_seznam', 'field' => "CONCAT(name, ' - ' ,category_text)"]], 'Glami' => ['field' => 'join_feed_glami', 'render' => 'renderBoolean', 'visible' => 'N', 'fieldType' => SectionsList::TYPE_LIST_AUTOCOMPLETE, 'fieldOptions' => ['autocomplete' => 'feed_glami', 'table' => 'kupshop_shared.feed_glami', 'field' => "CONCAT(name, ' - ' ,category_text)"]], 'orderby' => ['translate' => true, 'field' => 'orderby', 'render' => 'renderOrderBy', 'visible' => 'N', 'size' => 1.5, 'fieldType' => SectionsList::TYPE_LIST], 'orderdir' => ['translate' => true, 'field' => 'orderdir', 'render' => 'renderOrderDir', 'visible' => 'N', 'size' => 1.5, 'fieldType' => SectionsList::TYPE_LIST], 'Příznak' => ['field' => 'flags', 'render' => 'renderFlags', 'class' => 'columnCampaigns', 'size' => 1], 'Podsekce' => ['field' => 'subsection', 'render' => 'getSubsection', 'class' => 'alignRight hidden-label overflow-visible columnBtns', 'size' => 1], 'redirect_url' => ['translate' => true, 'field' => 'redirect_url', 'visible' => 'N', 'fieldType' => SectionsList::TYPE_STRING, 'size' => 1], ], 'class' => null, ]; protected $showExport = false; private $openedSections; protected $additionalData; protected array $sections = []; private ?array $slidersCache = null; protected function getSectionUtil(): SectionUtil { static $util = null; if (!$util) { $util = ServiceContainer::getService(SectionUtil::class); } return $util; } protected function getSliderUtil(): ?KupShop\ContentBundle\Util\SliderUtil { static $sliderUtil = null; return $sliderUtil ??= ( findModule(\Modules::SLIDERS) ? ServiceContainer::getService(\KupShop\ContentBundle\Util\SliderUtil::class) : null ); } public function handleGenerateVirtualSections() { $sectionUtil = $this->getSectionUtil(); $sectionUtil->generateVirtualSections(); $this->returnOK('Přegenerováno'); } public function customizeTableDef($tableDef) { ini_set('memory_limit', '256M'); $tableDef = parent::customizeTableDef($tableDef); $cfg = Config::get(); if (findModule(\Modules::TRANSLATIONS)) { $tableDef['fields']['translationsFigure'] = $this->getTranslationsFigureField( translationClass: \KupShop\I18nBundle\Translations\SectionsTranslation::class, column: ['translation_section' => 'translations'], ); } $tableDef['fields']['priority']['fieldOptions'] = [ '-1' => translate('priority_minor', 'sections'), '0' => translate('priority_normal', 'sections'), '1' => translate('priority_major', 'sections'), ]; $tableDef['fields']['visibility']['fieldOptions'] = [ 'Y' => translate('figureY', 'sections'), 'N' => translate('figureN', 'sections'), 'O' => translate('figureO', 'sections'), ]; $tableDef['fields']['orderby']['fieldOptions'] = [ 'date' => translate('orderby_date', 'sections'), 'title' => translate('orderby_title', 'sections'), 'price' => translate('orderby_price', 'sections'), 'store' => translate('orderby_store', 'sections'), 'sell' => translate('orderby_sell', 'sections'), 'code' => translate('orderby_code', 'sections'), 'discount' => translate('orderby_discount', 'sections'), 'soldPrice' => translate('orderby_soldPrice', 'sections'), 'storeValue' => translate('orderby_storeValue', 'sections'), ]; $tableDef['fields']['orderdir']['fieldOptions'] = [ 'ASC' => translate('orderdir_asc', 'sections'), 'DESC' => translate('orderdir_desc', 'sections'), ]; if (findModule(Modules::INDEXED_FILTER)) { $tableDef['fields']['filterUrl'] = [ 'translate' => true, 'field' => 's.filter_url', 'visible' => 'N', 'fieldType' => SectionsList::TYPE_STRING, 'size' => 1, ]; $tableDef['fields']['title'] = [ 'translate' => true, 'field' => 'section_filter_title', 'visible' => 'N', 'size' => 1, 'fieldType' => SectionsList::TYPE_STRING, ]; } if (findModule(Modules::PRODUCTS_SECTIONS, 'custom_url')) { $tableDef['fields']['sectionUrl'] = [ 'translate' => true, 'field' => 'section_custom_url', 'visible' => 'N', 'fieldType' => SectionsList::TYPE_STRING, 'size' => 1, ]; } $tableDef['fields']['Popis']['spec'] = function (Query\QueryBuilder $qb) { $subSelect = sqlQueryBuilder()->select('LEFT(STRIP_TAGS(b.content), 200)') ->from('blocks', 'b') ->where('b.id_root = s.id_block AND b.content IS NOT NULL AND b.content != "" ') ->orderBy('b.position') ->setMaxResults(1); $qb->addSubselect($subSelect, 'descr'); }; $tableDef['fields']['Heureka']['spec'] = function (Query\QueryBuilder $qb) { $qb->addSelect('f_h.id as join_feed_heureka') ->leftJoin('s', 'kupshop_shared.feed_heureka', 'f_h', 'f_h.id=s.feed_heureka'); }; $tableDef['fields']['Heureka SK']['spec'] = function (Query\QueryBuilder $qb) { $qb->addSelect('f_hsk.id as join_feed_heureka_sk') ->leftJoin('s', 'kupshop_shared.feed_heureka_sk', 'f_hsk', 'f_hsk.id=s.feed_heureka_sk'); }; $tableDef['fields']['Google']['spec'] = function (Query\QueryBuilder $qb) { $qb->addSelect('f_g.id as join_feed_google') ->leftJoin('s', 'kupshop_shared.feed_google', 'f_g', 'f_g.id=s.feed_google'); }; $tableDef['fields']['Seznam']['spec'] = function (Query\QueryBuilder $qb) { $qb->addSelect('f_s.id as join_feed_seznam') ->leftJoin('s', 'kupshop_shared.feed_seznam', 'f_s', 'f_s.id=s.feed_seznam'); }; $tableDef['fields']['Glami']['spec'] = function (Query\QueryBuilder $qb) { $qb->addSelect('f_gl.id IS NOT NULL as join_feed_glami') ->leftJoin('s', 'kupshop_shared.feed_glami', 'f_gl', 'f_gl.id=s.feed_glami'); }; $tableDef['fields']['Feed H/G/S/GL']['spec'] = function (Query\QueryBuilder $qb) use ($tableDef) { $qb->addSelect($tableDef['fields']['Heureka']['spec']); $qb->addSelect($tableDef['fields']['Google']['spec']); $qb->addSelect($tableDef['fields']['Seznam']['spec']); $qb->addSelect($tableDef['fields']['Glami']['spec']); }; if (findModule(\Modules::SLIDERS)) { $positions = \KupShop\ContentBundle\Util\SliderUtil::getPositions(); foreach ($positions as $position => $positionName) { $tableDef['fields']["{$positionName} banner"] = [ 'spec' => "JSON_EXTRACT(sliders_positions.positions, '$.{$position}') AS {$position}_slider_name", 'field' => "{$position}_slider_name", ]; } } return $tableDef; } public function getColumns() { $table = parent::getColumns(); // add first column and fix positions foreach ($table['fields'] as $key => $value) { $value['position']++; } $table['fields'] = array_merge([ 'Sekce' => [ 'label' => 'Název', 'field' => 'name', 'render' => 'getTitle', 'class' => 'alignLeft', 'size' => 4, 'position' => '0', 'visible' => 'Y', 'multiplier' => '1', 'type_id' => $table['id'], 'type' => $GLOBALS['type'], ], ], $table['fields']); return $table; } public function renderVisibility($values, $column) { if ($values['figure'] === 'O') { return $this->renderIcon('warning'); } return $this->renderBoolean($values, $column); } public function renderFlags($values, $column) { $badges = $this->getSectionUtil()->getBadges( figure: $values['figure'], virtual: $values['virtual'], showInSearch: $values['show_in_search'] ); return array_map( fn (array $badge) => $this->renderBadge( translate($badge['translate_key'], 'sections'), $badge['class'], $badge['icon'] ), $badges, ); } public function renderPriority($values, $column) { switch ($values[$column['field']]) { case -1: return translate('priority_minor', 'sections'); case 0: return translate('priority_normal', 'sections'); case 1: return translate('priority_major', 'sections'); } return ''; } public function renderOrderBy($values, $column) { if ($orderby = $values['orderby'] ?? null) { return translate('orderby_'.$orderby, 'sections'); } return ''; } public function renderOrderDir($values, $column) { if ($orderdir = $values['orderdir'] ?? null) { return translate('orderdir_'.strtolower($orderdir), 'sections'); } return ''; } public function getListRowValue($values, $field, $default = null) { $field_name = $this->getFieldArrayName($field); if (empty($values[$field_name])) { return $this->additionalData[$values['id']][$field_name] ?? $default; } return $values[$field_name]; } public function getTitle($values, $column) { if ($values['id'] == 0) { return HTML::create('strong')->attr('class', 'text-dark') ->text($this->getListRowValue($values, $column['field'])); } return [ HTML::create('span') ->class('drag-drop-mover') ->tag('i') ->class('bi bi-arrows-move handle') ->end(), HTML::create('span') ->class('bi bi-dash-circle opener ' .($this->openedSections == 'all' || in_array($values['id'], $this->openedSections) ? ' ' : 'plus ') .($values['children_count'] > 0 ? '' : 'disabled')), HTML::create('strong') ->attr('class', 'text-dark') ->text($this->getListRowValue($values, $column['field'])), ]; } public function getFeeds($values, $column) { $return = []; $return[] = $this->renderBoolean($values, ['field' => 'join_feed_heureka'], 'Heureka'); $return[] = $this->renderBoolean($values, ['field' => 'join_feed_google'], 'Google'); $return[] = $this->renderBoolean($values, ['field' => 'join_feed_seznam'], 'Seznam'); $return[] = $this->renderBoolean($values, ['field' => 'join_feed_glami'], 'Glami'); // add space between icons /** @var HTML $ret */ foreach ($return as &$ret) { $ret = $ret->tag('span')->attr('style', 'padding-right:4px')->end(); } return $return; } public function getTemplate() { if (getVal('root')) { return 'list/sections.ajax.tpl'; } return parent::getTemplate(); } public function getSubsection($values) { if ($values['id'] == 0) { $result = HTML::create('a') ->attr('class', 'btn btn-sm btn-block btn-success') ->attr('title', 'Přidat sekci') ->attr('href', 'javascript:nw("sections", '.null.');') ->tag('span') ->class('bi bi-plus-lg m-r-1') ->end(); return $result->text('Přidat sekci'); } return HTML::create('a') ->attr('class', 'btn btn-sm btn-success') ->attr('title', 'Přidat podsekci') ->attr('href', 'javascript:nw("sections", "", "§ions[]='.$values['id'].'");') ->tag('span') ->class('bi bi-plus-lg') ->end(); } public function handleDrag() { $tree = getVal('tree'); if ($tree) { sqlGetConnection()->transactional(function () use ($tree) { $old_id_topsection = returnSQLResult('SELECT id_topsection FROM sections_relation WHERE id_section=:id_section', ['id_section' => $tree['id']]); $compare = ' >= '; if (!empty($tree['after'])) { $id_anchor = $tree['after']; $compare = ' > '; } elseif (!empty($tree['before'])) { $id_anchor = $tree['before']; } else { $id_anchor = null; } if ($id_anchor) { $anchor = sqlFetchAssoc(sqlQuery('SELECT * FROM sections_relation WHERE id_section=:id_section', ['id_section' => $id_anchor])); } else { // Insert as the only child $anchor = ['position' => 0, 'id_topsection' => $tree['target']]; } // Check cycle $cycling = $this->checkSubSections($this->getCategoryById($tree['id'])['children'], $anchor['id_topsection']); if ($cycling) { header('HTTP/1.1 500 Error'); exit('Nelze zvolit jako nadřazenou sekci některou z podsekcí!'); } sqlQuery('DELETE FROM sections_relation WHERE id_section=:id_section', ['id_section' => $tree['id']]); sqlQueryBuilder()->update('sections_relation') ->set('position', 'position+2') ->where(Operator::equalsNullable(['id_topsection' => $anchor['id_topsection']])) ->andWhere("position {$compare} :anchor_position") ->setParameter('anchor_position', $anchor['position']) ->execute(); sqlQueryBuilder()->insert('sections_relation') ->directValues([ 'id_section' => $tree['id'], 'id_topsection' => $anchor['id_topsection'], 'position' => $anchor['position'] + 1, ]) ->execute(); self::orderTreeLevel($anchor['id_topsection']); if ($anchor['id_topsection'] != $old_id_topsection) { self::orderTreeLevel($old_id_topsection); } }); MenuSectionTree::invalidateCache(); exit('OK'); } exit('Err'); } public static function orderTreeLevel($id_topsection) { if (empty($id_topsection)) { $where = 'id_topsection IS NULL'; } else { $where = "id_topsection = {$id_topsection}"; } $SQL = sqlQuery('SELECT id_section, position FROM '.getTableName('sections_relation').' sr LEFT JOIN '.getTableName('sections')." s ON s.id=sr.id_section WHERE {$where} ORDER BY sr.position, s.name"); foreach ($SQL as $index => $row) { if ($row['position'] != $index) { sqlQuery('UPDATE '.getTableName('sections_relation')." SET position={$index} WHERE id_section={$row['id_section']} AND {$where}"); } } } public function renderCell($values, $column, $tooltip = '') { $value = parent::renderCell($values, $column, $tooltip); if (in_array($column['field'], ['join_feed_heureka', 'join_feed_heureka_sk', 'join_feed_seznam', 'join_feed_google', 'join_feed_glami'])) { return (bool) $value; } return $value; } public function getQuery() { $qb = $this->getSectionsQueryBuilder() ->andWhere(Operator::inIntArray($this->getFilterIds(), 's.id')); return $qb; } public function getFilterQuery(): Query\QueryBuilder { $qb = parent::getFilterQuery() ->leftJoin('s', 'sections_relation', 'sr', 's.id = sr.id_section'); if ($rootId = getVal('root') ?? false) { $qb->andWhere(Operator::equals(['sr.id_topsection' => $rootId])); } else { $qb->andWhere( Operator::orX( Operator::equals(['sr.id_topsection' => 0]), 'sr.id_topsection IS NULL' ) ); } return $qb; } public function get_vars() { $vars = parent::get_vars(); $vars['openedSections'] = $this->getOpenedSections(); return $vars; } public function getSQL(Query\QueryBuilder $qb) { $result = parent::getSQL($qb); $sections = $this->loadSubSections( sqlFetchAll($result['SQL'], 'id') ); $result['SQL'] = $sections; return $result; } protected function getDefaultOrder() { return [ 'sort' => 'sr.position', 'direction' => 'ASC', ]; } protected function getSectionsQueryBuilder(): Query\QueryBuilder { $qb = sqlQueryBuilder() ->select('s.*') ->from('sections', 's') ->leftJoin('s', 'sections_relation', 'sr', 's.id = sr.id_section') ->groupBy('s.id') ->orderBy('sr.position'); $childrenCountSubQuery = sqlQueryBuilder() ->select('COUNT(id_section)') ->from('sections_relation') ->where('id_topsection = s.id'); $qb->addSubselect($childrenCountSubQuery, 'children_count'); if (findModule(Modules::INDEXED_FILTER)) { $qb->addSelect('s.title as section_filter_title, s.filter_url'); } if (findModule(Modules::PRODUCTS_SECTIONS, 'custom_url')) { $qb->addSelect('s.url as section_custom_url'); } if (findModule(\Modules::SLIDERS)) { $sliderPositions = sqlQueryBuilder()->select('sis.id_section AS id', 'CONCAT("{", GROUP_CONCAT( DISTINCT CONCAT(\'"\', sis.position, \'": "\', sliders.name, \'"\') SEPARATOR \', \'), "}") AS positions') ->from('sliders_in_sections', 'sis') ->leftJoin('sis', 'sliders', 'sliders', 'sis.id_slider = sliders.id') ->groupBy('sis.id_section'); $qb->leftJoinSubQuery('s', $sliderPositions, 'sliders_positions', 's.id = sliders_positions.id'); } return $qb; } private function loadSubSections(array $rootSections): array { $openedSections = $this->getOpenedSections(); if (empty($openedSections)) { return $rootSections; } $sectionUtil = ServiceContainer::getService(SectionUtil::class); if ($openedSections === 'all') { $sectionIds = call_user_func_array('array_merge', array_map( fn ($x) => $sectionUtil->getDescendantCategories($x['id'], false), $rootSections )); } else { $sectionIds = array_keys($rootSections); foreach ($openedSections as $openedSection) { $sectionIds = array_merge($sectionIds, $sectionUtil->getDescendantCategories($openedSection, false)); } } $qb = $this->getSectionsQueryBuilder() ->addSelect('sr.id_topsection') ->andWhere( Operator::orX( Operator::inIntArray($sectionIds, 'sr.id_topsection'), 'sr.id_topsection IS NULL' ) ) ->orderBy('FIELD(s.id, :sectionIds)') ->setParameter('sectionIds', $sectionIds, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY); $qb = $this->evaluateColumnClosures($qb); return $this->buildSectionTree( sqlFetchAll($qb->execute(), 'id') ); } // zbuildim strom sekci za pomoci referenci, protoze je to mnohem rychlejsi, jak kdyz se to buildi rekurzivne private function buildSectionTree(array $sections): array { $tree = []; $references = []; foreach ($sections as $id => &$section) { // Add the node to our associative array using its ID as key $references[$id] = &$section; // Add empty placeholder for children $section['children'] = []; // If it's a root node, we add it directly to the tree if (empty($section['id_topsection'])) { $tree[$id] = &$section; } else { // It was not a root node, add this node as a reference in the parent. $references[$section['id_topsection']]['children'][$id] = &$section; } } return $tree; } private function getOpenedSections() { if ($this->openedSections !== null) { return $this->openedSections; } $openedSectionsGet = getVal('opened') ?? []; $openedSectionsCookie = getVal('products_list_opened_sections', $_COOKIE, false); if (isset($_COOKIE['products_list_opened_sections'])) { unset($_COOKIE['products_list_opened_sections']); setcookie('products_list_opened_sections', '', -1, '/'); } $this->openedSections = []; if ($openedSectionsGet != 'all') { $this->openedSections += ($openedSectionsGet) ? json_decode_strict($openedSectionsGet) : []; $this->openedSections += ($openedSectionsCookie) ? json_decode_strict($openedSectionsCookie) : []; } else { $this->openedSections = 'all'; } return $this->openedSections; } public function customizeMassTableDef($tableDef) { $tableDef = parent::customizeMassTableDef($tableDef); if (findModule(\Modules::SLIDERS)) { $positions = \KupShop\ContentBundle\Util\SliderUtil::getPositions(); foreach ($positions as $position => $positionName) { $tableDef['fields']["{$positionName} banner"] = [ 'fieldType' => SectionsListMassEdit::TYPE_SLIDERS, 'sliderPosition' => $position, 'field' => 'sliders_'.$position, 'size' => 1.25, ]; } $badges = $tableDef['fields']['Příznak']; unset($tableDef['fields']['Příznak']); $tableDef['fields']['Příznak'] = $badges; $tableDef['fields']['Popis']['size'] = 2; } unset($tableDef['fields']['Feed H/G/S/GL']); return $tableDef; } }