feat(main): Add banner option

This commit is contained in:
2026-08-13 13:59:08 +02:00
parent fb641fc236
commit ce480314f3
22 changed files with 316 additions and 39 deletions
@@ -3,9 +3,12 @@
namespace Websmid\CustomHtml\Form\DataHandler;
use PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataHandler\FormDataHandlerInterface;
use Websmid\CustomHtml\Config\Config;
use Websmid\CustomHtml\Model\CustomHtmlBlock;
use Websmid\CustomHtml\Model\CustomHtmlFaq;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Db;
use Exception;
use Validate;
final class CustomHtmlBlockDataHandler implements FormDataHandlerInterface
@@ -21,6 +24,10 @@ final class CustomHtmlBlockDataHandler implements FormDataHandlerInterface
$block->show_title = (int) $formData['show_title'];
$block->custom_class = (string) ($formData['custom_class'] ?? '');
$block->content = (string) $formData['content'];
$block->description = (string) ($formData['description'] ?? '');
$block->extra_description = (string) ($formData['extra_description'] ?? '');
$block->link = (string) ($formData['link'] ?? '');
$block->image = $this->saveUploadedImage($formData['image'] ?? null, '');
$block->active = (int) $formData['active'];
$block->ordering = $this->getNextOrdering($idGroup);
@@ -47,6 +54,10 @@ final class CustomHtmlBlockDataHandler implements FormDataHandlerInterface
$block->show_title = (int) $formData['show_title'];
$block->custom_class = (string) ($formData['custom_class'] ?? '');
$block->content = (string) $formData['content'];
$block->description = (string) ($formData['description'] ?? '');
$block->extra_description = (string) ($formData['extra_description'] ?? '');
$block->link = (string) ($formData['link'] ?? '');
$block->image = $this->saveUploadedImage($formData['image'] ?? null, $block->image);
$block->active = (int) $formData['active'];
if (!$block->update()) {
@@ -58,6 +69,49 @@ final class CustomHtmlBlockDataHandler implements FormDataHandlerInterface
return true;
}
/**
* Validates and stores an uploaded banner image, replacing the previous
* one if any. Never trusts the client-supplied filename or MIME header —
* the real MIME type is detected server-side and a random filename is
* generated, so nothing from the upload itself ends up on disk.
*/
private function saveUploadedImage(?UploadedFile $file, string $currentFilename): string
{
if (null === $file) {
return $currentFilename;
}
if (!$file->isValid()) {
throw new Exception('The uploaded image could not be read.');
}
if ($file->getSize() > Config::BANNER_IMAGE_MAX_SIZE) {
throw new Exception('The uploaded image is too large (max 5 MB).');
}
$mimeType = $file->getMimeType();
$extension = array_search($mimeType, Config::BANNER_IMAGE_ALLOWED_EXTENSIONS, true);
if (false === $extension) {
throw new Exception('The uploaded file is not a supported image type (jpg, png, gif, webp).');
}
$targetDir = CustomHtmlBlock::getBannerImageDir();
if (!is_dir($targetDir) && !mkdir($targetDir, 0755, true) && !is_dir($targetDir)) {
throw new Exception('Unable to create the banner image directory.');
}
$newFilename = bin2hex(random_bytes(16)) . '.' . $extension;
$file->move($targetDir, $newFilename);
if (!empty($currentFilename) && is_file($targetDir . $currentFilename)) {
unlink($targetDir . $currentFilename);
}
return $newFilename;
}
private function getNextOrdering(int $idGroup): int
{
$maxOrdering = Db::getInstance()->getValue(
@@ -24,6 +24,10 @@ final class CustomHtmlBlockDataProvider implements FormDataProviderInterface
'show_title' => (bool) $block->show_title,
'custom_class' => $block->custom_class,
'content' => $block->content,
'image' => null,
'description' => $block->description,
'extra_description' => $block->extra_description,
'link' => $block->link,
'faqs' => $this->loadFaqs((int) $id),
'active' => (bool) $block->active,
];
@@ -38,6 +42,10 @@ final class CustomHtmlBlockDataProvider implements FormDataProviderInterface
'show_title' => false,
'custom_class' => '',
'content' => '',
'image' => null,
'description' => '',
'extra_description' => '',
'link' => '',
'faqs' => [],
'active' => true,
];
+33 -9
View File
@@ -6,6 +6,7 @@ use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
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\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use PrestaShop\PrestaShop\Core\Form\ConfigurableFormChoiceProviderInterface;
@@ -35,23 +36,20 @@ class CustomHtmlBlockType extends AbstractType
'choices' => $this->groupChoiceProvider->getChoices(),
'required' => true,
])
->add('title', TextType::class, [
'label' => $this->translator->trans('Title', [], 'Modules.Wscustomhtml.Admin'),
'required' => true,
])
->add('type', ChoiceType::class, [
'label' => $this->translator->trans('Block type', [], 'Modules.Wscustomhtml.Admin'),
'choices' => array_flip(Config::BLOCK_TYPES),
'required' => true,
])
->add('title', TextType::class, [
'label' => $this->translator->trans('Title', [], 'Modules.Wscustomhtml.Admin'),
'required' => true,
'row_attr' => ['class' => 'col-8'],
])
->add('show_title', SwitchType::class, [
'label' => $this->translator->trans('Show title', [], 'Modules.Wscustomhtml.Admin'),
'required' => false,
])
->add('custom_class', TextType::class, [
'label' => $this->translator->trans('Custom classes', [], 'Modules.Wscustomhtml.Admin'),
'required' => false,
'attr' => ['placeholder' => 'col-md-6'],
'row_attr' => ['class' => 'col-3'],
])
->add('content', TextareaType::class, [
'label' => $this->translator->trans('Content', [], 'Modules.Wscustomhtml.Admin'),
@@ -59,6 +57,11 @@ class CustomHtmlBlockType extends AbstractType
'attr' => ['class' => 'autoload_rte'],
'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-html'],
])
->add('image', FileType::class, [
'label' => $this->translator->trans('Banner image', [], 'Modules.Wscustomhtml.Admin'),
'required' => false,
'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-banner'],
])
->add('faqs', CollectionType::class, [
'entry_type' => CustomHtmlFaqType::class,
'allow_add' => true,
@@ -67,6 +70,27 @@ class CustomHtmlBlockType extends AbstractType
'prototype' => true,
'required' => false,
])
->add('description', TextareaType::class, [
'label' => $this->translator->trans('Description', [], 'Modules.Wscustomhtml.Admin'),
'required' => false,
'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-banner'],
])
->add('extra_description', TextareaType::class, [
'label' => $this->translator->trans('Extra description', [], 'Modules.Wscustomhtml.Admin'),
'required' => false,
'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-banner'],
])
->add('link', TextType::class, [
'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'],
])
->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,