sectionUtil = ServiceContainer::getService(SectionUtil::class); $this->sliderUtil = ServiceContainer::getService(SliderUtil::class); } public function get_vars() { $ID = $this->getID(); $vars = parent::get_vars(); $pageVars = getVal('body', $vars); $pageVars['ID'] = $ID; $pageVars['data']['default_size'] = getVal('size', $GLOBALS['cfg']['Modules']['sliders'], [780, 400]); if ($ID == 1) { $pageVars['data']['default_size'] = getVal('size_index', $GLOBALS['cfg']['Modules']['sliders'], $pageVars['data']['size']); } $pager = $this->createPager(); $SQL = sqlQueryBuilder() ->select('SQL_CALC_FOUND_ROWS si.*') ->from('sliders_images', 'si') ->where(Operator::equals(['si.id_slider' => $ID])) ->andWhere($pager->getSpec()) ->orderBy('position', 'ASC') ->execute()->fetchAll(); $total_count = (int) sqlFetchAssoc(sqlQuery('SELECT FOUND_ROWS() as total_count'))['total_count']; $pager->setTotal($total_count); $photos = sqlFetchAll(sqlQueryBuilder() ->select('*') ->from('photos') ->where(Operator::inIntArray(array_map(function ($x) { return $x['id_photo']; }, $SQL), 'id')) ->execute(), 'id'); $imageLocator = ServiceContainer::getService(ImageLocator::class); $images = []; foreach ($SQL as $row) { $row['img'] = $row['id_photo'] ? $imageLocator->getImage($photos[$row['id_photo']]) : null; $row['active'] = $this->isSliderActive($row); $this->unserializeCustomData($row); $row['sliderTranslationsFigure'] = $this->getTranslationUtil()?->getTranslationsFigure(SlidersImagesTranslation::class, $row['id']); $images[] = $row; } $pageVars['images'] = $images; if (findModule(\Modules::PRODUCTS_SECTIONS)) { $pageVars['data']['slider_sections'] = $this->fetchSliderSections((int) $ID); } $this->unserializeCustomData($pageVars['data']); $vars['body'] = $pageVars; $vars['pager'] = $pager; return $vars; } public function createPager() { $pager = new \Pager(); $pager->setOnPage(self::ON_PAGE); $pager->setPageNumber((int) getVal('page', null, 1)); $pager->setUrl($_SERVER['REQUEST_URI']); return $pager; } public function getData() { $data = parent::getData(); if (getVal('Submit')) { $this->serializeCustomData($data); } return $data; } public function handleUpdate() { $OLD_ID = $this->getID(); $SQL = parent::handleUpdate(); $ID = $this->getID(); if ($duplicate = $this->isDuplicate()) { // duplicate superuser fields (sizes) sqlQueryBuilder() ->update('sliders', 's') ->join('s', 'sliders', 'so', 'so.id = :oldId') ->set('s.size', 'so.size') ->set('s.size_tablet', 'so.size_tablet') ->set('s.size_mobile', 'so.size_mobile') ->where(Operator::equals(['s.id' => $this->getID()])) ->setParameter('oldId', $OLD_ID) ->execute(); // duplicate sliders $duplicate_sliders = sqlFetchAll($this->selectSQL('sliders_images', ['id_slider' => $OLD_ID]), 'id'); foreach ($duplicate_sliders as &$img) { $img = $this->insertSlidersImage($img); } } $data = $this->getData(); $sliders = getVal('images', $data, []); krsort($sliders); foreach ($sliders as $id => &$img) { $photoId = empty($img['id_photo']) ? null : $img['id_photo']; if (!empty($img['delete']) || !$id) { if ($id > 0) { $delete_id = $img['id']; if ($duplicate) { $delete_id = $duplicate_sliders[$img['id']]['id'] ?? $img['id']; } $this->deleteSQL('sliders_images', ['id' => $delete_id, 'id_slider' => $ID]); } continue; } $img['date_from'] = ($img['date_from'] == '') ? null : $this->prepareDateTime($img['date_from']); $img['date_to'] = ($img['date_to'] == '') ? null : $this->prepareDateTime($img['date_to']); $this->serializeCustomData($img); $uploadedImages = $this->getUploadedImages((int) $id); $translateSlidersFigure = $img['translation_figure'] ?? []; unset($img['translation_figure']); if ($id < 0) { // Skip if desktop image is missing if (empty($uploadedImages['desktop'])) { continue; } // upload new photos when adding slider if ($photoId = $this->uploadImages($uploadedImages, null)) { $img['id_photo'] = $photoId; $img['position'] = 0; $img = $this->insertSlidersImage($img); } } else { if ($duplicate) { if ($duplicate_img = ($duplicate_sliders[$img['id']] ?? null)) { unset($img['id']); $duplicate_img = array_replace($duplicate_img, $img); $this->updateSQL('sliders_images', $duplicate_img, ['id' => $duplicate_img['id']]); $img = $duplicate_img; } } else { // upload photos to existing slider (upload of additional photo versions) if ($photoId) { $this->uploadImages($uploadedImages, $photoId); } $this->updateSQL('sliders_images', $img, ['id' => $img['id']]); } $this->getTranslationUtil()?->updateTranslationsFigure(SlidersImagesTranslation::class, $img['id'], $translateSlidersFigure); } } if (findModule(\Modules::PRODUCTS_SECTIONS)) { $this->saveSliderSections(getVal('slider_sections', $data, [])); } sqlQuery('SELECT @i := 0; UPDATE sliders_images SET position = (select @i := @i + 1) WHERE id_slider=:id ORDER BY position', ['id' => $ID]); MenuSectionTree::invalidateCache(); clearCache('sliders', true); if ($page = getVal('page')) { $_GET['page'] = $page; } $this->activityMessage($data['name']); $this->returnOK($GLOBALS['txt_str']['status']['saved']); } public function uploadImages(array $images, ?int $photoId): int { $versions = [ 'desktop' => Photos::PHOTO_VERSION_DESKTOP, 'tablet' => Photos::PHOTO_VERSION_TABLET, 'mobile' => Photos::PHOTO_VERSION_MOBILE, ]; foreach ($images as $version => $image) { if (!empty($image)) { $img = Photos::get($photoId, $versions[$version]); $img->uploadPhotoOrVideo($image, $image['name']); if ($photoId === null) { $photoId = (int) $img->getID(); } } } return $photoId; } public function getUploadedImages(int $id): array { $images = $_FILES['image'] ?? []; $result = []; foreach (['desktop', 'tablet', 'mobile'] as $type) { $result[$type] = ($images['size'][$id][$type] ?? 0) <= 0 ? [] : [ 'name' => $images['name'][$id][$type], 'type' => $images['type'][$id][$type], 'tmp_name' => $images['tmp_name'][$id][$type], 'error' => $images['error'][$id][$type], 'size' => $images['size'][$id][$type], ]; } return $result; } protected function insertSlidersImage($img) { unset($img['id']); $img['id_slider'] = $this->getID(); $this->insertSQL('sliders_images', $img); $img['id'] = sqlInsertId(); return $img; } protected function isSliderActive(array $slider): bool { $today = new DateTime(); $dateFrom = $slider['date_from'] ? new DateTime($slider['date_from']) : null; $dateTo = $slider['date_to'] ? new DateTime($slider['date_to']) : null; if (($dateFrom === null || $dateFrom < $today) && ($dateTo === null || $dateTo > $today)) { return true; } return false; } public function handleDrag() { if (($moved_item = getVal('moved_item')) && ($ID = $this->getID())) { $this->updateSQL('sliders_images', ['position' => 0], ['id' => $moved_item, 'id_slider' => $ID]); sqlQuery('SELECT @i := 0; UPDATE sliders_images SET position = (select @i := @i + 1) WHERE id_slider=:id ORDER BY position', ['id' => $ID]); } $this->returnOK(); } public function saveSliderSections(array $sliderSections): void { $sliderSections = array_map(fn (string $json) => json_decode_strict($json, true), $sliderSections); sqlGetConnection()->transactional(function () use ($sliderSections) { $this->sliderUtil->clearSliderSections(idSlider: (int) $this->getID()); $this->sliderUtil->saveSliderPositions($sliderSections, ['id_slider']); }); } protected function fetchSliderSections(int $sliderId): array { $sectionIds = sqlQueryBuilder()->select('sis.id_section AS id') ->from('sliders_in_sections', 'sis') ->andWhere(Operator::equals(['sis.id_slider' => $sliderId])); $qb = $this->createSliderSectionsQB() ->andWhere(Operator::inSubquery('s.id', $sectionIds)) ->groupBy('s.id_rootsection', 'sis.id_section', 'sis.id_slider', 'sis.position'); $sliderSections = []; foreach ($qb->execute() as $flatSection) { $sectionId = $flatSection['id_section']; if (!isset($sliderSections[$sectionId])) { $sliderSections[$sectionId] = [ 'id' => $sectionId, 'full_path' => $flatSection['full_path'], 'name' => $flatSection['section_name'], 'badges' => $this->getBadges($flatSection), 'positions' => $this->getEmptyPositions(), 'id_rootsection' => $flatSection['id_rootsection'], 'full_position' => $flatSection['full_position'], 'section_position' => $flatSection['section_position'], ]; } $sliderSections[$sectionId]['positions'][$flatSection['position']] = $flatSection; } $orderedSections = []; foreach ($sliderSections as $section) { $orderedSections[$section['id_rootsection']][] = $section; } foreach ($orderedSections as $orderedSection) { uasort($orderedSection, function ($a, $b) { return $a['section_position'] <=> $b['section_position']; }); } return array_reduce($orderedSections, fn ($carry, $sections) => array_merge($carry, $sections), []); } protected function createSliderSectionsQB(?int $idTopsection = null): QueryBuilder { $whereSection = isset($idTopsection) ? 'id_topsection = :idTopSection' : 'id_topsection IS NULL'; $recursiveSections = " WITH RECURSIVE cte (id, id_rootsection, id_topsection, name, figure, virtual, show_in_search, full_path, position, full_position) as ( SELECT id_section, id_section AS id_rootsection, id_topsection, (SELECT name FROM sections WHERE id = id_section LIMIT 1) as name, (SELECT figure FROM sections WHERE id = id_section LIMIT 1) as figure, (SELECT virtual FROM sections WHERE id = id_section LIMIT 1) as virtual, (SELECT show_in_search FROM sections WHERE id = id_section LIMIT 1) as show_in_search, (SELECT name FROM sections WHERE id = id_section LIMIT 1) as full_path, position, CAST(LPAD(position, 5, 0) AS CHAR(500)) AS full_position FROM sections_relation WHERE {$whereSection} UNION ALL SELECT sr.id_section, cte.id_rootsection, sr.id_topsection, (SELECT name FROM sections WHERE id = sr.id_section LIMIT 1) as name, (SELECT figure FROM sections WHERE id = sr.id_section LIMIT 1) as figure, (SELECT virtual FROM sections WHERE id = sr.id_section LIMIT 1) as virtual, (SELECT show_in_search FROM sections WHERE id = sr.id_section LIMIT 1) as show_in_search, CONCAT(cte.full_path,' > ',(SELECT name FROM sections WHERE id = sr.id_section LIMIT 1)) as full_path, CONCAT(full_position, '/', LPAD(sr.position, 5, 0)), sr.position FROM sections_relation sr INNER JOIN cte on sr.id_topsection = cte.id ) SELECT * FROM cte"; return sqlQueryBuilder()->select( 's.id AS id_section', 's.name AS section_name', 's.figure', 's.virtual', 's.show_in_search', 's.full_path', 'sis.id_slider', 'sis.position', 'sl.name AS slider_name', 's.id_topsection', 's.position AS section_position', 's.full_position', 's.id_rootsection' ) ->from("({$recursiveSections})", 's') ->leftJoin('s', 'sliders_in_sections', 'sis', 'sis.id_section = s.id') ->leftJoin('sis', 'sliders', 'sl', 'sis.id_slider = sl.id'); } protected function getBadges(array $section): array { if (!$section) { return []; } $badges = $this->sectionUtil->getBadges( figure: $section['figure'], virtual: $section['virtual'], showInSearch: $section['show_in_search'], ); foreach ($badges as &$badge) { $badge['title'] = translate($badge['translate_key'], 'sections'); } return $badges; } protected function getEmptyPositions(): array { return array_fill_keys(array_keys($this->sliderUtil::getPositions()), null); } public function handleAjaxSection(): never { $idSection = getVal('id_section'); if (!is_numeric($idSection)) { throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException('id_section is required'); } $sections = $this->createSliderSectionsQB() ->andWhere(Operator::equals(['s.id' => $idSection])); $result = []; foreach ($sections->execute() as $sectionRow) { if (empty($result)) { $result = [ 'id' => $idSection, 'name' => $sectionRow['section_name'], 'full_path' => $sectionRow['full_path'], 'badges' => $this->getBadges($sectionRow), 'positions' => $this->getEmptyPositions(), ]; } if (!empty($sectionRow['id_slider'])) { $result['positions'][$sectionRow['position']] = [ 'id_slider' => $sectionRow['id_slider'], 'slider_name' => $sectionRow['slider_name'], ]; } } header('Content-Type: application/json'); echo json_encode($result); exit; } } $sliders = new Sliders(); $sliders->run();