feat(main): Add alert type; add template override support

This commit is contained in:
2026-08-13 15:31:23 +02:00
parent ce480314f3
commit 34fb95a699
18 changed files with 202 additions and 8 deletions
+8
View File
@@ -14,6 +14,14 @@ class Config
'html' => 'HTML', 'html' => 'HTML',
'faq' => 'FAQ Accordion', 'faq' => 'FAQ Accordion',
'banner' => 'Banner', 'banner' => 'Banner',
'alert' => 'Alert',
];
const ALERT_STYLES = [
'success' => 'Success',
'info' => 'Info',
'warning' => 'Warning',
'danger' => 'Danger',
]; ];
const BANNER_IMAGE_ALLOWED_EXTENSIONS = [ const BANNER_IMAGE_ALLOWED_EXTENSIONS = [
@@ -27,6 +27,8 @@ final class CustomHtmlBlockDataHandler implements FormDataHandlerInterface
$block->description = (string) ($formData['description'] ?? ''); $block->description = (string) ($formData['description'] ?? '');
$block->extra_description = (string) ($formData['extra_description'] ?? ''); $block->extra_description = (string) ($formData['extra_description'] ?? '');
$block->link = (string) ($formData['link'] ?? ''); $block->link = (string) ($formData['link'] ?? '');
$block->alert_type = (string) ($formData['alert_type'] ?? 'info');
$block->timeout = (int) ($formData['timeout'] ?? 0);
$block->image = $this->saveUploadedImage($formData['image'] ?? null, ''); $block->image = $this->saveUploadedImage($formData['image'] ?? null, '');
$block->active = (int) $formData['active']; $block->active = (int) $formData['active'];
$block->ordering = $this->getNextOrdering($idGroup); $block->ordering = $this->getNextOrdering($idGroup);
@@ -57,6 +59,8 @@ final class CustomHtmlBlockDataHandler implements FormDataHandlerInterface
$block->description = (string) ($formData['description'] ?? ''); $block->description = (string) ($formData['description'] ?? '');
$block->extra_description = (string) ($formData['extra_description'] ?? ''); $block->extra_description = (string) ($formData['extra_description'] ?? '');
$block->link = (string) ($formData['link'] ?? ''); $block->link = (string) ($formData['link'] ?? '');
$block->alert_type = (string) ($formData['alert_type'] ?? 'info');
$block->timeout = (int) ($formData['timeout'] ?? 0);
$block->image = $this->saveUploadedImage($formData['image'] ?? null, $block->image); $block->image = $this->saveUploadedImage($formData['image'] ?? null, $block->image);
$block->active = (int) $formData['active']; $block->active = (int) $formData['active'];
@@ -28,6 +28,8 @@ final class CustomHtmlBlockDataProvider implements FormDataProviderInterface
'description' => $block->description, 'description' => $block->description,
'extra_description' => $block->extra_description, 'extra_description' => $block->extra_description,
'link' => $block->link, 'link' => $block->link,
'alert_type' => $block->alert_type,
'timeout' => (int) $block->timeout,
'faqs' => $this->loadFaqs((int) $id), 'faqs' => $this->loadFaqs((int) $id),
'active' => (bool) $block->active, 'active' => (bool) $block->active,
]; ];
@@ -46,6 +48,8 @@ final class CustomHtmlBlockDataProvider implements FormDataProviderInterface
'description' => '', 'description' => '',
'extra_description' => '', 'extra_description' => '',
'link' => '', 'link' => '',
'alert_type' => 'info',
'timeout' => 0,
'faqs' => [], 'faqs' => [],
'active' => true, 'active' => true,
]; ];
+15 -1
View File
@@ -7,6 +7,7 @@ use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use PrestaShop\PrestaShop\Core\Form\ConfigurableFormChoiceProviderInterface; use PrestaShop\PrestaShop\Core\Form\ConfigurableFormChoiceProviderInterface;
@@ -70,10 +71,23 @@ class CustomHtmlBlockType extends AbstractType
'prototype' => true, 'prototype' => true,
'required' => false, 'required' => false,
]) ])
->add('alert_type', ChoiceType::class, [
'label' => $this->translator->trans('Alert style', [], 'Modules.Wscustomhtml.Admin'),
'choices' => array_flip(Config::ALERT_STYLES),
'required' => false,
'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-alert'],
])
->add('timeout', IntegerType::class, [
'label' => $this->translator->trans('Auto-dismiss after (seconds)', [], 'Modules.Wscustomhtml.Admin'),
'required' => false,
'attr' => ['min' => 0, 'placeholder' => '0'],
'help' => $this->translator->trans('0 means the alert never auto-dismisses; visitors can still close it manually.', [], 'Modules.Wscustomhtml.Admin'),
'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-alert'],
])
->add('description', TextareaType::class, [ ->add('description', TextareaType::class, [
'label' => $this->translator->trans('Description', [], 'Modules.Wscustomhtml.Admin'), 'label' => $this->translator->trans('Description', [], 'Modules.Wscustomhtml.Admin'),
'required' => false, 'required' => false,
'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-banner'], 'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-banner customhtmlblock-type-alert'],
]) ])
->add('extra_description', TextareaType::class, [ ->add('extra_description', TextareaType::class, [
'label' => $this->translator->trans('Extra description', [], 'Modules.Wscustomhtml.Admin'), 'label' => $this->translator->trans('Extra description', [], 'Modules.Wscustomhtml.Admin'),
+9 -1
View File
@@ -37,6 +37,12 @@ class CustomHtmlBlock extends ObjectModel
/** @var string */ /** @var string */
public $image = ''; public $image = '';
/** @var string */
public $alert_type = 'info';
/** @var int */
public $timeout = 0;
/** @var int */ /** @var int */
public $active = 1; public $active = 1;
@@ -65,6 +71,8 @@ class CustomHtmlBlock extends ObjectModel
'extra_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' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 512],
'image' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 255], '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],
'active' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'active' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
'show_title' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'show_title' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
'ordering' => ['type' => self::TYPE_INT, 'validate' => 'isInt'], 'ordering' => ['type' => self::TYPE_INT, 'validate' => 'isInt'],
@@ -97,7 +105,7 @@ class CustomHtmlBlock extends ObjectModel
$result = Db::getInstance()->executeS( $result = Db::getInstance()->executeS(
'SELECT `id_ws_customhtml`, `id_ws_customhtml_group`, `title`, `type`, `content`, `custom_class`, 'SELECT `id_ws_customhtml`, `id_ws_customhtml_group`, `title`, `type`, `content`, `custom_class`,
`description`, `extra_description`, `link`, `image`, `active`, `show_title`, `ordering` `description`, `extra_description`, `link`, `image`, `alert_type`, `timeout`, `active`, `show_title`, `ordering`
FROM `' . _DB_PREFIX_ . 'ws_customhtml` FROM `' . _DB_PREFIX_ . 'ws_customhtml`
WHERE `id_ws_customhtml_group` IN (' . implode(',', $groupIds) . ') WHERE `id_ws_customhtml_group` IN (' . implode(',', $groupIds) . ')
AND `active` = 1 AND `active` = 1
+5
View File
@@ -15,6 +15,11 @@ trait DisplayHooks
'ws_customhtml_front', 'ws_customhtml_front',
'/modules/' . $this->name . '/views/css/front/customhtml.css' '/modules/' . $this->name . '/views/css/front/customhtml.css'
); );
$this->context->controller->registerJavascript(
'ws_customhtml_front',
'/modules/' . $this->name . '/views/js/front/customhtml.js'
);
} }
public function renderWidget($hookName = null, array $configuration = []) public function renderWidget($hookName = null, array $configuration = [])
+31
View File
@@ -0,0 +1,31 @@
<?php
if (!defined('_PS_VERSION_')) {
exit;
}
function upgrade_module_1_10_0($module)
{
$columnsToAdd = [
'alert_type' => 'VARCHAR(16) NOT NULL DEFAULT \'info\'',
'timeout' => '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 true;
}
+20
View File
@@ -0,0 +1,20 @@
<?php
if (!defined('_PS_VERSION_')) {
exit;
}
function upgrade_module_1_8_0($module)
{
$existing = Db::getInstance()->executeS(
'SHOW COLUMNS FROM `' . _DB_PREFIX_ . 'ws_customhtml` LIKE \'template_key\''
);
if (!empty($existing)) {
return true;
}
return Db::getInstance()->execute(
'ALTER TABLE `' . _DB_PREFIX_ . 'ws_customhtml` ADD COLUMN `template_key` VARCHAR(64) NOT NULL DEFAULT \'\''
);
}
+20
View File
@@ -0,0 +1,20 @@
<?php
if (!defined('_PS_VERSION_')) {
exit;
}
function upgrade_module_1_9_0($module)
{
$existing = Db::getInstance()->executeS(
'SHOW COLUMNS FROM `' . _DB_PREFIX_ . 'ws_customhtml` LIKE \'template_key\''
);
if (empty($existing)) {
return true;
}
return Db::getInstance()->execute(
'ALTER TABLE `' . _DB_PREFIX_ . 'ws_customhtml` DROP COLUMN `template_key`'
);
}
+10 -4
View File
@@ -18,21 +18,27 @@ td.action-type .btn-group-action {
max-width: unset !important; max-width: unset !important;
text-align: left !important; text-align: left !important;
} }
form[name="custom_html_block"] .card-body { form[name="custom_html_block"] .card-body,
form[name="custom_html_group"] .card-body {
padding: 25px 50px !important; padding: 25px 50px !important;
} }
form[name="custom_html_block"] .card-header, form[name="custom_html_block"] .card-header,
form[name="custom_html_block"] .card-footer { form[name="custom_html_block"] .card-footer,
form[name="custom_html_group"] .card-header,
form[name="custom_html_group"] .card-footer {
padding: 20px 50px !important; padding: 20px 50px !important;
} }
} }
@media (min-width: 1500px) { @media (min-width: 1500px) {
form[name="custom_html_block"] .card-body { form[name="custom_html_block"] .card-body,
form[name="custom_html_group"] .card-body {
padding: 25px 300px !important; padding: 25px 300px !important;
} }
form[name="custom_html_block"] .card-header, form[name="custom_html_block"] .card-header,
form[name="custom_html_block"] .card-footer { form[name="custom_html_block"] .card-footer,
form[name="custom_html_group"] .card-header,
form[name="custom_html_group"] .card-footer {
padding: 20px 300px !important; padding: 20px 300px !important;
} }
} }
+22
View File
@@ -15,3 +15,25 @@
.customhtml-banner-extra-description { .customhtml-banner-extra-description {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.customhtml-alert {
max-height: 500px;
overflow: hidden;
transition:
max-height 0.4s ease,
padding 0.4s ease,
margin 0.4s ease,
opacity 0.4s ease;
}
.customhtml-alert.customhtml-alert-collapsing {
max-height: 0;
padding-top: 0;
padding-bottom: 0;
margin-bottom: 0;
opacity: 0;
}
.customhtml-alert-title {
margin-right: 0.5rem;
}
+26
View File
@@ -0,0 +1,26 @@
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.customhtml-alert').forEach(function (alertEl) {
var timeout = parseInt(alertEl.getAttribute('data-timeout'), 10) || 0;
var timer = null;
var dismiss = function () {
alertEl.classList.add('customhtml-alert-collapsing');
};
if (timeout > 0) {
timer = setTimeout(dismiss, timeout * 1000);
}
var closeButton = alertEl.querySelector('.customhtml-alert-close');
if (closeButton) {
closeButton.addEventListener('click', function () {
if (timer) {
clearTimeout(timer);
}
dismiss();
});
}
});
});
@@ -59,6 +59,8 @@
</button> </button>
</div> </div>
{{ form_row(customHtmlBlockForm.alert_type) }}
{{ form_row(customHtmlBlockForm.timeout) }}
{{ form_row(customHtmlBlockForm.description) }} {{ form_row(customHtmlBlockForm.description) }}
{{ form_row(customHtmlBlockForm.extra_description) }} {{ form_row(customHtmlBlockForm.extra_description) }}
{{ form_row(customHtmlBlockForm.link) }} {{ form_row(customHtmlBlockForm.link) }}
@@ -20,6 +20,7 @@
$('.customhtmlblock-type-html').hide(); $('.customhtmlblock-type-html').hide();
$('.customhtmlblock-type-faq').hide(); $('.customhtmlblock-type-faq').hide();
$('.customhtmlblock-type-banner').hide(); $('.customhtmlblock-type-banner').hide();
$('.customhtmlblock-type-alert').hide();
$('.customhtmlblock-type-' + type).show(); $('.customhtmlblock-type-' + type).show();
} }
@@ -20,6 +20,7 @@
$('.customhtmlblock-type-html').hide(); $('.customhtmlblock-type-html').hide();
$('.customhtmlblock-type-faq').hide(); $('.customhtmlblock-type-faq').hide();
$('.customhtmlblock-type-banner').hide(); $('.customhtmlblock-type-banner').hide();
$('.customhtmlblock-type-alert').hide();
$('.customhtmlblock-type-' + type).show(); $('.customhtmlblock-type-' + type).show();
} }
+16
View File
@@ -0,0 +1,16 @@
{if !empty($block.description)}
<div
id="customhtml-alert-{$block.id_ws_customhtml|intval}"
class="alert alert-{$block.alert_type|escape:'html':'UTF-8'} alert-dismissible customhtml-alert"
role="alert"
data-timeout="{$block.timeout|intval}"
>
{if $block.show_title && !empty($block.title)}
<strong class="customhtml-alert-title">{$block.title|escape:'html':'UTF-8'}</strong>
{/if}
<span class="customhtml-alert-text">{$block.description|escape:'html':'UTF-8'|nl2br}</span>
<button type="button" class="close customhtml-alert-close" aria-label="{l s='Close' d='Modules.Wscustomhtml.Admin'}">
<span aria-hidden="true">&times;</span>
</button>
</div>
{/if}
+7 -1
View File
@@ -4,13 +4,19 @@
<div class="customhtml-group customhtml-group-{$group.id_ws_customhtml_group|intval} row"> <div class="customhtml-group customhtml-group-{$group.id_ws_customhtml_group|intval} row">
{foreach from=$group.blocks item="block"} {foreach from=$group.blocks item="block"}
<div class="customhtml-block col{if !empty($block.custom_class)} {$block.custom_class|escape:'html':'UTF-8'}{/if}"> <div class="customhtml-block col{if !empty($block.custom_class)} {$block.custom_class|escape:'html':'UTF-8'}{/if}">
{if $block.type == 'faq'} {assign var="customhtmlOverridePath" value="{$smarty.const._PS_THEME_DIR_}modules/ws_customhtml/views/templates/hook/{$hookName}/{$block.type}.tpl"}
{if file_exists($customhtmlOverridePath)}
{include file="module:ws_customhtml/views/templates/hook/`$hookName`/`$block.type`.tpl" block=$block group=$group hookName=$hookName}
{elseif $block.type == 'faq'}
{if $block.show_title && !empty($block.title)} {if $block.show_title && !empty($block.title)}
<div class="customhtml-title">{$block.title|escape:'html':'UTF-8'}</div> <div class="customhtml-title">{$block.title|escape:'html':'UTF-8'}</div>
{/if} {/if}
{include file="module:ws_customhtml/views/templates/hook/_faq_accordion.tpl" block=$block} {include file="module:ws_customhtml/views/templates/hook/_faq_accordion.tpl" block=$block}
{elseif $block.type == 'banner'} {elseif $block.type == 'banner'}
{include file="module:ws_customhtml/views/templates/hook/_banner.tpl" block=$block} {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}
{else} {else}
{if $block.show_title && !empty($block.title)} {if $block.show_title && !empty($block.title)}
<div class="customhtml-title">{$block.title|escape:'html':'UTF-8'}</div> <div class="customhtml-title">{$block.title|escape:'html':'UTF-8'}</div>
+1 -1
View File
@@ -29,7 +29,7 @@ class Ws_CustomHtml extends Module implements WidgetInterface
public function __construct() public function __construct()
{ {
$this->name = 'ws_customhtml'; $this->name = 'ws_customhtml';
$this->version = '1.7.0'; $this->version = '1.10.0';
$this->author = 'Isabelle Oving-Anno | De Websmid BV'; $this->author = 'Isabelle Oving-Anno | De Websmid BV';
$this->need_instance = 0; $this->need_instance = 0;
$this->bootstrap = true; $this->bootstrap = true;