diff --git a/config/form.yml b/config/form.yml index c2864f7..1d634eb 100644 --- a/config/form.yml +++ b/config/form.yml @@ -42,6 +42,14 @@ services: tags: - { name: form.type } + websmid.customhtml.form.type.customhtml_slide: + public: true + class: 'Websmid\CustomHtml\Form\Type\CustomHtmlSlideType' + arguments: + - '@translator' + tags: + - { name: form.type } + websmid.customhtml.form.type.customhtml_block: public: true class: 'Websmid\CustomHtml\Form\Type\CustomHtmlBlockType' diff --git a/src/Config/Config.php b/src/Config/Config.php index ab943ea..43c8fdf 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -16,8 +16,24 @@ class Config 'banner' => 'Banner', 'alert' => 'Alert', 'usp' => 'USP', + 'slider' => 'Slider', ]; + const SLIDER_DEFAULT_SLIDES_PER_VIEW_MOBILE = 1; + + const SLIDER_DEFAULT_SLIDES_PER_VIEW_TABLET = 2; + + const SLIDER_DEFAULT_SLIDES_PER_VIEW_DESKTOP = 3; + + const SLIDER_MODES = [ + 'always' => 'Always show as a slider', + 'responsive' => 'Slider on mobile/tablet, static grid on desktop', + ]; + + const SLIDER_DEFAULT_MODE = 'always'; + + const SLIDER_RESPONSIVE_BREAKPOINT = 480; + const ALERT_STYLES = [ 'success' => 'Success', 'info' => 'Info', diff --git a/src/Form/DataHandler/CustomHtmlBlockDataHandler.php b/src/Form/DataHandler/CustomHtmlBlockDataHandler.php index 2642335..f22dece 100644 --- a/src/Form/DataHandler/CustomHtmlBlockDataHandler.php +++ b/src/Form/DataHandler/CustomHtmlBlockDataHandler.php @@ -6,6 +6,7 @@ use PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataHandler\FormDataHandl use Websmid\CustomHtml\Config\Config; use Websmid\CustomHtml\Model\CustomHtmlBlock; use Websmid\CustomHtml\Model\CustomHtmlFaq; +use Websmid\CustomHtml\Model\CustomHtmlSlide; use Symfony\Component\HttpFoundation\File\UploadedFile; use Db; use Exception; @@ -27,8 +28,14 @@ final class CustomHtmlBlockDataHandler implements FormDataHandlerInterface $block->description = (string) ($formData['description'] ?? ''); $block->extra_description = (string) ($formData['extra_description'] ?? ''); $block->link = (string) ($formData['link'] ?? ''); + $block->link_text = (string) ($formData['link_text'] ?? ''); $block->alert_type = (string) ($formData['alert_type'] ?? 'info'); $block->timeout = (int) ($formData['timeout'] ?? 0); + $block->slider_mode = (string) ($formData['slider_mode'] ?? Config::SLIDER_DEFAULT_MODE); + $block->slides_per_view_mobile = (float) ($formData['slides_per_view_mobile'] ?? Config::SLIDER_DEFAULT_SLIDES_PER_VIEW_MOBILE); + $block->slides_per_view_tablet = (float) ($formData['slides_per_view_tablet'] ?? Config::SLIDER_DEFAULT_SLIDES_PER_VIEW_TABLET); + $block->slides_per_view_desktop = (float) ($formData['slides_per_view_desktop'] ?? Config::SLIDER_DEFAULT_SLIDES_PER_VIEW_DESKTOP); + $block->autoplay_delay = (int) ($formData['autoplay_delay'] ?? 0); $block->image = $this->saveUploadedImage($formData['image'] ?? null, ''); $block->active = (int) $formData['active']; $block->ordering = $this->getNextOrdering($idGroup); @@ -38,6 +45,7 @@ final class CustomHtmlBlockDataHandler implements FormDataHandlerInterface } $this->saveFaqs((int) $block->id, $formData['faqs'] ?? []); + $this->saveSlides((int) $block->id, $formData['slides'] ?? []); return $block->id; } @@ -59,8 +67,14 @@ final class CustomHtmlBlockDataHandler implements FormDataHandlerInterface $block->description = (string) ($formData['description'] ?? ''); $block->extra_description = (string) ($formData['extra_description'] ?? ''); $block->link = (string) ($formData['link'] ?? ''); + $block->link_text = (string) ($formData['link_text'] ?? ''); $block->alert_type = (string) ($formData['alert_type'] ?? 'info'); $block->timeout = (int) ($formData['timeout'] ?? 0); + $block->slider_mode = (string) ($formData['slider_mode'] ?? Config::SLIDER_DEFAULT_MODE); + $block->slides_per_view_mobile = (float) ($formData['slides_per_view_mobile'] ?? Config::SLIDER_DEFAULT_SLIDES_PER_VIEW_MOBILE); + $block->slides_per_view_tablet = (float) ($formData['slides_per_view_tablet'] ?? Config::SLIDER_DEFAULT_SLIDES_PER_VIEW_TABLET); + $block->slides_per_view_desktop = (float) ($formData['slides_per_view_desktop'] ?? Config::SLIDER_DEFAULT_SLIDES_PER_VIEW_DESKTOP); + $block->autoplay_delay = (int) ($formData['autoplay_delay'] ?? 0); $block->image = $this->saveUploadedImage($formData['image'] ?? null, $block->image); $block->active = (int) $formData['active']; @@ -69,6 +83,7 @@ final class CustomHtmlBlockDataHandler implements FormDataHandlerInterface } $this->saveFaqs((int) $id, $formData['faqs'] ?? []); + $this->saveSlides((int) $id, $formData['slides'] ?? []); return true; } @@ -143,4 +158,37 @@ final class CustomHtmlBlockDataHandler implements FormDataHandlerInterface $faq->add(); } } + + /** + * Slides are fully replaced on every save, like FAQ items — except a + * slide's image can't round-trip through its file input, so the previous + * filename is looked up by the slide's hidden id and reused whenever no + * new file was uploaded for that row. + */ + private function saveSlides(int $idBlock, array $slides): void + { + $existingImages = []; + + foreach (CustomHtmlSlide::getByBlockId($idBlock) as $existingSlide) { + $existingImages[(int) $existingSlide['id_ws_customhtml_slide']] = $existingSlide['image']; + } + + CustomHtmlSlide::deleteByBlockId($idBlock); + + foreach (array_values($slides) as $position => $slideData) { + $existingId = (int) ($slideData['id'] ?? 0); + $existingImage = $existingImages[$existingId] ?? ''; + + $slide = new CustomHtmlSlide(); + $slide->id_ws_customhtml = $idBlock; + $slide->title = (string) ($slideData['title'] ?? ''); + $slide->description = (string) ($slideData['description'] ?? ''); + $slide->extra_description = (string) ($slideData['extra_description'] ?? ''); + $slide->link = (string) ($slideData['link'] ?? ''); + $slide->link_text = (string) ($slideData['link_text'] ?? ''); + $slide->image = $this->saveUploadedImage($slideData['image'] ?? null, $existingImage); + $slide->ordering = $position; + $slide->add(); + } + } } diff --git a/src/Form/DataProvider/CustomHtmlBlockDataProvider.php b/src/Form/DataProvider/CustomHtmlBlockDataProvider.php index 2337365..f9f4141 100644 --- a/src/Form/DataProvider/CustomHtmlBlockDataProvider.php +++ b/src/Form/DataProvider/CustomHtmlBlockDataProvider.php @@ -3,8 +3,10 @@ namespace Websmid\CustomHtml\Form\DataProvider; use PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataProvider\FormDataProviderInterface; +use Websmid\CustomHtml\Config\Config; use Websmid\CustomHtml\Model\CustomHtmlBlock; use Websmid\CustomHtml\Model\CustomHtmlFaq; +use Websmid\CustomHtml\Model\CustomHtmlSlide; use Validate; final class CustomHtmlBlockDataProvider implements FormDataProviderInterface @@ -28,9 +30,16 @@ final class CustomHtmlBlockDataProvider implements FormDataProviderInterface 'description' => $block->description, 'extra_description' => $block->extra_description, 'link' => $block->link, + 'link_text' => $block->link_text, 'alert_type' => $block->alert_type, 'timeout' => (int) $block->timeout, + 'slider_mode' => $block->slider_mode, + 'slides_per_view_mobile' => (float) $block->slides_per_view_mobile, + 'slides_per_view_tablet' => (float) $block->slides_per_view_tablet, + 'slides_per_view_desktop' => (float) $block->slides_per_view_desktop, + 'autoplay_delay' => (int) $block->autoplay_delay, 'faqs' => $this->loadFaqs((int) $id), + 'slides' => $this->loadSlides((int) $id), 'active' => (bool) $block->active, ]; } @@ -48,9 +57,16 @@ final class CustomHtmlBlockDataProvider implements FormDataProviderInterface 'description' => '', 'extra_description' => '', 'link' => '', + 'link_text' => '', 'alert_type' => 'info', 'timeout' => 0, + 'slider_mode' => Config::SLIDER_DEFAULT_MODE, + 'slides_per_view_mobile' => Config::SLIDER_DEFAULT_SLIDES_PER_VIEW_MOBILE, + 'slides_per_view_tablet' => Config::SLIDER_DEFAULT_SLIDES_PER_VIEW_TABLET, + 'slides_per_view_desktop' => Config::SLIDER_DEFAULT_SLIDES_PER_VIEW_DESKTOP, + 'autoplay_delay' => 0, 'faqs' => [], + 'slides' => [], 'active' => true, ]; } @@ -64,4 +80,19 @@ final class CustomHtmlBlockDataProvider implements FormDataProviderInterface ]; }, CustomHtmlFaq::getByBlockId($idBlock)); } + + private function loadSlides(int $idBlock): array + { + return array_map(static function (array $slide) { + return [ + 'id' => (int) $slide['id_ws_customhtml_slide'], + 'title' => $slide['title'], + 'description' => $slide['description'], + 'extra_description' => $slide['extra_description'], + 'link' => $slide['link'], + 'link_text' => $slide['link_text'], + 'image' => null, + ]; + }, CustomHtmlSlide::getByBlockId($idBlock)); + } } diff --git a/src/Form/Type/CustomHtmlBlockType.php b/src/Form/Type/CustomHtmlBlockType.php index 6b00201..754d929 100644 --- a/src/Form/Type/CustomHtmlBlockType.php +++ b/src/Form/Type/CustomHtmlBlockType.php @@ -8,6 +8,7 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\Form\Extension\Core\Type\IntegerType; +use Symfony\Component\Form\Extension\Core\Type\NumberType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; use PrestaShop\PrestaShop\Core\Form\ConfigurableFormChoiceProviderInterface; @@ -70,6 +71,49 @@ class CustomHtmlBlockType extends AbstractType 'prototype' => true, 'required' => false, ]) + ->add('slides', CollectionType::class, [ + 'entry_type' => CustomHtmlSlideType::class, + 'allow_add' => true, + 'allow_delete' => true, + 'by_reference' => false, + 'prototype' => true, + 'required' => false, + ]) + ->add('slider_mode', ChoiceType::class, [ + 'label' => $this->translator->trans('Slider mode', [], 'Modules.Wscustomhtml.Admin'), + 'choices' => array_flip(Config::SLIDER_MODES), + 'required' => false, + 'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-slider'], + ]) + ->add('slides_per_view_mobile', NumberType::class, [ + 'label' => $this->translator->trans('Slides visible on mobile', [], 'Modules.Wscustomhtml.Admin'), + 'required' => false, + 'scale' => 2, + 'attr' => ['min' => 0, 'step' => '0.05'], + 'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-slider col-4'], + ]) + ->add('slides_per_view_tablet', NumberType::class, [ + 'label' => $this->translator->trans('Slides visible on tablet', [], 'Modules.Wscustomhtml.Admin'), + 'required' => false, + 'scale' => 2, + 'attr' => ['min' => 0, 'step' => '0.05'], + 'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-slider col-4'], + ]) + ->add('slides_per_view_desktop', NumberType::class, [ + 'label' => $this->translator->trans('Slides visible on desktop', [], 'Modules.Wscustomhtml.Admin'), + 'required' => false, + 'scale' => 2, + 'attr' => ['min' => 0, 'step' => '0.05'], + 'help' => $this->translator->trans('In "static grid on desktop" mode this is rounded to the nearest whole number of columns.', [], 'Modules.Wscustomhtml.Admin'), + 'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-slider col-4'], + ]) + ->add('autoplay_delay', IntegerType::class, [ + 'label' => $this->translator->trans('Autoplay delay (seconds)', [], 'Modules.Wscustomhtml.Admin'), + 'required' => false, + 'attr' => ['min' => 0, 'placeholder' => '0'], + 'help' => $this->translator->trans('0 means the slider never auto-advances; visitors can still swipe or use the arrows.', [], 'Modules.Wscustomhtml.Admin'), + 'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-slider'], + ]) ->add('alert_type', ChoiceType::class, [ 'label' => $this->translator->trans('Alert style', [], 'Modules.Wscustomhtml.Admin'), 'choices' => array_flip(Config::ALERT_STYLES), @@ -94,10 +138,16 @@ class CustomHtmlBlockType extends AbstractType 'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-banner'], ]) ->add('link', TextType::class, [ - 'label' => $this->translator->trans('Link', [], 'Modules.Wscustomhtml.Admin'), + 'label' => $this->translator->trans('Link URL', [], 'Modules.Wscustomhtml.Admin'), 'required' => false, 'attr' => ['placeholder' => 'https://example.com or /internal-page'], - 'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-banner customhtmlblock-type-usp'], + 'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-banner customhtmlblock-type-usp customhtmlblock-type-alert col-6'], + ]) + ->add('link_text', TextType::class, [ + 'label' => $this->translator->trans('Link text', [], 'Modules.Wscustomhtml.Admin'), + 'required' => false, + 'attr' => ['placeholder' => 'Learn more'], + 'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-alert col-6'], ]) ->add('custom_class', TextType::class, [ 'label' => $this->translator->trans('Custom classes', [], 'Modules.Wscustomhtml.Admin'), diff --git a/src/Form/Type/CustomHtmlSlideType.php b/src/Form/Type/CustomHtmlSlideType.php new file mode 100644 index 0000000..d0763c3 --- /dev/null +++ b/src/Form/Type/CustomHtmlSlideType.php @@ -0,0 +1,55 @@ +translator = $translator; + } + + public function buildForm(FormBuilderInterface $formBuilder, array $options): void + { + $formBuilder + ->add('id', HiddenType::class, [ + 'required' => false, + ]) + ->add('title', TextType::class, [ + 'label' => $this->translator->trans('Title', [], 'Modules.Wscustomhtml.Admin'), + 'required' => false, + ]) + ->add('description', TextareaType::class, [ + 'label' => $this->translator->trans('Description', [], 'Modules.Wscustomhtml.Admin'), + 'required' => false, + ]) + ->add('extra_description', TextareaType::class, [ + 'label' => $this->translator->trans('Extra description', [], 'Modules.Wscustomhtml.Admin'), + 'required' => false, + ]) + ->add('link', TextType::class, [ + 'label' => $this->translator->trans('Link URL', [], 'Modules.Wscustomhtml.Admin'), + 'required' => false, + 'attr' => ['placeholder' => 'https://example.com or /internal-page'], + ]) + ->add('link_text', TextType::class, [ + 'label' => $this->translator->trans('Link text', [], 'Modules.Wscustomhtml.Admin'), + 'required' => false, + 'attr' => ['placeholder' => 'Learn more'], + ]) + ->add('image', FileType::class, [ + 'label' => $this->translator->trans('Image', [], 'Modules.Wscustomhtml.Admin'), + 'required' => false, + ]); + } +} diff --git a/src/Model/CustomHtmlBlock.php b/src/Model/CustomHtmlBlock.php index efa6d10..ce3b9dd 100644 --- a/src/Model/CustomHtmlBlock.php +++ b/src/Model/CustomHtmlBlock.php @@ -34,6 +34,9 @@ class CustomHtmlBlock extends ObjectModel /** @var string */ public $link = ''; + /** @var string */ + public $link_text = ''; + /** @var string */ public $image = ''; @@ -43,6 +46,21 @@ class CustomHtmlBlock extends ObjectModel /** @var int */ public $timeout = 0; + /** @var string */ + public $slider_mode = 'always'; + + /** @var float */ + public $slides_per_view_mobile = 1; + + /** @var float */ + public $slides_per_view_tablet = 2; + + /** @var float */ + public $slides_per_view_desktop = 3; + + /** @var int */ + public $autoplay_delay = 0; + /** @var int */ public $active = 1; @@ -70,9 +88,15 @@ class CustomHtmlBlock extends ObjectModel 'description' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 1024], 'extra_description' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 1024], 'link' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 512], + 'link_text' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 128], 'image' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 255], 'alert_type' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 16], 'timeout' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => false], + 'slider_mode' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 16], + 'slides_per_view_mobile' => ['type' => self::TYPE_FLOAT, 'validate' => 'isUnsignedFloat', 'required' => false], + 'slides_per_view_tablet' => ['type' => self::TYPE_FLOAT, 'validate' => 'isUnsignedFloat', 'required' => false], + 'slides_per_view_desktop' => ['type' => self::TYPE_FLOAT, 'validate' => 'isUnsignedFloat', 'required' => false], + 'autoplay_delay' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => false], 'active' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'show_title' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'ordering' => ['type' => self::TYPE_INT, 'validate' => 'isInt'], @@ -105,7 +129,9 @@ class CustomHtmlBlock extends ObjectModel $result = Db::getInstance()->executeS( 'SELECT `id_ws_customhtml`, `id_ws_customhtml_group`, `title`, `type`, `content`, `custom_class`, - `description`, `extra_description`, `link`, `image`, `alert_type`, `timeout`, `active`, `show_title`, `ordering` + `description`, `extra_description`, `link`, `link_text`, `image`, `alert_type`, `timeout`, + `slider_mode`, `slides_per_view_mobile`, `slides_per_view_tablet`, `slides_per_view_desktop`, `autoplay_delay`, + `active`, `show_title`, `ordering` FROM `' . _DB_PREFIX_ . 'ws_customhtml` WHERE `id_ws_customhtml_group` IN (' . implode(',', $groupIds) . ') AND `active` = 1 diff --git a/src/Model/CustomHtmlSlide.php b/src/Model/CustomHtmlSlide.php new file mode 100644 index 0000000..e7f32e0 --- /dev/null +++ b/src/Model/CustomHtmlSlide.php @@ -0,0 +1,76 @@ + 'ws_customhtml_slide', + 'primary' => 'id_ws_customhtml_slide', + 'fields' => [ + 'id_ws_customhtml' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true], + 'title' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 256], + 'description' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 1024], + 'extra_description' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 1024], + 'link' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 512], + 'link_text' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 128], + 'image' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 255], + 'ordering' => ['type' => self::TYPE_INT, 'validate' => 'isInt'], + 'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'], + 'date_upd' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'], + ], + ]; + + public static function getByBlockId(int $idBlock): array + { + $result = Db::getInstance()->executeS( + 'SELECT `id_ws_customhtml_slide`, `title`, `description`, `extra_description`, `link`, `link_text`, `image`, `ordering` + FROM `' . _DB_PREFIX_ . 'ws_customhtml_slide` + WHERE `id_ws_customhtml` = ' . (int) $idBlock . ' + ORDER BY `ordering` ASC' + ); + + return $result ?: []; + } + + public static function deleteByBlockId(int $idBlock): void + { + Db::getInstance()->delete('ws_customhtml_slide', '`id_ws_customhtml` = ' . (int) $idBlock); + } +} diff --git a/src/Module/DisplayHooks.php b/src/Module/DisplayHooks.php index f1d3a36..bf5d2b4 100644 --- a/src/Module/DisplayHooks.php +++ b/src/Module/DisplayHooks.php @@ -2,9 +2,11 @@ namespace Websmid\CustomHtml\Module; +use Websmid\CustomHtml\Config\Config; use Websmid\CustomHtml\Model\CustomHtmlBlock; use Websmid\CustomHtml\Model\CustomHtmlFaq; use Websmid\CustomHtml\Model\CustomHtmlGroup; +use Websmid\CustomHtml\Model\CustomHtmlSlide; use Context; trait DisplayHooks @@ -70,6 +72,7 @@ trait DisplayHooks $blocksByGroup = CustomHtmlBlock::getActiveByGroupIds(array_column($groups, 'id_ws_customhtml_group')); $blocksByGroup = $this->attachFaqsToBlocks($blocksByGroup); $blocksByGroup = $this->attachImageUrls($blocksByGroup); + $blocksByGroup = $this->attachSlidesToBlocks($blocksByGroup); $groups = array_map(static function (array $group) use ($blocksByGroup) { $group['blocks'] = $blocksByGroup[(int) $group['id_ws_customhtml_group']] ?? []; @@ -107,4 +110,67 @@ trait DisplayHooks }, $blocks); }, $blocksByGroup); } + + private function attachSlidesToBlocks(array $blocksByGroup): array + { + return array_map(function (array $blocks) { + return array_map(function (array $block) { + if ($block['type'] === 'slider') { + $block['slides'] = $this->loadSlidesForBlock((int) $block['id_ws_customhtml']); + $block['slider_config'] = $this->buildSliderConfig($block); + $block['slider_desktop_column_width'] = $this->buildSliderDesktopColumnWidth($block); + } + + return $block; + }, $blocks); + }, $blocksByGroup); + } + + private function loadSlidesForBlock(int $idBlock): array + { + return array_map(static function (array $slide) { + $slide['image_url'] = CustomHtmlBlock::getBannerImageUrl($slide['image']); + $slide['show_title'] = true; + + return $slide; + }, CustomHtmlSlide::getByBlockId($idBlock)); + } + + private function buildSliderConfig(array $block): string + { + $config = [ + 'slidesPerView' => (float) $block['slides_per_view_mobile'], + 'spaceBetween' => 16, + 'speed' => 500, + 'watchOverflow' => true, + ]; + + if ($block['slider_mode'] === 'responsive') { + $config['breakpoints'] = [ + Config::SLIDER_RESPONSIVE_BREAKPOINT => ['slidesPerView' => (float) $block['slides_per_view_tablet']], + ]; + } else { + $config['breakpoints'] = [ + 768 => ['slidesPerView' => (float) $block['slides_per_view_tablet']], + 992 => ['slidesPerView' => (float) $block['slides_per_view_desktop']], + ]; + } + + if ((int) $block['autoplay_delay'] > 0) { + $config['loop'] = true; + $config['autoplay'] = [ + 'delay' => (int) $block['autoplay_delay'] * 1000, + 'disableOnInteraction' => false, + ]; + } + + return json_encode($config); + } + + private function buildSliderDesktopColumnWidth(array $block): string + { + $columns = max(1, (int) round((float) $block['slides_per_view_desktop'])); + + return round(100 / $columns, 4) . '%'; + } } diff --git a/src/Module/Install.php b/src/Module/Install.php index 1cac55b..42bd874 100644 --- a/src/Module/Install.php +++ b/src/Module/Install.php @@ -69,7 +69,15 @@ trait Install `description` TEXT, `extra_description` TEXT, `link` VARCHAR(512) NOT NULL DEFAULT \'\', + `link_text` VARCHAR(128) NOT NULL DEFAULT \'\', `image` VARCHAR(255) NOT NULL DEFAULT \'\', + `alert_type` VARCHAR(16) NOT NULL DEFAULT \'info\', + `timeout` INT(11) NOT NULL DEFAULT 0, + `slider_mode` VARCHAR(16) NOT NULL DEFAULT \'always\', + `slides_per_view_mobile` DECIMAL(4,2) NOT NULL DEFAULT 1, + `slides_per_view_tablet` DECIMAL(4,2) NOT NULL DEFAULT 2, + `slides_per_view_desktop` DECIMAL(4,2) NOT NULL DEFAULT 3, + `autoplay_delay` INT(11) NOT NULL DEFAULT 0, `active` TINYINT(1) NOT NULL DEFAULT 1, `show_title` TINYINT(1) NOT NULL DEFAULT 0, `ordering` INT(11) NOT NULL DEFAULT 0, @@ -92,6 +100,23 @@ trait Install ); '; + $sqlToExec[] = ' + CREATE TABLE IF NOT EXISTS ' . _DB_PREFIX_ . 'ws_customhtml_slide ( + `id_ws_customhtml_slide` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, + `id_ws_customhtml` INT(11) NOT NULL, + `title` VARCHAR(256) NOT NULL DEFAULT \'\', + `description` TEXT, + `extra_description` TEXT, + `link` VARCHAR(512) NOT NULL DEFAULT \'\', + `link_text` VARCHAR(128) NOT NULL DEFAULT \'\', + `image` VARCHAR(255) NOT NULL DEFAULT \'\', + `ordering` INT(11) NOT NULL DEFAULT 0, + `date_add` DATETIME NOT NULL, + `date_upd` DATETIME NOT NULL, + KEY `id_ws_customhtml` (`id_ws_customhtml`) + ); + '; + foreach ($sqlToExec as $sqlBlock) { Db::getInstance()->execute(trim($sqlBlock)); } diff --git a/upgrade/upgrade-1.15.0.php b/upgrade/upgrade-1.15.0.php new file mode 100644 index 0000000..af84f6b --- /dev/null +++ b/upgrade/upgrade-1.15.0.php @@ -0,0 +1,21 @@ +executeS( + 'SHOW COLUMNS FROM `' . _DB_PREFIX_ . 'ws_customhtml` LIKE \'link_text\'' + ); + + if (!empty($existing)) { + return true; + } + + return Db::getInstance()->execute( + 'ALTER TABLE `' . _DB_PREFIX_ . 'ws_customhtml` + ADD COLUMN `link_text` VARCHAR(128) NOT NULL DEFAULT \'\' AFTER `link`' + ); +} diff --git a/upgrade/upgrade-1.16.0.php b/upgrade/upgrade-1.16.0.php new file mode 100644 index 0000000..f6428be --- /dev/null +++ b/upgrade/upgrade-1.16.0.php @@ -0,0 +1,48 @@ + 'DECIMAL(4,2) NOT NULL DEFAULT 1', + 'slides_per_view_tablet' => 'DECIMAL(4,2) NOT NULL DEFAULT 2', + 'slides_per_view_desktop' => 'DECIMAL(4,2) NOT NULL DEFAULT 3', + 'autoplay_delay' => 'INT(11) NOT NULL DEFAULT 0', + ]; + + foreach ($columnsToAdd as $column => $definition) { + $existing = Db::getInstance()->executeS( + 'SHOW COLUMNS FROM `' . _DB_PREFIX_ . 'ws_customhtml` LIKE \'' . $column . '\'' + ); + + if (!empty($existing)) { + continue; + } + + if (!Db::getInstance()->execute( + 'ALTER TABLE `' . _DB_PREFIX_ . 'ws_customhtml` ADD COLUMN `' . $column . '` ' . $definition + )) { + return false; + } + } + + return Db::getInstance()->execute( + 'CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'ws_customhtml_slide` ( + `id_ws_customhtml_slide` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, + `id_ws_customhtml` INT(11) NOT NULL, + `title` VARCHAR(256) NOT NULL DEFAULT \'\', + `description` TEXT, + `extra_description` TEXT, + `link` VARCHAR(512) NOT NULL DEFAULT \'\', + `link_text` VARCHAR(128) NOT NULL DEFAULT \'\', + `image` VARCHAR(255) NOT NULL DEFAULT \'\', + `ordering` INT(11) NOT NULL DEFAULT 0, + `date_add` DATETIME NOT NULL, + `date_upd` DATETIME NOT NULL, + KEY `id_ws_customhtml` (`id_ws_customhtml`) + )' + ); +} diff --git a/upgrade/upgrade-1.17.0.php b/upgrade/upgrade-1.17.0.php new file mode 100644 index 0000000..f3ef049 --- /dev/null +++ b/upgrade/upgrade-1.17.0.php @@ -0,0 +1,21 @@ +executeS( + 'SHOW COLUMNS FROM `' . _DB_PREFIX_ . 'ws_customhtml` LIKE \'slider_mode\'' + ); + + if (!empty($existing)) { + return true; + } + + return Db::getInstance()->execute( + 'ALTER TABLE `' . _DB_PREFIX_ . 'ws_customhtml` + ADD COLUMN `slider_mode` VARCHAR(16) NOT NULL DEFAULT \'always\' AFTER `timeout`' + ); +} diff --git a/views/css/front/customhtml.css b/views/css/front/customhtml.css index e20ab72..2a22a43 100644 --- a/views/css/front/customhtml.css +++ b/views/css/front/customhtml.css @@ -18,13 +18,13 @@ margin-bottom: 1rem; } -.customhtml-banner-image--gradient { +/* .customhtml-banner-image--gradient { width: 100%; height: 100%; position: absolute; background: transparent linear-gradient(180deg, #27272777 0%, #272727 100%) 0% 0% no-repeat padding-box; -} +} */ /* Alert */ .customhtml-alert { max-height: 500px; @@ -48,6 +48,11 @@ margin-right: 0.5rem; } +.customhtml-alert-btn { + margin-left: 0.75rem; + white-space: nowrap; +} + .customhtml-usp { gap: 0.5rem; color: inherit; @@ -63,6 +68,32 @@ font-weight: 500; } +/* Slider */ +.customhtml-slider-grid-item { + padding-left: 0.75rem; + padding-right: 0.75rem; + margin-bottom: 1rem; +} + +/* Home banners slider (migrated from ws_extrabanner's home-banners section) */ +.displayHomeBannersSlider--container .customhtml-banner-image { + min-height: 220px; + background-color: #f2f0e9; +} + +.displayHomeBannersSlider--container .customhtml-banner-content * { + color: #fff; +} + +.displayHomeBannersSlider--container .customhtml-banner-description, +.displayHomeBannersSlider--container .customhtml-banner-extra-description { + margin-bottom: 0; +} + +.displayHomeBannersSlider--container .customhtml-banner-extra-description { + font-weight: 500; +} + /* FAQ */ .accordion-icon-minus { display: block; diff --git a/views/js/admin/customhtmlblock_form.js b/views/js/admin/customhtmlblock_form.js new file mode 100644 index 0000000..f2ea562 --- /dev/null +++ b/views/js/admin/customhtmlblock_form.js @@ -0,0 +1,48 @@ +$(document).ready(function () { + window.prestashop.component.initComponents([ + 'TinyMCEEditor', + ]); + + function toggleBlockTypeFields() { + var type = $('#custom_html_block_type').val(); + + $('.customhtmlblock-type-html').hide(); + $('.customhtmlblock-type-faq').hide(); + $('.customhtmlblock-type-banner').hide(); + $('.customhtmlblock-type-alert').hide(); + $('.customhtmlblock-type-usp').hide(); + $('.customhtmlblock-type-slider').hide(); + $('.customhtmlblock-type-' + type).show(); + } + + $('#custom_html_block_type').on('change', toggleBlockTypeFields); + toggleBlockTypeFields(); + + function initRepeater(containerSelector, addButtonSelector, itemClass, removeButtonClass) { + var container = $(containerSelector); + var removeLabel = container.data('removeLabel'); + + $(addButtonSelector).on('click', function () { + var index = container.data('index'); + var prototype = container.data('prototype').replace(/__name__/g, index); + + var item = $('
{/if}