From 06e98a429b50a74ddcea9b863cf2bdb78ab05f32 Mon Sep 17 00:00:00 2001 From: Isabelle Date: Thu, 20 Aug 2026 15:22:15 +0200 Subject: [PATCH] feat(main): Introduce USP block type and custom classes for HTML groups This release adds a new "USP" (Unique Selling Proposition) block type, allowing for flexible display of image-based selling points with optional links. Additionally, administrators can now assign custom CSS classes to Custom HTML Groups, enhancing layout and styling capabilities. Includes a refinement to the FAQ accordion template to ensure questions without answers do not display a collapse button, improving usability. Updates module version to 1.13.0 with a database upgrade script for the new `custom_class` column. --- src/Config/Config.php | 1 + .../CustomHtmlGroupDataHandler.php | 16 ++-- .../CustomHtmlGroupDataProvider.php | 14 +-- src/Form/Type/CustomHtmlBlockType.php | 6 +- src/Form/Type/CustomHtmlGroupType.php | 5 + .../CustomHtmlGroupGridDefinitionFactory.php | 5 + .../Query/CustomHtmlGroupQueryBuilder.php | 1 + src/Model/CustomHtmlGroup.php | 6 +- src/Module/DisplayHooks.php | 6 +- src/Module/Install.php | 1 + upgrade/upgrade-1.13.0.php | 21 ++++ views/css/front/customhtml.css | 15 +++ .../admin/form/blocks/block_form.html.twig | 2 +- .../form/customhtmlblock_create.html.twig | 1 + .../admin/form/customhtmlblock_edit.html.twig | 1 + views/templates/hook/_faq_accordion.tpl | 95 ++++++++++--------- views/templates/hook/_usp.tpl | 20 ++++ views/templates/hook/customhtml_block.tpl | 4 +- ws_customhtml.php | 2 +- 19 files changed, 156 insertions(+), 66 deletions(-) create mode 100644 upgrade/upgrade-1.13.0.php create mode 100644 views/templates/hook/_usp.tpl diff --git a/src/Config/Config.php b/src/Config/Config.php index 26e886d..ab943ea 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -15,6 +15,7 @@ class Config 'faq' => 'FAQ Accordion', 'banner' => 'Banner', 'alert' => 'Alert', + 'usp' => 'USP', ]; const ALERT_STYLES = [ diff --git a/src/Form/DataHandler/CustomHtmlGroupDataHandler.php b/src/Form/DataHandler/CustomHtmlGroupDataHandler.php index 8f86fe5..a21cd4f 100644 --- a/src/Form/DataHandler/CustomHtmlGroupDataHandler.php +++ b/src/Form/DataHandler/CustomHtmlGroupDataHandler.php @@ -15,10 +15,11 @@ final class CustomHtmlGroupDataHandler implements FormDataHandlerInterface public function create(array $formData) { $group = new CustomHtmlGroup(); - $group->title = (string) $formData['title']; - $group->hook_name = (string) $formData['hook_name']; - $group->active = (int) $formData['active']; - $group->ordering = $this->getNextOrdering(); + $group->title = (string) $formData['title']; + $group->hook_name = (string) $formData['hook_name']; + $group->custom_class = (string) ($formData['custom_class'] ?? ''); + $group->active = (int) $formData['active']; + $group->ordering = $this->getNextOrdering(); if (!$group->add()) { return false; @@ -46,9 +47,10 @@ final class CustomHtmlGroupDataHandler implements FormDataHandlerInterface return false; } - $group->title = (string) $formData['title']; - $group->hook_name = (string) $formData['hook_name']; - $group->active = (int) $formData['active']; + $group->title = (string) $formData['title']; + $group->hook_name = (string) $formData['hook_name']; + $group->custom_class = (string) ($formData['custom_class'] ?? ''); + $group->active = (int) $formData['active']; if (!$group->update()) { return false; diff --git a/src/Form/DataProvider/CustomHtmlGroupDataProvider.php b/src/Form/DataProvider/CustomHtmlGroupDataProvider.php index cb450b6..2739ad5 100644 --- a/src/Form/DataProvider/CustomHtmlGroupDataProvider.php +++ b/src/Form/DataProvider/CustomHtmlGroupDataProvider.php @@ -17,18 +17,20 @@ final class CustomHtmlGroupDataProvider implements FormDataProviderInterface } return [ - 'title' => $group->title, - 'hook_name' => $group->hook_name, - 'active' => (bool) $group->active, + 'title' => $group->title, + 'hook_name' => $group->hook_name, + 'custom_class' => $group->custom_class, + 'active' => (bool) $group->active, ]; } public function getDefaultData() { return [ - 'title' => '', - 'hook_name' => '', - 'active' => true, + 'title' => '', + 'hook_name' => '', + 'custom_class' => '', + 'active' => true, ]; } } diff --git a/src/Form/Type/CustomHtmlBlockType.php b/src/Form/Type/CustomHtmlBlockType.php index c6ef418..6b00201 100644 --- a/src/Form/Type/CustomHtmlBlockType.php +++ b/src/Form/Type/CustomHtmlBlockType.php @@ -58,9 +58,9 @@ class CustomHtmlBlockType extends AbstractType 'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-html'], ]) ->add('image', FileType::class, [ - 'label' => $this->translator->trans('Banner image', [], 'Modules.Wscustomhtml.Admin'), + 'label' => $this->translator->trans('Image', [], 'Modules.Wscustomhtml.Admin'), 'required' => false, - 'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-banner'], + 'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-banner customhtmlblock-type-usp'], ]) ->add('faqs', CollectionType::class, [ 'entry_type' => CustomHtmlFaqType::class, @@ -97,7 +97,7 @@ class CustomHtmlBlockType extends AbstractType 'label' => $this->translator->trans('Link', [], 'Modules.Wscustomhtml.Admin'), 'required' => false, 'attr' => ['placeholder' => 'https://example.com or /internal-page'], - 'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-banner'], + 'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-banner customhtmlblock-type-usp'], ]) ->add('custom_class', TextType::class, [ 'label' => $this->translator->trans('Custom classes', [], 'Modules.Wscustomhtml.Admin'), diff --git a/src/Form/Type/CustomHtmlGroupType.php b/src/Form/Type/CustomHtmlGroupType.php index 3a4858c..121132c 100644 --- a/src/Form/Type/CustomHtmlGroupType.php +++ b/src/Form/Type/CustomHtmlGroupType.php @@ -28,6 +28,11 @@ class CustomHtmlGroupType extends AbstractType 'label' => $this->translator->trans('Hook name', [], 'Modules.Wscustomhtml.Admin'), 'required' => true, ]) + ->add('custom_class', TextType::class, [ + 'label' => $this->translator->trans('Custom classes', [], 'Modules.Wscustomhtml.Admin'), + 'required' => false, + 'attr' => ['placeholder' => 'col-md-6'], + ]) ->add('active', SwitchType::class, [ 'label' => $this->translator->trans('Active', [], 'Modules.Wscustomhtml.Admin'), 'required' => false, diff --git a/src/Grid/Definition/Factory/CustomHtmlGroupGridDefinitionFactory.php b/src/Grid/Definition/Factory/CustomHtmlGroupGridDefinitionFactory.php index 3a47da8..2e96696 100644 --- a/src/Grid/Definition/Factory/CustomHtmlGroupGridDefinitionFactory.php +++ b/src/Grid/Definition/Factory/CustomHtmlGroupGridDefinitionFactory.php @@ -72,6 +72,11 @@ final class CustomHtmlGroupGridDefinitionFactory extends AbstractGridDefinitionF ->setName($this->trans('Hook', [], 'Modules.Wscustomhtml.Admin')) ->setOptions(['field' => 'hook_name']) ) + ->add( + (new DataColumn('custom_class')) + ->setName($this->trans('Custom classes', [], 'Modules.Wscustomhtml.Admin')) + ->setOptions(['field' => 'custom_class']) + ) ->add( (new DataColumn('blocks_count')) ->setName($this->trans('Blocks', [], 'Modules.Wscustomhtml.Admin')) diff --git a/src/Grid/Query/CustomHtmlGroupQueryBuilder.php b/src/Grid/Query/CustomHtmlGroupQueryBuilder.php index b9c1ac2..8f59875 100644 --- a/src/Grid/Query/CustomHtmlGroupQueryBuilder.php +++ b/src/Grid/Query/CustomHtmlGroupQueryBuilder.php @@ -32,6 +32,7 @@ final class CustomHtmlGroupQueryBuilder extends AbstractDoctrineQueryBuilder 'wchg.`id_ws_customhtml_group`', 'wchg.`title`', 'wchg.`hook_name`', + 'wchg.`custom_class`', 'wchg.`active`', 'wchg.`ordering`', 'wchg.`date_add`', diff --git a/src/Model/CustomHtmlGroup.php b/src/Model/CustomHtmlGroup.php index b219950..fd1377a 100644 --- a/src/Model/CustomHtmlGroup.php +++ b/src/Model/CustomHtmlGroup.php @@ -16,6 +16,9 @@ class CustomHtmlGroup extends ObjectModel /** @var string */ public $title = ''; + /** @var string */ + public $custom_class = ''; + /** @var int */ public $active = 1; @@ -34,6 +37,7 @@ class CustomHtmlGroup extends ObjectModel 'fields' => [ 'hook_name' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true, 'size' => 64], 'title' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true, 'size' => 256], + 'custom_class' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 128], 'active' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'ordering' => ['type' => self::TYPE_INT, 'validate' => 'isInt'], 'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'], @@ -44,7 +48,7 @@ class CustomHtmlGroup extends ObjectModel public static function getActiveByHookName(string $hookName): array { $result = Db::getInstance()->executeS( - 'SELECT `id_ws_customhtml_group`, `title`, `active`, `ordering` + 'SELECT `id_ws_customhtml_group`, `title`, `active`, `custom_class`, `ordering` FROM `' . _DB_PREFIX_ . 'ws_customhtml_group` WHERE `hook_name` = \'' . pSQL($hookName) . '\' AND `active` = 1 diff --git a/src/Module/DisplayHooks.php b/src/Module/DisplayHooks.php index 35b0eb2..f1d3a36 100644 --- a/src/Module/DisplayHooks.php +++ b/src/Module/DisplayHooks.php @@ -69,7 +69,7 @@ trait DisplayHooks $blocksByGroup = CustomHtmlBlock::getActiveByGroupIds(array_column($groups, 'id_ws_customhtml_group')); $blocksByGroup = $this->attachFaqsToBlocks($blocksByGroup); - $blocksByGroup = $this->attachBannerImageUrls($blocksByGroup); + $blocksByGroup = $this->attachImageUrls($blocksByGroup); $groups = array_map(static function (array $group) use ($blocksByGroup) { $group['blocks'] = $blocksByGroup[(int) $group['id_ws_customhtml_group']] ?? []; @@ -95,11 +95,11 @@ trait DisplayHooks }, $blocksByGroup); } - private function attachBannerImageUrls(array $blocksByGroup): array + private function attachImageUrls(array $blocksByGroup): array { return array_map(static function (array $blocks) { return array_map(static function (array $block) { - if ($block['type'] === 'banner') { + if (in_array($block['type'], ['banner', 'usp'], true)) { $block['image_url'] = CustomHtmlBlock::getBannerImageUrl($block['image']); } diff --git a/src/Module/Install.php b/src/Module/Install.php index f817864..1cac55b 100644 --- a/src/Module/Install.php +++ b/src/Module/Install.php @@ -50,6 +50,7 @@ trait Install `id_ws_customhtml_group` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, `hook_name` VARCHAR(64) NOT NULL, `title` VARCHAR(256) NOT NULL DEFAULT \'\', + `custom_class` VARCHAR(128) NOT NULL DEFAULT \'\', `active` TINYINT(1) NOT NULL DEFAULT 1, `ordering` INT(11) NOT NULL DEFAULT 0, `date_add` DATETIME NOT NULL, diff --git a/upgrade/upgrade-1.13.0.php b/upgrade/upgrade-1.13.0.php new file mode 100644 index 0000000..a6bcf42 --- /dev/null +++ b/upgrade/upgrade-1.13.0.php @@ -0,0 +1,21 @@ +executeS( + 'SHOW COLUMNS FROM `' . _DB_PREFIX_ . 'ws_customhtml_group` LIKE \'custom_class\'' + ); + + if (!empty($existing)) { + return true; + } + + return Db::getInstance()->execute( + 'ALTER TABLE `' . _DB_PREFIX_ . 'ws_customhtml_group` + ADD COLUMN `custom_class` VARCHAR(128) NOT NULL DEFAULT \'\' AFTER `title`' + ); +} diff --git a/views/css/front/customhtml.css b/views/css/front/customhtml.css index 679aa6c..e20ab72 100644 --- a/views/css/front/customhtml.css +++ b/views/css/front/customhtml.css @@ -48,6 +48,21 @@ margin-right: 0.5rem; } +.customhtml-usp { + gap: 0.5rem; + color: inherit; + text-decoration: none; +} + +.customhtml-usp-icon img { + max-height: 32px; + width: auto; +} + +.customhtml-usp-text { + font-weight: 500; +} + /* FAQ */ .accordion-icon-minus { display: block; diff --git a/views/templates/admin/form/blocks/block_form.html.twig b/views/templates/admin/form/blocks/block_form.html.twig index 66a6c7f..481fa0d 100644 --- a/views/templates/admin/form/blocks/block_form.html.twig +++ b/views/templates/admin/form/blocks/block_form.html.twig @@ -27,7 +27,7 @@ {{ form_row(customHtmlBlockForm.content) }} {% if currentImageUrl is defined and currentImageUrl %} -
+
diff --git a/views/templates/admin/form/customhtmlblock_create.html.twig b/views/templates/admin/form/customhtmlblock_create.html.twig index 608fe3e..643c9db 100644 --- a/views/templates/admin/form/customhtmlblock_create.html.twig +++ b/views/templates/admin/form/customhtmlblock_create.html.twig @@ -21,6 +21,7 @@ $('.customhtmlblock-type-faq').hide(); $('.customhtmlblock-type-banner').hide(); $('.customhtmlblock-type-alert').hide(); + $('.customhtmlblock-type-usp').hide(); $('.customhtmlblock-type-' + type).show(); } diff --git a/views/templates/admin/form/customhtmlblock_edit.html.twig b/views/templates/admin/form/customhtmlblock_edit.html.twig index 9a184f2..a4289b8 100644 --- a/views/templates/admin/form/customhtmlblock_edit.html.twig +++ b/views/templates/admin/form/customhtmlblock_edit.html.twig @@ -21,6 +21,7 @@ $('.customhtmlblock-type-faq').hide(); $('.customhtmlblock-type-banner').hide(); $('.customhtmlblock-type-alert').hide(); + $('.customhtmlblock-type-usp').hide(); $('.customhtmlblock-type-' + type).show(); } diff --git a/views/templates/hook/_faq_accordion.tpl b/views/templates/hook/_faq_accordion.tpl index bbbccfa..572c83a 100644 --- a/views/templates/hook/_faq_accordion.tpl +++ b/views/templates/hook/_faq_accordion.tpl @@ -1,48 +1,57 @@ {if !empty($block.faqs)} -
- {foreach from=$block.faqs item="faq" name="customhtmlFaqLoop"} -
-
- + {/if} +
- -
-
-
- {$faq.answer nofilter} + {if !empty($faq.answer)} +
+
+ {$faq.answer nofilter} +
+
+ {/if}
-
+ {/foreach}
- {/foreach} -
{/if} diff --git a/views/templates/hook/_usp.tpl b/views/templates/hook/_usp.tpl new file mode 100644 index 0000000..b1a7500 --- /dev/null +++ b/views/templates/hook/_usp.tpl @@ -0,0 +1,20 @@ +{if !empty($block.image_url) || ($block.show_title && !empty($block.title))} +{if !empty($block.link)} + +{else} +
+{/if} + {if !empty($block.image_url)} + + + + {/if} + {if $block.show_title && !empty($block.title)} + {$block.title|escape:'html':'UTF-8'} + {/if} +{if !empty($block.link)} + +{else} +
+{/if} +{/if} diff --git a/views/templates/hook/customhtml_block.tpl b/views/templates/hook/customhtml_block.tpl index 899201a..f73bb30 100644 --- a/views/templates/hook/customhtml_block.tpl +++ b/views/templates/hook/customhtml_block.tpl @@ -1,7 +1,7 @@ {if !empty($groupList)}
{foreach from=$groupList item="group"} -
+
{foreach from=$group.blocks item="block"}
{assign var="customhtmlOverridePath" value="{$smarty.const._PS_THEME_DIR_}modules/ws_customhtml/views/templates/hook/{$hookName}/{$block.type}.tpl"} @@ -17,6 +17,8 @@ {include file="module:ws_customhtml/views/templates/hook/_banner.tpl" block=$block} {elseif $block.type == 'alert'} {include file="module:ws_customhtml/views/templates/hook/_alert.tpl" block=$block} + {elseif $block.type == 'usp'} + {include file="module:ws_customhtml/views/templates/hook/_usp.tpl" block=$block} {else} {if $block.show_title && !empty($block.title)}
{$block.title|escape:'html':'UTF-8'}
diff --git a/ws_customhtml.php b/ws_customhtml.php index 31739e3..331282c 100644 --- a/ws_customhtml.php +++ b/ws_customhtml.php @@ -29,7 +29,7 @@ class Ws_CustomHtml extends Module implements WidgetInterface public function __construct() { $this->name = 'ws_customhtml'; - $this->version = '1.11.0'; + $this->version = '1.13.0'; $this->author = 'Isabelle Oving-Anno | De Websmid BV'; $this->need_instance = 0; $this->bootstrap = true;