Compare commits

..
17 Commits
Author SHA1 Message Date
isabelle e97af7a1da fix(mian): Add silence files; add casting
Everything now casts right at the SQL interpolation point
2026-09-10 11:46:36 +02:00
isabelle 150e068550 feat(main): Remove dead code 2026-09-10 09:43:00 +02:00
isabelle a71ab197ce fix(main): Fix pagination 2026-09-09 16:56:25 +02:00
isabelle ccd105907a feat(main): Dutch translation XLF files 2026-09-09 14:20:56 +02:00
isabelle 4e7333493a feat(main): Make FAQ and slide entities translatable 2026-09-09 13:17:58 +02:00
isabelle 7cafc376b7 feat(main): Made group and blocks translatable.
Still need to do slide and FAQ entities
2026-09-09 11:56:18 +02:00
isabelle 85b56f1a13 fix(main): Separate slide tpl from banner tpl
also added a toggle column for active in the group view
2026-09-08 18:05:59 +02:00
isabelle 620c34e353 feat(main: Added default block classes 2026-09-08 16:36:39 +02:00
isabelle be9a676d9d feat(main): Add slider block type 2026-08-25 14:32:21 +02:00
isabelle 06e98a429b 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.
2026-08-20 15:22:15 +02:00
isabelle a4e479490b feat(main): Small adjustments
Css adjustments for certain blocks; module instructions compliance
2026-08-19 15:10:52 +02:00
isabelle e804c37175 feat(main): Make groups reorderable 2026-08-13 16:42:17 +02:00
isabelle 34fb95a699 feat(main): Add alert type; add template override support 2026-08-13 15:31:23 +02:00
isabelle ce480314f3 feat(main): Add banner option 2026-08-13 13:59:08 +02:00
isabelle fb641fc236 feat(main): Add custom CSS classes to HTML blocks
This feature allows administrators to define custom CSS classes for individual HTML blocks.
It enhances styling flexibility and integrates with common grid systems (e.g., Bootstrap `col-md-6`)
by applying user-defined classes to the block's wrapper element.

Includes:
- A new `custom_class` field in the block model, form, and data handlers.
- Database schema updates with an upgrade path to handle potential renames from `column_class`.
- Frontend rendering to apply the classes.
- Registration of `actionFrontControllerSetMedia` hook to load front-end CSS.
- Basic responsive image styling for blocks.
2026-08-13 11:33:24 +02:00
isabelle d0da05845a feat(main): Draggable blocks 2026-08-12 15:39:07 +02:00
isabelle 1493081602 feat(main): Add FAQ accordion 2026-08-11 13:25:06 +02:00
87 changed files with 2570 additions and 153 deletions
+2
View File
@@ -1 +1,3 @@
/vendor/
/views/img/banners/*
!/views/img/banners/.gitkeep
+16 -2
View File
@@ -2,7 +2,7 @@ services:
websmid.customhtml.form.type.customhtml_group:
public: true
class: 'Websmid\CustomHtml\Form\Type\CustomHtmlGroupType'
parent: 'form.type.translatable.aware'
parent: "form.type.translatable.aware"
tags:
- { name: form.type }
@@ -33,10 +33,24 @@ services:
arguments:
- '@websmid.customhtml.form.customhtmlgroup_data_handler'
websmid.customhtml.form.type.customhtml_faq:
public: true
class: 'Websmid\CustomHtml\Form\Type\CustomHtmlFaqType'
parent: "form.type.translatable.aware"
tags:
- { name: form.type }
websmid.customhtml.form.type.customhtml_slide:
public: true
class: 'Websmid\CustomHtml\Form\Type\CustomHtmlSlideType'
parent: "form.type.translatable.aware"
tags:
- { name: form.type }
websmid.customhtml.form.type.customhtml_block:
public: true
class: 'Websmid\CustomHtml\Form\Type\CustomHtmlBlockType'
parent: 'form.type.translatable.aware'
parent: "form.type.translatable.aware"
arguments:
- '@websmid.customhtml.form.choice_provider.customhtml_group_choice_provider'
tags:
+20
View File
@@ -34,6 +34,16 @@ services:
- '@prestashop.core.grid.filter.form_factory'
- '@prestashop.core.hook.dispatcher'
websmid.customhtml.grid.position.customhtmlgroup_position_definition:
public: true
class: 'PrestaShop\PrestaShop\Core\Grid\Position\PositionDefinition'
arguments:
- 'ws_customhtml_group'
- 'id_ws_customhtml_group'
- 'ordering'
- ~
- 0
websmid.customhtml.grid.definition.factory.customhtmlblock_definition_factory:
public: true
class: 'Websmid\CustomHtml\Grid\Definition\Factory\CustomHtmlBlockGridDefinitionFactory'
@@ -68,3 +78,13 @@ services:
- '@websmid.customhtml.grid.data.factory.customhtmlblock_data_factory'
- '@prestashop.core.grid.filter.form_factory'
- '@prestashop.core.hook.dispatcher'
websmid.customhtml.grid.position.customhtmlblock_position_definition:
public: true
class: 'PrestaShop\PrestaShop\Core\Grid\Position\PositionDefinition'
arguments:
- 'ws_customhtml'
- 'id_ws_customhtml'
- 'ordering'
- 'id_ws_customhtml_group'
- 0
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+18
View File
@@ -39,6 +39,18 @@ customhtmlgroup_delete:
defaults:
_controller: 'Websmid\CustomHtml\Controller\CustomHtmlGroupController::deleteAction'
customhtmlgroup_toggle:
path: ws_customhtml/group/toggle/{idCustomHtmlGroup}
methods: [GET, POST]
defaults:
_controller: 'Websmid\CustomHtml\Controller\CustomHtmlGroupController::toggleAction'
customhtmlgroup_update_position:
path: ws_customhtml/group/update-position
methods: [POST]
defaults:
_controller: 'Websmid\CustomHtml\Controller\CustomHtmlGroupController::updatePositionAction'
customhtmlblock_index:
path: ws_customhtml/block
methods: [GET]
@@ -71,3 +83,9 @@ customhtmlblock_delete:
methods: [DELETE, GET, POST]
defaults:
_controller: 'Websmid\CustomHtml\Controller\CustomHtmlBlockController::deleteAction'
customhtmlblock_update_position:
path: ws_customhtml/block/{idGroup}/update-position
methods: [POST]
defaults:
_controller: 'Websmid\CustomHtml\Controller\CustomHtmlBlockController::updatePositionAction'
+39 -4
View File
@@ -6,14 +6,49 @@ class Config
{
const HOOKS = [
'actionAdminControllerSetMedia',
'actionCustomhtmlblockGridDataModifier',
'actionFrontControllerSetMedia',
];
const ADMIN_CONTROLLERS = [
'AdminCustomHtmlParent',
'AdminCustomHtmlOverview',
'AdminCustomHtmlBlockOverview',
const BLOCK_TYPES = [
'html' => 'HTML',
'faq' => 'FAQ Accordion',
'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_MODES = [
'always' => 'Always show as a slider',
'responsive' => 'Mobile only',
];
const SLIDER_DEFAULT_MODE = 'always';
const SLIDER_RESPONSIVE_BREAKPOINT = 480;
const ALERT_STYLES = [
'success' => 'Success',
'info' => 'Info',
'warning' => 'Warning',
'danger' => 'Danger',
];
const BANNER_IMAGE_ALLOWED_EXTENSIONS = [
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'webp' => 'image/webp',
];
const BANNER_IMAGE_MAX_SIZE = 5242880;
// Order matters: the parent tab must be registered before its children so
// Tab::getIdFromClassName('AdminCustomHtmlParent') resolves during install.
const TABS = [
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+55 -10
View File
@@ -3,6 +3,9 @@
namespace Websmid\CustomHtml\Controller;
use Exception;
use PrestaShop\PrestaShop\Core\Grid\Position\Exception\PositionUpdateException;
use PrestaShop\PrestaShop\Core\Grid\Position\GridPositionUpdaterInterface;
use PrestaShop\PrestaShop\Core\Grid\Position\PositionUpdateFactoryInterface;
use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
use PrestaShopBundle\Security\Voter\PageVoter;
use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -11,14 +14,18 @@ use Symfony\Component\HttpFoundation\Response;
use Websmid\CustomHtml\Grid\Definition\Factory\CustomHtmlBlockGridDefinitionFactory;
use Websmid\CustomHtml\Grid\Filters\CustomHtmlBlockFilters;
use Websmid\CustomHtml\Model\CustomHtmlBlock;
use Websmid\CustomHtml\Model\CustomHtmlFaq;
use Validate;
class CustomHtmlBlockController extends FrameworkBundleAdminController
{
public function indexAction(Request $request, CustomHtmlBlockFilters $filters): Response
{
$idGroup = null;
if ($request->query->has('idGroup')) {
$filters->addFilter(['id_ws_customhtml_group' => (int) $request->query->get('idGroup')]);
$idGroup = (int) $request->query->get('idGroup');
$filters->addFilter(['id_ws_customhtml_group' => $idGroup]);
$filters->setNeedsToBePersisted(false);
}
@@ -27,7 +34,7 @@ class CustomHtmlBlockController extends FrameworkBundleAdminController
return $this->render('@Modules/ws_customhtml/views/templates/admin/grid/customhtmlblock.html.twig', [
'customHtmlBlockGrid' => $this->presentGrid($grid),
'layoutHeaderToolbarBtn' => $this->getToolbarButtons($request->get('_legacy_controller')),
'layoutHeaderToolbarBtn' => $this->getToolbarButtons($request->get('_legacy_controller'), $idGroup),
]);
}
@@ -47,9 +54,11 @@ class CustomHtmlBlockController extends FrameworkBundleAdminController
$formHandler = $this->get('websmid.customhtml.form.customhtmlblock_form_handler');
$form = $formBuilder->getForm();
$idGroup = $request->query->has('idGroup') ? (int) $request->query->get('idGroup') : null;
$redirectParams = null !== $idGroup ? ['idGroup' => $idGroup] : [];
if ($request->isMethod('GET') && $request->query->has('idGroup')) {
$form->get('id_ws_customhtml_group')->setData((int) $request->query->get('idGroup'));
if ($request->isMethod('GET') && null !== $idGroup) {
$form->get('id_ws_customhtml_group')->setData($idGroup);
}
$form->handleRequest($request);
@@ -60,7 +69,7 @@ class CustomHtmlBlockController extends FrameworkBundleAdminController
if (null !== $handlerResult->getIdentifiableObjectId()) {
$this->addFlash('success', $this->trans('Successful creation.', 'Admin.Notifications.Success'));
return $this->redirectToRoute('customhtmlblock_index');
return $this->redirectToRoute('customhtmlblock_index', $redirectParams);
}
} catch (Exception $e) {
$this->addFlash('error', $this->getErrorMessageForException($e, []));
@@ -68,6 +77,7 @@ class CustomHtmlBlockController extends FrameworkBundleAdminController
return $this->render('@Modules/ws_customhtml/views/templates/admin/form/customhtmlblock_create.html.twig', [
'customHtmlBlockForm' => $form->createView(),
'idGroup' => $idGroup,
]);
}
@@ -77,6 +87,8 @@ class CustomHtmlBlockController extends FrameworkBundleAdminController
$formHandler = $this->get('websmid.customhtml.form.customhtmlblock_form_handler');
$form = $formBuilder->getFormFor($idCustomHtmlBlock);
$idGroup = $request->query->has('idGroup') ? (int) $request->query->get('idGroup') : null;
$redirectParams = null !== $idGroup ? ['idGroup' => $idGroup] : [];
try {
$form->handleRequest($request);
@@ -85,35 +97,66 @@ class CustomHtmlBlockController extends FrameworkBundleAdminController
if ($handlerResult->isSubmitted() && $handlerResult->isValid()) {
$this->addFlash('success', $this->trans('Successful update.', 'Admin.Notifications.Success'));
return $this->redirectToRoute('customhtmlblock_index');
return $this->redirectToRoute('customhtmlblock_index', $redirectParams);
}
} catch (Exception $e) {
$this->addFlash('error', $this->getErrorMessageForException($e, []));
}
$block = new CustomHtmlBlock($idCustomHtmlBlock);
$currentImageUrl = Validate::isLoadedObject($block) ? CustomHtmlBlock::getBannerImageUrl($block->image) : '';
return $this->render('@Modules/ws_customhtml/views/templates/admin/form/customhtmlblock_edit.html.twig', [
'customHtmlBlockForm' => $form->createView(),
'idCustomHtmlBlock' => $idCustomHtmlBlock,
'currentImageUrl' => $currentImageUrl,
'idGroup' => $idGroup,
]);
}
public function deleteAction(Request $request, int $idCustomHtmlBlock): RedirectResponse
{
$idGroup = $request->query->has('idGroup') ? (int) $request->query->get('idGroup') : null;
$redirectParams = null !== $idGroup ? ['idGroup' => $idGroup] : [];
$block = new CustomHtmlBlock($idCustomHtmlBlock);
if (!Validate::isLoadedObject($block)) {
$this->addFlash('error', $this->trans('Unable to find the block.', 'Modules.Wscustomhtml.Admin'));
return $this->redirectToRoute('customhtmlblock_index');
return $this->redirectToRoute('customhtmlblock_index', $redirectParams);
}
CustomHtmlFaq::deleteByBlockId($idCustomHtmlBlock);
$block->delete();
$this->addFlash('success', $this->trans('Successful deletion.', 'Admin.Notifications.Success'));
return $this->redirectToRoute('customhtmlblock_index');
return $this->redirectToRoute('customhtmlblock_index', $redirectParams);
}
private function getToolbarButtons(?string $securitySubject): array
public function updatePositionAction(int $idGroup, Request $request): RedirectResponse
{
$positionDefinition = $this->get('websmid.customhtml.grid.position.customhtmlblock_position_definition');
try {
$positionUpdateFactory = $this->get(PositionUpdateFactoryInterface::class);
$positionUpdate = $positionUpdateFactory->buildPositionUpdate([
'positions' => $request->request->all('positions'),
'parentId' => $idGroup,
], $positionDefinition);
$updater = $this->get(GridPositionUpdaterInterface::class);
$updater->update($positionUpdate);
$this->addFlash('success', $this->trans('Successful update.', 'Admin.Notifications.Success'));
} catch (PositionUpdateException $e) {
$this->addFlash('error', $this->getErrorMessageForException($e, []));
}
return $this->redirectToRoute('customhtmlblock_index', ['idGroup' => $idGroup]);
}
private function getToolbarButtons(?string $securitySubject, ?int $idGroup = null): array
{
$toolbarButtons = [];
@@ -121,8 +164,10 @@ class CustomHtmlBlockController extends FrameworkBundleAdminController
return $toolbarButtons;
}
$routeParams = null !== $idGroup ? ['idGroup' => $idGroup] : [];
$toolbarButtons['customhtmlblock_create'] = [
'href' => $this->generateUrl('customhtmlblock_create'),
'href' => $this->generateUrl('customhtmlblock_create', $routeParams),
'desc' => $this->trans('New block', 'Admin.Actions'),
'icon' => 'add_circle_outline',
'class' => 'btn-primary',
@@ -3,6 +3,9 @@
namespace Websmid\CustomHtml\Controller;
use Exception;
use PrestaShop\PrestaShop\Core\Grid\Position\Exception\PositionUpdateException;
use PrestaShop\PrestaShop\Core\Grid\Position\GridPositionUpdaterInterface;
use PrestaShop\PrestaShop\Core\Grid\Position\PositionUpdateFactoryInterface;
use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
use PrestaShopBundle\Security\Voter\PageVoter;
use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -103,6 +106,48 @@ class CustomHtmlGroupController extends FrameworkBundleAdminController
return $this->redirectToRoute('customhtmlgroup_index');
}
public function toggleAction(int $idCustomHtmlGroup): RedirectResponse
{
$group = new CustomHtmlGroup($idCustomHtmlGroup);
if (!Validate::isLoadedObject($group)) {
$this->addFlash('error', $this->trans('Unable to find the group.', 'Modules.Wscustomhtml.Admin'));
return $this->redirectToRoute('customhtmlgroup_index');
}
$group->active = (int) !$group->active;
if (!$group->update()) {
$this->addFlash('error', $this->trans('An error occurred while updating the status.', 'Admin.Notifications.Error'));
} else {
$this->addFlash('success', $this->trans('Successful update.', 'Admin.Notifications.Success'));
}
return $this->redirectToRoute('customhtmlgroup_index');
}
public function updatePositionAction(Request $request): RedirectResponse
{
$positionDefinition = $this->get('websmid.customhtml.grid.position.customhtmlgroup_position_definition');
try {
$positionUpdateFactory = $this->get(PositionUpdateFactoryInterface::class);
$positionUpdate = $positionUpdateFactory->buildPositionUpdate([
'positions' => $request->request->all('positions'),
], $positionDefinition);
$updater = $this->get(GridPositionUpdaterInterface::class);
$updater->update($positionUpdate);
$this->addFlash('success', $this->trans('Successful update.', 'Admin.Notifications.Success'));
} catch (PositionUpdateException $e) {
$this->addFlash('error', $this->getErrorMessageForException($e, []));
}
return $this->redirectToRoute('customhtmlgroup_index');
}
private function getToolbarButtons(?string $securitySubject): array
{
$toolbarButtons = [];
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
@@ -5,6 +5,7 @@ namespace Websmid\CustomHtml\Form\ChoiceProvider;
use PrestaShop\PrestaShop\Core\Form\ConfigurableFormChoiceProviderInterface;
use PrestaShop\PrestaShop\Core\Form\FormChoiceAttributeProviderInterface;
use PrestaShop\PrestaShop\Core\Form\FormChoiceProviderInterface;
use Context;
use Db;
final class CustomHtmlGroupChoiceProvider implements
@@ -14,16 +15,10 @@ final class CustomHtmlGroupChoiceProvider implements
{
public function getChoices(array $options = []): array
{
$groups = Db::getInstance()->executeS(
'SELECT `id_ws_customhtml_group`, `title`
FROM `' . _DB_PREFIX_ . 'ws_customhtml_group`
ORDER BY `title` ASC'
);
$choices = [];
foreach ($groups as $group) {
$choices[$group['title']] = (int) $group['id_ws_customhtml_group'];
foreach ($this->getGroupsWithLabels() as $label => $group) {
$choices[$label] = (int) $group['id_ws_customhtml_group'];
}
return $choices;
@@ -31,6 +26,42 @@ final class CustomHtmlGroupChoiceProvider implements
public function getChoicesAttributes(): array
{
return [];
$choicesAttributes = [];
foreach ($this->getGroupsWithLabels() as $label => $group) {
if (!empty($group['default_block_class'])) {
$choicesAttributes[$label]['data-default-block-class'] = $group['default_block_class'];
}
}
return $choicesAttributes;
}
private function getGroupsWithLabels(): array
{
$idLang = (int) Context::getContext()->language->id;
$groups = Db::getInstance()->executeS(
'SELECT g.`id_ws_customhtml_group`, gl.`title`, g.`default_block_class`
FROM `' . _DB_PREFIX_ . 'ws_customhtml_group` g
LEFT JOIN `' . _DB_PREFIX_ . 'ws_customhtml_group_lang` gl
ON gl.`id_ws_customhtml_group` = g.`id_ws_customhtml_group` AND gl.`id_lang` = ' . (int) $idLang . '
ORDER BY gl.`title` ASC'
);
$titleCounts = array_count_values(array_column($groups, 'title'));
$groupsWithLabels = [];
foreach ($groups as $group) {
$label = $group['title'];
if ($titleCounts[$label] > 1) {
$label .= ' (#' . $group['id_ws_customhtml_group'] . ')';
}
$groupsWithLabels[$label] = $group;
}
return $groupsWithLabels;
}
}
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
@@ -3,25 +3,50 @@
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 Websmid\CustomHtml\Model\CustomHtmlSlide;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Db;
use Exception;
use Validate;
final class CustomHtmlBlockDataHandler implements FormDataHandlerInterface
{
public function create(array $formData)
{
$idGroup = (int) $formData['id_ws_customhtml_group'];
$block = new CustomHtmlBlock();
$block->id_ws_customhtml_group = (int) $formData['id_ws_customhtml_group'];
$block->title = (string) $formData['title'];
$block->id_ws_customhtml_group = $idGroup;
$block->title = $formData['title'];
$block->type = (string) $formData['type'];
$block->show_title = (int) $formData['show_title'];
$block->content = (string) $formData['content'];
$block->custom_class = (string) ($formData['custom_class'] ?? '');
$block->content = $formData['content'];
$block->description = $formData['description'] ?? [];
$block->extra_description = $formData['extra_description'] ?? [];
$block->link = $formData['link'] ?? [];
$block->link_text = $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'] ?? 0);
$block->autoplay_delay = (int) ($formData['autoplay_delay'] ?? 0);
$block->image = $this->saveUploadedImage($formData['image'] ?? null, '');
$block->active = (int) $formData['active'];
$block->ordering = 0;
$block->ordering = $this->getNextOrdering($idGroup);
if (!$block->add()) {
return false;
}
$this->saveFaqs((int) $block->id, $formData['faqs'] ?? []);
$this->saveSlides((int) $block->id, $formData['slides'] ?? []);
return $block->id;
}
@@ -34,11 +59,140 @@ final class CustomHtmlBlockDataHandler implements FormDataHandlerInterface
}
$block->id_ws_customhtml_group = (int) $formData['id_ws_customhtml_group'];
$block->title = (string) $formData['title'];
$block->title = $formData['title'];
$block->type = (string) $formData['type'];
$block->show_title = (int) $formData['show_title'];
$block->content = (string) $formData['content'];
$block->custom_class = (string) ($formData['custom_class'] ?? '');
$block->content = $formData['content'];
$block->description = $formData['description'] ?? [];
$block->extra_description = $formData['extra_description'] ?? [];
$block->link = $formData['link'] ?? [];
$block->link_text = $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'] ?? 0);
$block->autoplay_delay = (int) ($formData['autoplay_delay'] ?? 0);
$block->image = $this->saveUploadedImage($formData['image'] ?? null, $block->image);
$block->active = (int) $formData['active'];
return (bool) $block->update();
if (!$block->update()) {
return false;
}
$this->saveFaqs((int) $id, $formData['faqs'] ?? []);
$this->saveSlides((int) $id, $formData['slides'] ?? []);
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(
'SELECT MAX(`ordering`) FROM `' . _DB_PREFIX_ . 'ws_customhtml`
WHERE `id_ws_customhtml_group` = ' . (int) $idGroup
);
return $maxOrdering === false || $maxOrdering === null ? 0 : ((int) $maxOrdering + 1);
}
private function saveFaqs(int $idBlock, array $faqs): void
{
CustomHtmlFaq::deleteByBlockId($idBlock);
foreach (array_values($faqs) as $position => $faqData) {
$question = $faqData['question'] ?? [];
if (empty(array_filter($question, static function ($value) {
return trim((string) $value) !== '';
}))) {
continue;
}
$faq = new CustomHtmlFaq();
$faq->id_ws_customhtml = $idBlock;
$faq->question = $question;
$faq->answer = $faqData['answer'] ?? [];
$faq->ordering = $position;
$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 = $slideData['title'] ?? [];
$slide->description = $slideData['description'] ?? [];
$slide->extra_description = $slideData['extra_description'] ?? [];
$slide->link = $slideData['link'] ?? [];
$slide->link_text = $slideData['link_text'] ?? [];
$slide->image = $this->saveUploadedImage($slideData['image'] ?? null, $existingImage);
$slide->ordering = $position;
$slide->add();
}
}
}
@@ -5,6 +5,7 @@ namespace Websmid\CustomHtml\Form\DataHandler;
use PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataHandler\FormDataHandlerInterface;
use Websmid\CustomHtml\Model\CustomHtmlGroup;
use Context;
use Db;
use Hook;
use Module;
use Validate;
@@ -14,10 +15,12 @@ final class CustomHtmlGroupDataHandler implements FormDataHandlerInterface
public function create(array $formData)
{
$group = new CustomHtmlGroup();
$group->title = (string) $formData['title'];
$group->title = $formData['title'];
$group->hook_name = (string) $formData['hook_name'];
$group->custom_class = (string) ($formData['custom_class'] ?? '');
$group->default_block_class = (string) ($formData['default_block_class'] ?? '');
$group->active = (int) $formData['active'];
$group->ordering = 0;
$group->ordering = $this->getNextOrdering();
if (!$group->add()) {
return false;
@@ -28,6 +31,15 @@ final class CustomHtmlGroupDataHandler implements FormDataHandlerInterface
return $group->id;
}
private function getNextOrdering(): int
{
$maxOrdering = Db::getInstance()->getValue(
'SELECT MAX(`ordering`) FROM `' . _DB_PREFIX_ . 'ws_customhtml_group`'
);
return $maxOrdering !== false ? ((int) $maxOrdering + 1) : 0;
}
public function update($id, array $formData)
{
$group = new CustomHtmlGroup((int) $id);
@@ -36,8 +48,10 @@ final class CustomHtmlGroupDataHandler implements FormDataHandlerInterface
return false;
}
$group->title = (string) $formData['title'];
$group->title = $formData['title'];
$group->hook_name = (string) $formData['hook_name'];
$group->custom_class = (string) ($formData['custom_class'] ?? '');
$group->default_block_class = (string) ($formData['default_block_class'] ?? '');
$group->active = (int) $formData['active'];
if (!$group->update()) {
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
@@ -3,7 +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
@@ -19,8 +22,24 @@ final class CustomHtmlBlockDataProvider implements FormDataProviderInterface
return [
'id_ws_customhtml_group' => (int) $block->id_ws_customhtml_group,
'title' => $block->title,
'type' => $block->type,
'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,
'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 > 0 ? (float) $block->slides_per_view_desktop : null,
'autoplay_delay' => (int) $block->autoplay_delay,
'faqs' => $this->loadFaqs((int) $id),
'slides' => $this->loadSlides((int) $id),
'active' => (bool) $block->active,
];
}
@@ -29,10 +48,51 @@ final class CustomHtmlBlockDataProvider implements FormDataProviderInterface
{
return [
'id_ws_customhtml_group' => 0,
'title' => '',
'title' => [],
'type' => 'html',
'show_title' => false,
'content' => '',
'custom_class' => '',
'content' => [],
'image' => null,
'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' => null,
'autoplay_delay' => 0,
'faqs' => [],
'slides' => [],
'active' => true,
];
}
private function loadFaqs(int $idBlock): array
{
return array_map(static function (array $faq) {
return [
'question' => $faq['question'],
'answer' => $faq['answer'],
];
}, 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));
}
}
@@ -19,6 +19,8 @@ final class CustomHtmlGroupDataProvider implements FormDataProviderInterface
return [
'title' => $group->title,
'hook_name' => $group->hook_name,
'custom_class' => $group->custom_class,
'default_block_class' => $group->default_block_class,
'active' => (bool) $group->active,
];
}
@@ -26,8 +28,10 @@ final class CustomHtmlGroupDataProvider implements FormDataProviderInterface
public function getDefaultData()
{
return [
'title' => '',
'title' => [],
'hook_name' => '',
'custom_class' => '',
'default_block_class' => '',
'active' => true,
];
}
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+130 -4
View File
@@ -5,15 +5,23 @@ namespace Websmid\CustomHtml\Form\Type;
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\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;
use PrestaShopBundle\Form\Admin\Type\FormattedTextareaType;
use PrestaShopBundle\Form\Admin\Type\SwitchType;
use PrestaShopBundle\Form\Admin\Type\TranslatableType;
use Symfony\Contracts\Translation\TranslatorInterface;
use Websmid\CustomHtml\Config\Config;
class CustomHtmlBlockType extends AbstractType
{
private $translator;
private $locales;
private $groupChoiceProvider;
public function __construct(
@@ -22,6 +30,7 @@ class CustomHtmlBlockType extends AbstractType
ConfigurableFormChoiceProviderInterface $groupChoiceProvider
) {
$this->translator = $translator;
$this->locales = $locales;
$this->groupChoiceProvider = $groupChoiceProvider;
}
@@ -31,20 +40,137 @@ class CustomHtmlBlockType extends AbstractType
->add('id_ws_customhtml_group', ChoiceType::class, [
'label' => $this->translator->trans('Group', [], 'Modules.Wscustomhtml.Admin'),
'choices' => $this->groupChoiceProvider->getChoices(),
'choice_attr' => $this->groupChoiceProvider->getChoicesAttributes(),
'required' => true,
])
->add('title', TextType::class, [
'label' => $this->translator->trans('Title', [], 'Modules.Wscustomhtml.Admin'),
->add('type', ChoiceType::class, [
'label' => $this->translator->trans('Block type', [], 'Modules.Wscustomhtml.Admin'),
'choices' => array_flip(Config::BLOCK_TYPES),
'required' => true,
])
->add('title', TranslatableType::class, [
'type' => TextType::class,
'label' => $this->translator->trans('Title', [], 'Modules.Wscustomhtml.Admin'),
'locales' => $this->locales,
'required' => true,
'row_attr' => ['class' => 'col-8'],
])
->add('show_title', SwitchType::class, [
'label' => $this->translator->trans('Show title', [], 'Modules.Wscustomhtml.Admin'),
'required' => false,
'row_attr' => ['class' => 'col-3'],
])
->add('content', TextareaType::class, [
->add('content', TranslatableType::class, [
'type' => FormattedTextareaType::class,
'label' => $this->translator->trans('Content', [], 'Modules.Wscustomhtml.Admin'),
'locales' => $this->locales,
'required' => false,
'attr' => ['class' => 'autoload_rte'],
'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-html'],
])
->add('image', FileType::class, [
'label' => $this->translator->trans('Image', [], 'Modules.Wscustomhtml.Admin'),
'required' => false,
'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-banner customhtmlblock-type-usp'],
])
->add('faqs', CollectionType::class, [
'entry_type' => CustomHtmlFaqType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'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,
'help' => $this->translator->trans('"Mobile only": slider only on mobile, a static grid shows instead from tablet up.', [], 'Modules.Wscustomhtml.Admin'),
'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', 'placeholder' => $this->translator->trans('Auto', [], 'Modules.Wscustomhtml.Admin')],
'help' => $this->translator->trans('In "Mobile only" mode this is rounded to the nearest whole number of columns. Leave empty for an equal-width column.', [], '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),
'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', TranslatableType::class, [
'type' => TextareaType::class,
'label' => $this->translator->trans('Description', [], 'Modules.Wscustomhtml.Admin'),
'locales' => $this->locales,
'required' => false,
'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-banner customhtmlblock-type-alert customhtmlblock-type-usp'],
])
->add('extra_description', TranslatableType::class, [
'type' => TextareaType::class,
'label' => $this->translator->trans('Extra description', [], 'Modules.Wscustomhtml.Admin'),
'locales' => $this->locales,
'required' => false,
'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-banner'],
])
->add('link', TranslatableType::class, [
'type' => TextType::class,
'label' => $this->translator->trans('Link URL', [], 'Modules.Wscustomhtml.Admin'),
'locales' => $this->locales,
'required' => false,
'options' => ['attr' => ['placeholder' => 'https://example.com or /internal-page']],
'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-banner customhtmlblock-type-usp customhtmlblock-type-alert'],
])
->add('link_text', TranslatableType::class, [
'type' => TextType::class,
'label' => $this->translator->trans('Link text', [], 'Modules.Wscustomhtml.Admin'),
'locales' => $this->locales,
'required' => false,
'options' => ['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'),
'required' => false,
'attr' => ['placeholder' => 'col-md-6'],
])
->add('active', SwitchType::class, [
'label' => $this->translator->trans('Active', [], 'Modules.Wscustomhtml.Admin'),
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace Websmid\CustomHtml\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use PrestaShopBundle\Form\Admin\Type\TranslatableType;
use Symfony\Contracts\Translation\TranslatorInterface;
class CustomHtmlFaqType extends AbstractType
{
private $translator;
private $locales;
public function __construct(TranslatorInterface $translator, array $locales)
{
$this->translator = $translator;
$this->locales = $locales;
}
public function buildForm(FormBuilderInterface $formBuilder, array $options): void
{
$formBuilder
->add('question', TranslatableType::class, [
'type' => TextType::class,
'label' => $this->translator->trans('Question', [], 'Modules.Wscustomhtml.Admin'),
'locales' => $this->locales,
'required' => false,
])
->add('answer', TranslatableType::class, [
'type' => TextareaType::class,
'label' => $this->translator->trans('Answer', [], 'Modules.Wscustomhtml.Admin'),
'locales' => $this->locales,
'required' => false,
]);
}
}
+17 -1
View File
@@ -6,28 +6,44 @@ use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use PrestaShopBundle\Form\Admin\Type\SwitchType;
use PrestaShopBundle\Form\Admin\Type\TranslatableType;
use Symfony\Contracts\Translation\TranslatorInterface;
class CustomHtmlGroupType extends AbstractType
{
private $translator;
private $locales;
public function __construct(TranslatorInterface $translator, array $locales)
{
$this->translator = $translator;
$this->locales = $locales;
}
public function buildForm(FormBuilderInterface $formBuilder, array $options): void
{
$formBuilder
->add('title', TextType::class, [
->add('title', TranslatableType::class, [
'type' => TextType::class,
'label' => $this->translator->trans('Title', [], 'Modules.Wscustomhtml.Admin'),
'locales' => $this->locales,
'required' => true,
])
->add('hook_name', TextType::class, [
'label' => $this->translator->trans('Hook name', [], 'Modules.Wscustomhtml.Admin'),
'required' => true,
])
->add('custom_class', TextType::class, [
'label' => $this->translator->trans('Custom group classes', [], 'Modules.Wscustomhtml.Admin'),
'required' => false,
'attr' => ['placeholder' => 'col-md-6'],
])
->add('default_block_class', TextType::class, [
'label' => $this->translator->trans('Default block class', [], 'Modules.Wscustomhtml.Admin'),
'required' => false,
'attr' => ['placeholder' => 'col-12 col-lg-6 col-xl-5'],
'help' => $this->translator->trans('Applied to every block in this group automatically, in addition to that block\'s own custom classes. Useful when several blocks need the same column layout.', [], 'Modules.Wscustomhtml.Admin'),
])
->add('active', SwitchType::class, [
'label' => $this->translator->trans('Active', [], 'Modules.Wscustomhtml.Admin'),
'required' => false,
+68
View File
@@ -0,0 +1,68 @@
<?php
namespace Websmid\CustomHtml\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use PrestaShopBundle\Form\Admin\Type\TranslatableType;
use Symfony\Contracts\Translation\TranslatorInterface;
class CustomHtmlSlideType extends AbstractType
{
private $translator;
private $locales;
public function __construct(TranslatorInterface $translator, array $locales)
{
$this->translator = $translator;
$this->locales = $locales;
}
public function buildForm(FormBuilderInterface $formBuilder, array $options): void
{
$formBuilder
->add('id', HiddenType::class, [
'required' => false,
])
->add('title', TranslatableType::class, [
'type' => TextType::class,
'label' => $this->translator->trans('Title', [], 'Modules.Wscustomhtml.Admin'),
'locales' => $this->locales,
'required' => false,
])
->add('description', TranslatableType::class, [
'type' => TextareaType::class,
'label' => $this->translator->trans('Description', [], 'Modules.Wscustomhtml.Admin'),
'locales' => $this->locales,
'required' => false,
])
->add('extra_description', TranslatableType::class, [
'type' => TextareaType::class,
'label' => $this->translator->trans('Extra description', [], 'Modules.Wscustomhtml.Admin'),
'locales' => $this->locales,
'required' => false,
])
->add('link', TranslatableType::class, [
'type' => TextType::class,
'label' => $this->translator->trans('Link URL', [], 'Modules.Wscustomhtml.Admin'),
'locales' => $this->locales,
'required' => false,
'options' => ['attr' => ['placeholder' => 'https://example.com or /internal-page']],
])
->add('link_text', TranslatableType::class, [
'type' => TextType::class,
'label' => $this->translator->trans('Link text', [], 'Modules.Wscustomhtml.Admin'),
'locales' => $this->locales,
'required' => false,
'options' => ['attr' => ['placeholder' => 'Learn more']],
])
->add('image', FileType::class, [
'label' => $this->translator->trans('Image', [], 'Modules.Wscustomhtml.Admin'),
'required' => false,
]);
}
}
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
@@ -8,6 +8,7 @@ use PrestaShop\PrestaShop\Core\Grid\Action\Row\Type\LinkRowAction;
use PrestaShop\PrestaShop\Core\Grid\Action\Type\SimpleGridAction;
use PrestaShop\PrestaShop\Core\Grid\Column\ColumnCollection;
use PrestaShop\PrestaShop\Core\Grid\Column\Type\Common\ActionColumn;
use PrestaShop\PrestaShop\Core\Grid\Column\Type\Common\PositionColumn;
use PrestaShop\PrestaShop\Core\Grid\Column\Type\DataColumn;
use PrestaShop\PrestaShop\Core\Grid\Definition\Factory\AbstractGridDefinitionFactory;
use PrestaShop\PrestaShop\Core\Grid\Definition\Factory\DeleteActionTrait;
@@ -15,7 +16,9 @@ use PrestaShop\PrestaShop\Core\Grid\Filter\Filter;
use PrestaShop\PrestaShop\Core\Grid\Filter\FilterCollection;
use PrestaShop\PrestaShop\Core\Hook\HookDispatcherInterface;
use PrestaShopBundle\Form\Admin\Type\SearchAndResetType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Websmid\CustomHtml\Config\Config;
final class CustomHtmlBlockGridDefinitionFactory extends AbstractGridDefinitionFactory
{
@@ -71,15 +74,29 @@ final class CustomHtmlBlockGridDefinitionFactory extends AbstractGridDefinitionF
->setName($this->trans('Group', [], 'Modules.Wscustomhtml.Admin'))
->setOptions(['field' => 'group_title'])
)
->add(
(new DataColumn('type'))
->setName($this->trans('Type', [], 'Modules.Wscustomhtml.Admin'))
->setOptions(['field' => 'type'])
)
->add(
(new DataColumn('active'))
->setName($this->trans('Active', [], 'Admin.Global'))
->setOptions(['field' => 'active'])
)
->add(
(new DataColumn('ordering'))
(new PositionColumn('position'))
->setName($this->trans('Position', [], 'Modules.Wscustomhtml.Admin'))
->setOptions(['field' => 'ordering'])
->setOptions([
'id_field' => 'id_ws_customhtml',
'position_field' => 'ordering',
'update_method' => 'POST',
'update_route' => 'customhtmlblock_update_position',
'record_route_params' => [
'id_ws_customhtml_group' => 'idGroup',
],
'required_filter' => 'id_ws_customhtml_group',
])
)
->add(
(new DataColumn('date_add'))
@@ -120,6 +137,15 @@ final class CustomHtmlBlockGridDefinitionFactory extends AbstractGridDefinitionF
'attr' => ['placeholder' => $this->trans('Group', [], 'Modules.Wscustomhtml.Admin')],
])
)
->add(
(new Filter('type', ChoiceType::class))
->setAssociatedColumn('type')
->setTypeOptions([
'required' => false,
'choices' => array_flip(Config::BLOCK_TYPES),
'placeholder' => $this->trans('All', [], 'Admin.Global'),
])
)
->add(
(new Filter('actions', SearchAndResetType::class))
->setAssociatedColumn('actions')
@@ -144,10 +170,17 @@ final class CustomHtmlBlockGridDefinitionFactory extends AbstractGridDefinitionF
'route_param_field' => 'id_ws_customhtml',
'use_inline_display' => true,
'clickable_row' => true,
'extra_route_params' => ['idGroup' => 'id_ws_customhtml_group'],
])
)
->add(
$this->buildDeleteAction('customhtmlblock_delete', 'idCustomHtmlBlock', 'id_ws_customhtml')
$this->buildDeleteAction(
'customhtmlblock_delete',
'idCustomHtmlBlock',
'id_ws_customhtml',
'POST',
['idGroup' => 'id_ws_customhtml_group']
)
);
}
}
@@ -8,6 +8,8 @@ use PrestaShop\PrestaShop\Core\Grid\Action\Row\Type\LinkRowAction;
use PrestaShop\PrestaShop\Core\Grid\Action\Type\SimpleGridAction;
use PrestaShop\PrestaShop\Core\Grid\Column\ColumnCollection;
use PrestaShop\PrestaShop\Core\Grid\Column\Type\Common\ActionColumn;
use PrestaShop\PrestaShop\Core\Grid\Column\Type\Common\PositionColumn;
use PrestaShop\PrestaShop\Core\Grid\Column\Type\Common\ToggleColumn;
use PrestaShop\PrestaShop\Core\Grid\Column\Type\DataColumn;
use PrestaShop\PrestaShop\Core\Grid\Definition\Factory\AbstractGridDefinitionFactory;
use PrestaShop\PrestaShop\Core\Grid\Definition\Factory\DeleteActionTrait;
@@ -77,9 +79,24 @@ final class CustomHtmlGroupGridDefinitionFactory extends AbstractGridDefinitionF
->setOptions(['field' => 'blocks_count', 'sortable' => false])
)
->add(
(new DataColumn('active'))
(new ToggleColumn('active'))
->setName($this->trans('Active', [], 'Admin.Global'))
->setOptions(['field' => 'active'])
->setOptions([
'field' => 'active',
'primary_field' => 'id_ws_customhtml_group',
'route' => 'customhtmlgroup_toggle',
'route_param_name' => 'idCustomHtmlGroup',
])
)
->add(
(new PositionColumn('position'))
->setName($this->trans('Position', [], 'Admin.Global'))
->setOptions([
'id_field' => 'id_ws_customhtml_group',
'position_field' => 'ordering',
'update_method' => 'POST',
'update_route' => 'customhtmlgroup_update_position',
])
)
->add(
(new DataColumn('date_add'))
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+1 -1
View File
@@ -14,7 +14,7 @@ final class CustomHtmlBlockFilters extends Filters
return [
'limit' => 10,
'offset' => 0,
'orderBy' => 'wchb.id_ws_customhtml',
'orderBy' => 'position',
'sortOrder' => 'asc',
'filters' => [],
];
+1 -1
View File
@@ -14,7 +14,7 @@ final class CustomHtmlGroupFilters extends Filters
return [
'limit' => 10,
'offset' => 0,
'orderBy' => 'wchg.id_ws_customhtml_group',
'orderBy' => 'position',
'sortOrder' => 'asc',
'filters' => [],
];
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+33 -14
View File
@@ -11,12 +11,7 @@ use PrestaShop\PrestaShop\Core\Grid\Search\SearchCriteriaInterface;
final class CustomHtmlBlockQueryBuilder extends AbstractDoctrineQueryBuilder
{
private $searchCriteriaApplicator;
private $contextLanguageId;
private $contextShopId;
private $contextShopGroupId;
private $filterApplicator;
private $configuration;
public function __construct(
Connection $connection,
@@ -29,12 +24,8 @@ final class CustomHtmlBlockQueryBuilder extends AbstractDoctrineQueryBuilder
Configuration $configuration
) {
parent::__construct($connection, $dbPrefix);
$this->searchCriteriaApplicator = $searchCriteriaApplicator;
$this->contextLanguageId = $contextLanguageId;
$this->contextShopId = $contextShopId;
$this->contextShopGroupId = $contextShopGroupId;
$this->filterApplicator = $filterApplicator;
$this->configuration = $configuration;
}
public function getSearchQueryBuilder(SearchCriteriaInterface $searchCriteria)
@@ -43,8 +34,10 @@ final class CustomHtmlBlockQueryBuilder extends AbstractDoctrineQueryBuilder
$qb->select(
'wchb.`id_ws_customhtml`',
'wchb.`title`',
'wchg.`title` AS group_title',
'wchb.`id_ws_customhtml_group`',
'wchbl.`title`',
'wchgl.`title` AS group_title',
'wchb.`type`',
'wchb.`active`',
'wchb.`ordering`',
'wchb.`date_add`'
@@ -52,8 +45,10 @@ final class CustomHtmlBlockQueryBuilder extends AbstractDoctrineQueryBuilder
$this->applyFilters($qb, $searchCriteria);
$orderBy = $searchCriteria->getOrderBy() === 'position' ? 'wchb.ordering' : $searchCriteria->getOrderBy();
$qb
->orderBy($searchCriteria->getOrderBy(), $searchCriteria->getOrderWay())
->orderBy($orderBy, $searchCriteria->getOrderWay())
->setFirstResult($searchCriteria->getOffset() ?? 0)
->setMaxResults($searchCriteria->getLimit());
@@ -80,6 +75,18 @@ final class CustomHtmlBlockQueryBuilder extends AbstractDoctrineQueryBuilder
$this->dbPrefix . 'ws_customhtml_group',
'wchg',
'wchg.`id_ws_customhtml_group` = wchb.`id_ws_customhtml_group`'
)
->leftJoin(
'wchb',
$this->dbPrefix . 'ws_customhtml_group_lang',
'wchgl',
'wchgl.`id_ws_customhtml_group` = wchb.`id_ws_customhtml_group` AND wchgl.`id_lang` = ' . (int) $this->contextLanguageId
)
->leftJoin(
'wchb',
$this->dbPrefix . 'ws_customhtml_lang',
'wchbl',
'wchbl.`id_ws_customhtml` = wchb.`id_ws_customhtml` AND wchbl.`id_lang` = ' . (int) $this->contextLanguageId
);
}
@@ -102,12 +109,24 @@ final class CustomHtmlBlockQueryBuilder extends AbstractDoctrineQueryBuilder
continue;
}
if ($filterName === 'title') {
$qb->andWhere('wchbl.`title` LIKE :title')
->setParameter('title', '%' . pSQL($filterValue) . '%');
continue;
}
if ($filterName === 'group_title') {
$qb->andWhere('wchg.`title` LIKE :group_title')
$qb->andWhere('wchgl.`title` LIKE :group_title')
->setParameter('group_title', '%' . pSQL($filterValue) . '%');
continue;
}
if ($filterName === 'type') {
$qb->andWhere('wchb.`type` = :type')
->setParameter('type', pSQL($filterValue));
continue;
}
$qb->andWhere('wchb.`' . $filterName . '` LIKE :' . $filterName)
->setParameter($filterName, '%' . pSQL($filterValue) . '%');
}
+20 -13
View File
@@ -11,12 +11,7 @@ use PrestaShop\PrestaShop\Core\Grid\Search\SearchCriteriaInterface;
final class CustomHtmlGroupQueryBuilder extends AbstractDoctrineQueryBuilder
{
private $searchCriteriaApplicator;
private $contextLanguageId;
private $contextShopId;
private $contextShopGroupId;
private $filterApplicator;
private $configuration;
public function __construct(
Connection $connection,
@@ -29,12 +24,8 @@ final class CustomHtmlGroupQueryBuilder extends AbstractDoctrineQueryBuilder
Configuration $configuration
) {
parent::__construct($connection, $dbPrefix);
$this->searchCriteriaApplicator = $searchCriteriaApplicator;
$this->contextLanguageId = $contextLanguageId;
$this->contextShopId = $contextShopId;
$this->contextShopGroupId = $contextShopGroupId;
$this->filterApplicator = $filterApplicator;
$this->configuration = $configuration;
}
public function getSearchQueryBuilder(SearchCriteriaInterface $searchCriteria)
@@ -43,9 +34,11 @@ final class CustomHtmlGroupQueryBuilder extends AbstractDoctrineQueryBuilder
$qb->select(
'wchg.`id_ws_customhtml_group`',
'wchg.`title`',
'wchgl.`title`',
'wchg.`hook_name`',
'wchg.`custom_class`',
'wchg.`active`',
'wchg.`ordering`',
'wchg.`date_add`',
'(SELECT COUNT(*) FROM `' . $this->dbPrefix . 'ws_customhtml` wch
WHERE wch.`id_ws_customhtml_group` = wchg.`id_ws_customhtml_group`) AS blocks_count'
@@ -53,8 +46,10 @@ final class CustomHtmlGroupQueryBuilder extends AbstractDoctrineQueryBuilder
$this->applyFilters($qb, $searchCriteria);
$orderBy = $searchCriteria->getOrderBy() === 'position' ? 'wchg.ordering' : $searchCriteria->getOrderBy();
$qb
->orderBy($searchCriteria->getOrderBy(), $searchCriteria->getOrderWay())
->orderBy($orderBy, $searchCriteria->getOrderWay())
->setFirstResult($searchCriteria->getOffset() ?? 0)
->setMaxResults($searchCriteria->getLimit());
@@ -75,7 +70,13 @@ final class CustomHtmlGroupQueryBuilder extends AbstractDoctrineQueryBuilder
{
return $this->connection
->createQueryBuilder()
->from($this->dbPrefix . 'ws_customhtml_group', 'wchg');
->from($this->dbPrefix . 'ws_customhtml_group', 'wchg')
->leftJoin(
'wchg',
$this->dbPrefix . 'ws_customhtml_group_lang',
'wchgl',
'wchgl.`id_ws_customhtml_group` = wchg.`id_ws_customhtml_group` AND wchgl.`id_lang` = ' . (int) $this->contextLanguageId
);
}
private function applyFilters($qb, SearchCriteriaInterface $searchCriteria): void
@@ -91,6 +92,12 @@ final class CustomHtmlGroupQueryBuilder extends AbstractDoctrineQueryBuilder
continue;
}
if ($filterName === 'title') {
$qb->andWhere('wchgl.`title` LIKE :title')
->setParameter('title', '%' . pSQL($filterValue) . '%');
continue;
}
$qb->andWhere('wchg.`' . $filterName . '` LIKE :' . $filterName)
->setParameter($filterName, '%' . pSQL($filterValue) . '%');
}
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+84 -8
View File
@@ -16,9 +16,51 @@ class CustomHtmlBlock extends ObjectModel
/** @var string */
public $title = '';
/** @var string */
public $type = 'html';
/** @var string */
public $content = '';
/** @var string */
public $custom_class = '';
/** @var string */
public $description = '';
/** @var string */
public $extra_description = '';
/** @var string */
public $link = '';
/** @var string */
public $link_text = '';
/** @var string */
public $image = '';
/** @var string */
public $alert_type = 'info';
/** @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;
@@ -37,10 +79,25 @@ class CustomHtmlBlock extends ObjectModel
public static $definition = [
'table' => 'ws_customhtml',
'primary' => 'id_ws_customhtml',
'multilang' => true,
'fields' => [
'id_ws_customhtml_group' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'title' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true, 'size' => 256],
'content' => ['type' => self::TYPE_HTML, 'validate' => 'isCleanHtml'],
'title' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true, 'size' => 256, 'lang' => true],
'type' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true, 'size' => 32],
'content' => ['type' => self::TYPE_HTML, 'validate' => 'isCleanHtml', 'lang' => true],
'custom_class' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 128],
'description' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 1024, 'lang' => true],
'extra_description' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 1024, 'lang' => true],
'link' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 512, 'lang' => true],
'link_text' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 128, 'lang' => true],
'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'],
@@ -49,7 +106,21 @@ class CustomHtmlBlock extends ObjectModel
],
];
public static function getActiveByGroupIds(array $groupIds): array
public static function getBannerImageDir(): string
{
return _PS_MODULE_DIR_ . 'ws_customhtml/views/img/banners/';
}
public static function getBannerImageUrl(string $filename): string
{
if (empty($filename)) {
return '';
}
return _MODULE_DIR_ . 'ws_customhtml/views/img/banners/' . $filename;
}
public static function getActiveByGroupIds(array $groupIds, int $idLang): array
{
$groupIds = array_map('intval', $groupIds);
@@ -58,11 +129,16 @@ class CustomHtmlBlock extends ObjectModel
}
$result = Db::getInstance()->executeS(
'SELECT `id_ws_customhtml`, `id_ws_customhtml_group`, `title`, `content`, `active`, `show_title`, `ordering`
FROM `' . _DB_PREFIX_ . 'ws_customhtml`
WHERE `id_ws_customhtml_group` IN (' . implode(',', $groupIds) . ')
AND `active` = 1
ORDER BY `ordering` ASC'
'SELECT wch.`id_ws_customhtml`, wch.`id_ws_customhtml_group`, wchl.`title`, wch.`type`, wchl.`content`, wch.`custom_class`,
wchl.`description`, wchl.`extra_description`, wchl.`link`, wchl.`link_text`, wch.`image`, wch.`alert_type`, wch.`timeout`,
wch.`slider_mode`, wch.`slides_per_view_mobile`, wch.`slides_per_view_tablet`, wch.`slides_per_view_desktop`, wch.`autoplay_delay`,
wch.`active`, wch.`show_title`, wch.`ordering`
FROM `' . _DB_PREFIX_ . 'ws_customhtml` wch
LEFT JOIN `' . _DB_PREFIX_ . 'ws_customhtml_lang` wchl
ON wchl.`id_ws_customhtml` = wch.`id_ws_customhtml` AND wchl.`id_lang` = ' . (int) $idLang . '
WHERE wch.`id_ws_customhtml_group` IN (' . implode(',', $groupIds) . ')
AND wch.`active` = 1
ORDER BY wch.`ordering` ASC'
);
$blocksByGroup = [];
+89
View File
@@ -0,0 +1,89 @@
<?php
namespace Websmid\CustomHtml\Model;
use ObjectModel;
use Db;
class CustomHtmlFaq extends ObjectModel
{
/** @var int */
public $id;
/** @var int */
public $id_ws_customhtml;
/** @var string */
public $question = '';
/** @var string */
public $answer = '';
/** @var int */
public $ordering = 0;
/** @var string */
public $date_add;
/** @var string */
public $date_upd;
public static $definition = [
'table' => 'ws_customhtml_faq',
'primary' => 'id_ws_customhtml_faq',
'multilang' => true,
'fields' => [
'id_ws_customhtml' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'question' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true, 'size' => 512, 'lang' => true],
'answer' => ['type' => self::TYPE_HTML, 'validate' => 'isCleanHtml', 'lang' => true],
'ordering' => ['type' => self::TYPE_INT, 'validate' => 'isInt'],
'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
'date_upd' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
],
];
/**
* Admin use — loads every language into lang fields as [id_lang => value] arrays.
*/
public static function getByBlockId(int $idBlock): array
{
$ids = Db::getInstance()->executeS(
'SELECT `id_ws_customhtml_faq` FROM `' . _DB_PREFIX_ . 'ws_customhtml_faq`
WHERE `id_ws_customhtml` = ' . (int) $idBlock . '
ORDER BY `ordering` ASC'
);
return array_map(static function (array $row) {
$faq = new self((int) $row['id_ws_customhtml_faq']);
return [
'id_ws_customhtml_faq' => (int) $faq->id,
'question' => $faq->question,
'answer' => $faq->answer,
'ordering' => (int) $faq->ordering,
];
}, $ids ?: []);
}
/**
* Front-end use — resolves lang fields to plain strings for one language.
*/
public static function getByBlockIdForLang(int $idBlock, int $idLang): array
{
$result = Db::getInstance()->executeS(
'SELECT wcf.`id_ws_customhtml_faq`, wcfl.`question`, wcfl.`answer`, wcf.`ordering`
FROM `' . _DB_PREFIX_ . 'ws_customhtml_faq` wcf
LEFT JOIN `' . _DB_PREFIX_ . 'ws_customhtml_faq_lang` wcfl
ON wcfl.`id_ws_customhtml_faq` = wcf.`id_ws_customhtml_faq` AND wcfl.`id_lang` = ' . (int) $idLang . '
WHERE wcf.`id_ws_customhtml` = ' . (int) $idBlock . '
ORDER BY wcf.`ordering` ASC'
);
return $result ?: [];
}
public static function deleteByBlockId(int $idBlock): void
{
Db::getInstance()->delete('ws_customhtml_faq', '`id_ws_customhtml` = ' . (int) $idBlock);
}
}
+11 -2
View File
@@ -16,6 +16,12 @@ class CustomHtmlGroup extends ObjectModel
/** @var string */
public $title = '';
/** @var string */
public $custom_class = '';
/** @var string */
public $default_block_class = '';
/** @var int */
public $active = 1;
@@ -31,9 +37,12 @@ class CustomHtmlGroup extends ObjectModel
public static $definition = [
'table' => 'ws_customhtml_group',
'primary' => 'id_ws_customhtml_group',
'multilang' => true,
'fields' => [
'hook_name' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true, 'size' => 64],
'title' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true, 'size' => 256],
'title' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true, 'size' => 256, 'lang' => true],
'custom_class' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 128],
'default_block_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 +53,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`, `active`, `custom_class`, `default_block_class`, `ordering`
FROM `' . _DB_PREFIX_ . 'ws_customhtml_group`
WHERE `hook_name` = \'' . pSQL($hookName) . '\'
AND `active` = 1
+110
View File
@@ -0,0 +1,110 @@
<?php
namespace Websmid\CustomHtml\Model;
use ObjectModel;
use Db;
class CustomHtmlSlide extends ObjectModel
{
/** @var int */
public $id;
/** @var int */
public $id_ws_customhtml;
/** @var string */
public $title = '';
/** @var string */
public $description = '';
/** @var string */
public $extra_description = '';
/** @var string */
public $link = '';
/** @var string */
public $link_text = '';
/** @var string */
public $image = '';
/** @var int */
public $ordering = 0;
/** @var string */
public $date_add;
/** @var string */
public $date_upd;
public static $definition = [
'table' => 'ws_customhtml_slide',
'primary' => 'id_ws_customhtml_slide',
'multilang' => true,
'fields' => [
'id_ws_customhtml' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'title' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 256, 'lang' => true],
'description' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 1024, 'lang' => true],
'extra_description' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 1024, 'lang' => true],
'link' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 512, 'lang' => true],
'link_text' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 128, 'lang' => true],
'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'],
],
];
/**
* Admin use — loads every language into lang fields as [id_lang => value] arrays.
*/
public static function getByBlockId(int $idBlock): array
{
$ids = Db::getInstance()->executeS(
'SELECT `id_ws_customhtml_slide` FROM `' . _DB_PREFIX_ . 'ws_customhtml_slide`
WHERE `id_ws_customhtml` = ' . (int) $idBlock . '
ORDER BY `ordering` ASC'
);
return array_map(static function (array $row) {
$slide = new self((int) $row['id_ws_customhtml_slide']);
return [
'id_ws_customhtml_slide' => (int) $slide->id,
'title' => $slide->title,
'description' => $slide->description,
'extra_description' => $slide->extra_description,
'link' => $slide->link,
'link_text' => $slide->link_text,
'image' => $slide->image,
'ordering' => (int) $slide->ordering,
];
}, $ids ?: []);
}
/**
* Front-end use — resolves lang fields to plain strings for one language.
*/
public static function getByBlockIdForLang(int $idBlock, int $idLang): array
{
$result = Db::getInstance()->executeS(
'SELECT wcs.`id_ws_customhtml_slide`, wcsl.`title`, wcsl.`description`, wcsl.`extra_description`,
wcsl.`link`, wcsl.`link_text`, wcs.`image`, wcs.`ordering`
FROM `' . _DB_PREFIX_ . 'ws_customhtml_slide` wcs
LEFT JOIN `' . _DB_PREFIX_ . 'ws_customhtml_slide_lang` wcsl
ON wcsl.`id_ws_customhtml_slide` = wcs.`id_ws_customhtml_slide` AND wcsl.`id_lang` = ' . (int) $idLang . '
WHERE wcs.`id_ws_customhtml` = ' . (int) $idBlock . '
ORDER BY wcs.`ordering` ASC'
);
return $result ?: [];
}
public static function deleteByBlockId(int $idBlock): void
{
Db::getInstance()->delete('ws_customhtml_slide', '`id_ws_customhtml` = ' . (int) $idBlock);
}
}
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+19 -2
View File
@@ -3,6 +3,9 @@
namespace Websmid\CustomHtml\Module;
use Websmid\CustomHtml\Config\Config;
use PrestaShop\PrestaShop\Adapter\SymfonyContainer;
use PrestaShop\PrestaShop\Core\Grid\Data\GridData;
use PrestaShop\PrestaShop\Core\Grid\Record\RecordCollection;
use Tools;
trait AdminHooks
@@ -19,12 +22,26 @@ trait AdminHooks
public function hookActionAdminControllerSetMedia(): void
{
$controllerName = $this->context->controller->controller_name ?? '';
$request = SymfonyContainer::getInstance()->get('request_stack')->getCurrentRequest();
$routeName = $request ? (string) $request->attributes->get('_route') : '';
if (!in_array($controllerName, Config::ADMIN_CONTROLLERS, true)) {
if (0 !== strpos($routeName, 'customhtml')) {
return;
}
$this->context->controller->addCSS($this->getPathUri() . 'views/css/admin/grid.css');
}
public function hookActionCustomhtmlblockGridDataModifier(array $params): void
{
$gridData = $params['data'];
$records = array_map(static function (array $row) {
$row['type'] = Config::BLOCK_TYPES[$row['type']] ?? $row['type'];
return $row;
}, $gridData->getRecords()->all());
$params['data'] = new GridData(new RecordCollection($records), $gridData->getRecordsTotal(), $gridData->getQuery());
}
}
+119 -3
View File
@@ -2,12 +2,28 @@
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
{
public function hookActionFrontControllerSetMedia(): void
{
$this->context->controller->registerStylesheet(
'ws_customhtml_front',
'/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 = [])
{
if (empty($hookName)) {
@@ -33,7 +49,8 @@ trait DisplayHooks
public function getWidgetVariables($hookName = null, array $configuration = [])
{
$groupList = $this->loadActiveGroupsWithBlocks((string) $hookName);
$idLang = (int) Context::getContext()->language->id;
$groupList = $this->loadActiveGroupsWithBlocks((string) $hookName, $idLang);
if (empty($groupList)) {
return [];
@@ -45,7 +62,7 @@ trait DisplayHooks
];
}
private function loadActiveGroupsWithBlocks(string $hookName): array
private function loadActiveGroupsWithBlocks(string $hookName, int $idLang): array
{
$groups = CustomHtmlGroup::getActiveByHookName($hookName);
@@ -53,7 +70,10 @@ trait DisplayHooks
return [];
}
$blocksByGroup = CustomHtmlBlock::getActiveByGroupIds(array_column($groups, 'id_ws_customhtml_group'));
$blocksByGroup = CustomHtmlBlock::getActiveByGroupIds(array_column($groups, 'id_ws_customhtml_group'), $idLang);
$blocksByGroup = $this->attachFaqsToBlocks($blocksByGroup, $idLang);
$blocksByGroup = $this->attachImageUrls($blocksByGroup);
$blocksByGroup = $this->attachSlidesToBlocks($blocksByGroup, $idLang);
$groups = array_map(static function (array $group) use ($blocksByGroup) {
$group['blocks'] = $blocksByGroup[(int) $group['id_ws_customhtml_group']] ?? [];
@@ -65,4 +85,100 @@ trait DisplayHooks
return !empty($group['blocks']);
}));
}
private function attachFaqsToBlocks(array $blocksByGroup, int $idLang): array
{
return array_map(static function (array $blocks) use ($idLang) {
return array_map(static function (array $block) use ($idLang) {
if ($block['type'] === 'faq') {
$block['faqs'] = CustomHtmlFaq::getByBlockIdForLang((int) $block['id_ws_customhtml'], $idLang);
}
return $block;
}, $blocks);
}, $blocksByGroup);
}
private function attachImageUrls(array $blocksByGroup): array
{
return array_map(static function (array $blocks) {
return array_map(static function (array $block) {
if (in_array($block['type'], ['banner', 'usp'], true)) {
$block['image_url'] = CustomHtmlBlock::getBannerImageUrl($block['image']);
}
return $block;
}, $blocks);
}, $blocksByGroup);
}
private function attachSlidesToBlocks(array $blocksByGroup, int $idLang): array
{
return array_map(function (array $blocks) use ($idLang) {
return array_map(function (array $block) use ($idLang) {
if ($block['type'] === 'slider') {
$block['slides'] = $this->loadSlidesForBlock((int) $block['id_ws_customhtml'], $idLang);
$block['slider_config'] = $this->buildSliderConfig($block);
$block['slider_desktop_column_width'] = $this->buildSliderDesktopColumnWidth($block);
}
return $block;
}, $blocks);
}, $blocksByGroup);
}
private function loadSlidesForBlock(int $idBlock, int $idLang): array
{
return array_map(static function (array $slide) {
$slide['image_url'] = CustomHtmlBlock::getBannerImageUrl($slide['image']);
$slide['show_title'] = true;
return $slide;
}, CustomHtmlSlide::getByBlockIdForLang($idBlock, $idLang));
}
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']],
];
if ((float) $block['slides_per_view_desktop'] > 0) {
$config['breakpoints'][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
{
if ((float) $block['slides_per_view_desktop'] <= 0) {
return null;
}
$columns = max(1, (int) round((float) $block['slides_per_view_desktop']));
return round(100 / $columns, 4) . '%';
}
}
+111 -3
View File
@@ -3,7 +3,9 @@
namespace Websmid\CustomHtml\Module;
use Websmid\CustomHtml\Config\Config;
use Context;
use Db;
use Hook;
use Language;
use Tab;
@@ -31,6 +33,11 @@ trait Install
return false;
}
if (!$this->registerGroupHooks()) {
error_log('[ws_customhtml] registerGroupHooks() failed');
return false;
}
return true;
}
@@ -42,7 +49,8 @@ trait Install
CREATE TABLE IF NOT EXISTS ' . _DB_PREFIX_ . 'ws_customhtml_group (
`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 \'\',
`default_block_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,
@@ -50,12 +58,29 @@ trait Install
);
';
$sqlToExec[] = '
CREATE TABLE IF NOT EXISTS ' . _DB_PREFIX_ . 'ws_customhtml_group_lang (
`id_ws_customhtml_group` INT(11) NOT NULL,
`id_lang` INT(11) NOT NULL,
`title` VARCHAR(256) NOT NULL DEFAULT \'\',
PRIMARY KEY (`id_ws_customhtml_group`, `id_lang`)
);
';
$sqlToExec[] = '
CREATE TABLE IF NOT EXISTS ' . _DB_PREFIX_ . 'ws_customhtml (
`id_ws_customhtml` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`id_ws_customhtml_group` INT(11) NOT NULL,
`title` VARCHAR(256) NOT NULL DEFAULT \'\',
`content` LONGTEXT,
`type` VARCHAR(32) NOT NULL DEFAULT \'html\',
`custom_class` 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,
@@ -65,6 +90,66 @@ trait Install
);
';
$sqlToExec[] = '
CREATE TABLE IF NOT EXISTS ' . _DB_PREFIX_ . 'ws_customhtml_lang (
`id_ws_customhtml` INT(11) NOT NULL,
`id_lang` INT(11) NOT NULL,
`title` VARCHAR(256) NOT NULL DEFAULT \'\',
`content` LONGTEXT,
`description` TEXT,
`extra_description` TEXT,
`link` VARCHAR(512) NOT NULL DEFAULT \'\',
`link_text` VARCHAR(128) NOT NULL DEFAULT \'\',
PRIMARY KEY (`id_ws_customhtml`, `id_lang`)
);
';
$sqlToExec[] = '
CREATE TABLE IF NOT EXISTS ' . _DB_PREFIX_ . 'ws_customhtml_faq (
`id_ws_customhtml_faq` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`id_ws_customhtml` INT(11) NOT NULL,
`ordering` INT(11) NOT NULL DEFAULT 0,
`date_add` DATETIME NOT NULL,
`date_upd` DATETIME NOT NULL,
KEY `id_ws_customhtml` (`id_ws_customhtml`)
);
';
$sqlToExec[] = '
CREATE TABLE IF NOT EXISTS ' . _DB_PREFIX_ . 'ws_customhtml_faq_lang (
`id_ws_customhtml_faq` INT(11) NOT NULL,
`id_lang` INT(11) NOT NULL,
`question` VARCHAR(512) NOT NULL DEFAULT \'\',
`answer` TEXT,
PRIMARY KEY (`id_ws_customhtml_faq`, `id_lang`)
);
';
$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,
`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`)
);
';
$sqlToExec[] = '
CREATE TABLE IF NOT EXISTS ' . _DB_PREFIX_ . 'ws_customhtml_slide_lang (
`id_ws_customhtml_slide` INT(11) NOT NULL,
`id_lang` 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 \'\',
PRIMARY KEY (`id_ws_customhtml_slide`, `id_lang`)
);
';
foreach ($sqlToExec as $sqlBlock) {
Db::getInstance()->execute(trim($sqlBlock));
}
@@ -83,6 +168,29 @@ trait Install
return true;
}
private function registerGroupHooks(): bool
{
$hookNames = Db::getInstance()->executeS(
'SELECT DISTINCT `hook_name` FROM `' . _DB_PREFIX_ . 'ws_customhtml_group`'
);
$idShop = (int) Context::getContext()->shop->id;
foreach ($hookNames as $row) {
$hookName = $row['hook_name'];
if (Hook::isModuleRegisteredOnHook($this, $hookName, $idShop)) {
continue;
}
if (!$this->registerHook($hookName)) {
return false;
}
}
return true;
}
private function registerTabs(array $moduleTabs): bool
{
$languages = Language::getLanguages(false);
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
@@ -0,0 +1,257 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file original="Modules.Wscustomhtml.Admin.xlf" source-language="en" target-language="nl" datatype="plaintext">
<body>
<trans-unit id="512923e3b931df3400cc06287e7541cc" approved="yes">
<source>Custom HTML</source>
<target state="final">Aangepaste HTML</target>
<note>File: ws_customhtml.php [Line: 39]</note>
</trans-unit>
<trans-unit id="7831af914461a744912d0579b8bb9d1a" approved="yes">
<source>Create and manage custom HTML blocks for frontend hooks.</source>
<target state="final">Aangepaste HTML-blokken maken en beheren voor frontend hooks.</target>
<note>File: ws_customhtml.php [Line: 40]</note>
</trans-unit>
<trans-unit id="b78a3223503896721cca1303f776159b" approved="yes">
<source>Title</source>
<target state="final">Titel</target>
<note>File: src/Form/Type/CustomHtmlGroupType.php [Line: 28]</note>
</trans-unit>
<trans-unit id="26f548bcbdeea5047396db341a10a2bf" approved="yes">
<source>Hook name</source>
<target state="final">Hooknaam</target>
<note>File: src/Form/Type/CustomHtmlGroupType.php [Line: 33]</note>
</trans-unit>
<trans-unit id="843575e3d0942a474aed722856631ec1" approved="yes">
<source>Custom group classes</source>
<target state="final">Aangepaste groepsklassen</target>
<note>File: src/Form/Type/CustomHtmlGroupType.php [Line: 37]</note>
</trans-unit>
<trans-unit id="c9339c0b1a199af41f690392077119af" approved="yes">
<source>Default block class</source>
<target state="final">Standaard blokklasse</target>
<note>File: src/Form/Type/CustomHtmlGroupType.php [Line: 42]</note>
</trans-unit>
<trans-unit id="dee82074a8fdd944350a8ed6ef7000ba" approved="yes">
<source>Applied to every block in this group automatically, in addition to that block&apos;s own custom classes. Useful when several blocks need the same column layout.</source>
<target state="final">Wordt automatisch toegepast op elk blok in deze groep, naast de eigen aangepaste klassen van dat blok. Handig wanneer meerdere blokken dezelfde kolomindeling nodig hebben.</target>
<note>File: src/Form/Type/CustomHtmlGroupType.php [Line: 45]</note>
</trans-unit>
<trans-unit id="4d3d769b812b6faa6b76e1a8abaece2d" approved="yes">
<source>Active</source>
<target state="final">Actief</target>
<note>File: src/Form/Type/CustomHtmlGroupType.php [Line: 48]</note>
</trans-unit>
<trans-unit id="3c889850381b0603721d568824ecfd18" approved="yes">
<source>Custom HTML Groups</source>
<target state="final">Aangepaste HTML-groepen</target>
<note>File: src/Grid/Definition/Factory/CustomHtmlGroupGridDefinitionFactory.php [Line: 40]</note>
</trans-unit>
<trans-unit id="b9b371458ab7c314f88b81c553f6ce51" approved="yes">
<source>Hook</source>
<target state="final">Hook</target>
<note>File: src/Grid/Definition/Factory/CustomHtmlGroupGridDefinitionFactory.php [Line: 73]</note>
</trans-unit>
<trans-unit id="04c4f9f595c4f21d1de65eb07056c15a" approved="yes">
<source>Blocks</source>
<target state="final">Blokken</target>
<note>File: src/Grid/Definition/Factory/CustomHtmlGroupGridDefinitionFactory.php [Line: 78]</note>
</trans-unit>
<trans-unit id="195a8106bcb998b48a86acb2fee032c5" approved="yes">
<source>View blocks</source>
<target state="final">Blokken bekijken</target>
<note>File: src/Grid/Definition/Factory/CustomHtmlGroupGridDefinitionFactory.php [Line: 156]</note>
</trans-unit>
<trans-unit id="2ff8071bcd15a31f9a743f1b06d0d00f" approved="yes">
<source>Add block</source>
<target state="final">Blok toevoegen</target>
<note>File: src/Grid/Definition/Factory/CustomHtmlGroupGridDefinitionFactory.php [Line: 166]</note>
</trans-unit>
<trans-unit id="93ee75bea0afaff1edf1444b1f8d4757" approved="yes">
<source>Custom HTML Blocks</source>
<target state="final">Aangepaste HTML-blokken</target>
<note>File: src/Grid/Definition/Factory/CustomHtmlBlockGridDefinitionFactory.php [Line: 41]</note>
</trans-unit>
<trans-unit id="03937134cedab9078be39a77ee3a48a0" approved="yes">
<source>Group</source>
<target state="final">Groep</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 41]</note>
</trans-unit>
<trans-unit id="a1fa27779242b4902f7ae3bdd5c6d508" approved="yes">
<source>Type</source>
<target state="final">Type</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 47]</note>
</trans-unit>
<trans-unit id="52f5e0bc3859bc5f5e25130b6c7e8881" approved="yes">
<source>Position</source>
<target state="final">Positie</target>
<note>File: src/Grid/Definition/Factory/CustomHtmlBlockGridDefinitionFactory.php [Line: 89]</note>
</trans-unit>
<trans-unit id="7c060faccd05262f4901c16573f645a9" approved="yes">
<source>Unable to find the group.</source>
<target state="final">Kan de groep niet vinden.</target>
<note>File: src/Controller/CustomHtmlGroupController.php [Line: 98]</note>
</trans-unit>
<trans-unit id="d738152dd177b4600836d2db542e8845" approved="yes">
<source>Unable to find the block.</source>
<target state="final">Kan het blok niet vinden.</target>
<note>File: src/Controller/CustomHtmlBlockController.php [Line: 125]</note>
</trans-unit>
<trans-unit id="b5a812dff3c33b63cf77f0ce007da42e" approved="yes">
<source>Block type</source>
<target state="final">Bloktype</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 47]</note>
</trans-unit>
<trans-unit id="507b7246f245b8935c5aafd81e80c4ee" approved="yes">
<source>Show title</source>
<target state="final">Titel tonen</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 59]</note>
</trans-unit>
<trans-unit id="f15c1cae7882448b3fb0404682e17e61" approved="yes">
<source>Content</source>
<target state="final">Inhoud</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 65]</note>
</trans-unit>
<trans-unit id="be53a0541a6d36f6ecb879fa2c584b08" approved="yes">
<source>Image</source>
<target state="final">Afbeelding</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 71]</note>
</trans-unit>
<trans-unit id="d60bc7c265eb450473afbd22ececda09" approved="yes">
<source>Slider mode</source>
<target state="final">Slidermodus</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 92]</note>
</trans-unit>
<trans-unit id="9e2352975b97b9b49e97dcd80788ed88" approved="yes">
<source>&quot;Mobile only&quot;: slider only on mobile, a static grid shows instead from tablet up.</source>
<target state="final">&quot;Alleen mobiel&quot;: slider alleen op mobiel, vanaf tablet wordt in plaats daarvan een statisch raster getoond.</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 95]</note>
</trans-unit>
<trans-unit id="0cffdd608d41a4c9c7bac7cf621ca5b9" approved="yes">
<source>Slides visible on mobile</source>
<target state="final">Zichtbare slides op mobiel</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 99]</note>
</trans-unit>
<trans-unit id="35bf3a5e2797e56b2d06b18acaae41f9" approved="yes">
<source>Slides visible on tablet</source>
<target state="final">Zichtbare slides op tablet</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 106]</note>
</trans-unit>
<trans-unit id="40d77e75964dd04ea925f34300dd8b48" approved="yes">
<source>Slides visible on desktop</source>
<target state="final">Zichtbare slides op desktop</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 113]</note>
</trans-unit>
<trans-unit id="06b9281e396db002010bde1de57262eb" approved="yes">
<source>Auto</source>
<target state="final">Automatisch</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 116]</note>
</trans-unit>
<trans-unit id="921048c199ee33279927fc77d2dc213a" approved="yes">
<source>In &quot;Mobile only&quot; mode this is rounded to the nearest whole number of columns. Leave empty for an equal-width column.</source>
<target state="final">In de modus &quot;Alleen mobiel&quot; wordt dit afgerond naar het dichtstbijzijnde hele aantal kolommen. Laat leeg voor een kolom met gelijke breedte.</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 117]</note>
</trans-unit>
<trans-unit id="db03b62817b3b2f4657b679c13bb5b28" approved="yes">
<source>Autoplay delay (seconds)</source>
<target state="final">Vertraging automatisch afspelen (seconden)</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 121]</note>
</trans-unit>
<trans-unit id="ae8c3292c47fef302c93202badd63109" approved="yes">
<source>0 means the slider never auto-advances; visitors can still swipe or use the arrows.</source>
<target state="final">0 betekent dat de slider nooit automatisch doorschuift; bezoekers kunnen nog wel swipen of de pijlen gebruiken.</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 124]</note>
</trans-unit>
<trans-unit id="d479cd75faccc438f93b8b6414a2a329" approved="yes">
<source>Alert style</source>
<target state="final">Waarschuwingsstijl</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 128]</note>
</trans-unit>
<trans-unit id="4527eae2e84aed7a238504cbbf509272" approved="yes">
<source>Auto-dismiss after (seconds)</source>
<target state="final">Automatisch sluiten na (seconden)</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 134]</note>
</trans-unit>
<trans-unit id="e59eb2e617eb720ae30bbf71010a7a97" approved="yes">
<source>0 means the alert never auto-dismisses; visitors can still close it manually.</source>
<target state="final">0 betekent dat de melding nooit automatisch sluit; bezoekers kunnen deze nog wel handmatig sluiten.</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 137]</note>
</trans-unit>
<trans-unit id="b5a7adde1af5c87d7fd797b6245c2a39" approved="yes">
<source>Description</source>
<target state="final">Beschrijving</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 142]</note>
</trans-unit>
<trans-unit id="7cc0fd746c5cec9842dd8d5251ee2352" approved="yes">
<source>Extra description</source>
<target state="final">Extra beschrijving</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 149]</note>
</trans-unit>
<trans-unit id="5e4e1bd6f922fed062b62c446ca34f84" approved="yes">
<source>Link URL</source>
<target state="final">Link-URL</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 156]</note>
</trans-unit>
<trans-unit id="63a11faa3a692d4e00fa8e03bbe8a0d6" approved="yes">
<source>Link text</source>
<target state="final">Linktekst</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 163]</note>
</trans-unit>
<trans-unit id="c15a6ec85c9e2dfc65fde2c60b592564" approved="yes">
<source>Custom classes</source>
<target state="final">Aangepaste klassen</target>
<note>File: src/Form/Type/CustomHtmlBlockType.php [Line: 171]</note>
</trans-unit>
<trans-unit id="a97ea56b0e00b2379736ae60869ff66a" approved="yes">
<source>Question</source>
<target state="final">Vraag</target>
<note>File: src/Form/Type/CustomHtmlFaqType.php [Line: 24]</note>
</trans-unit>
<trans-unit id="3751695c17afed4055a7a5ad5d46f6f5" approved="yes">
<source>Answer</source>
<target state="final">Antwoord</target>
<note>File: src/Form/Type/CustomHtmlFaqType.php [Line: 28]</note>
</trans-unit>
<trans-unit id="c28fc5d6c4a0a5ee47e06d56bab36dec" approved="yes">
<source>Custom HTML Block</source>
<target state="final">Aangepast HTML-blok</target>
<note>File: views/templates/admin/form/blocks/block_form.html.twig [Line: 26]</note>
</trans-unit>
<trans-unit id="73be1b885def51a0ff0d8905365b3eeb" approved="yes">
<source>Current image</source>
<target state="final">Huidige afbeelding</target>
<note>File: views/templates/admin/form/blocks/block_form.html.twig [Line: 42]</note>
</trans-unit>
<trans-unit id="4dfb7691cbfd8ee3a0f42018bcb2db65" approved="yes">
<source>FAQ items</source>
<target state="final">FAQ-items</target>
<note>File: views/templates/admin/form/blocks/block_form.html.twig [Line: 50]</note>
</trans-unit>
<trans-unit id="a4156d4998aa5183e106a57342f7315a" approved="yes">
<source>Add FAQ item</source>
<target state="final">FAQ-item toevoegen</target>
<note>File: views/templates/admin/form/blocks/block_form.html.twig [Line: 70]</note>
</trans-unit>
<trans-unit id="a8d90ed8f17c347c8d46e0ef22c85ecb" approved="yes">
<source>Slides</source>
<target state="final">Slides</target>
<note>File: views/templates/admin/form/blocks/block_form.html.twig [Line: 76]</note>
</trans-unit>
<trans-unit id="056f53405bf3cf4bde3731c26d725365" approved="yes">
<source>Add slide</source>
<target state="final">Slide toevoegen</target>
<note>File: views/templates/admin/form/blocks/block_form.html.twig [Line: 96]</note>
</trans-unit>
<trans-unit id="5ff997cff41cb8eeb86e6e484233a8af" approved="yes">
<source>The group that this block belongs to applies the classes &quot;%s%&quot; to every block. Edit the group to change this - it is stored under the &quot;Default block class&quot; input.</source>
<target state="final">De groep waartoe dit blok behoort past de klassen &quot;%s%&quot; toe op elk blok. Bewerk de groep om dit te wijzigen - dit wordt opgeslagen onder het veld &quot;Standaard blokklasse&quot;.</target>
<note>File: views/templates/admin/form/blocks/block_form.html.twig [Line: 118]</note>
</trans-unit>
<trans-unit id="5973aee626e71dba35de5485ccde9481" approved="yes">
<source>Custom HTML Group</source>
<target state="final">Aangepaste HTML-groep</target>
<note>File: views/templates/admin/form/blocks/group_form.html.twig [Line: 9]</note>
</trans-unit>
</body>
</file>
</xliff>
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file original="Modules.Wscustomhtml.Shop.xlf" source-language="en" target-language="nl" datatype="plaintext">
<body>
<trans-unit id="d59048f21fd887ad520398ce677be586" approved="yes">
<source>Learn more</source>
<target state="final">Meer informatie</target>
<note>File: views/templates/hook/_alert.tpl [Line: 20]</note>
</trans-unit>
<trans-unit id="d3d2e617335f08df83599665eef8a418" approved="yes">
<source>Close</source>
<target state="final">Sluiten</target>
<note>File: views/templates/hook/_alert.tpl [Line: 23]</note>
</trans-unit>
</body>
</file>
</xliff>
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+33
View File
@@ -9,3 +9,36 @@ td.action-type .btn-group-action {
align-items: center;
justify-content: center;
}
@media (min-width: 544px) {
.form-horizontal
.form-group.row
> .form-control-label:first-of-type:not(.label-on-top) {
flex: 0 0 auto !important;
max-width: unset !important;
text-align: left !important;
}
form[name="custom_html_block"] .card-body,
form[name="custom_html_group"] .card-body {
padding: 25px 50px !important;
}
form[name="custom_html_block"] .card-header,
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;
}
}
@media (min-width: 1500px) {
form[name="custom_html_block"] .card-body,
form[name="custom_html_group"] .card-body {
padding: 25px 300px !important;
}
form[name="custom_html_block"] .card-header,
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;
}
}
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+116
View File
@@ -0,0 +1,116 @@
/* Banners */
.customhtml-block img {
max-width: 100%;
height: auto;
}
.customhtml-banner-content {
padding: 2rem;
bottom: 0px;
position: absolute;
z-index: 2;
}
.customhtml-banner-title {
margin-top: 0;
}
.customhtml-banner-description,
.customhtml-banner-extra-description {
margin-bottom: 1rem;
}
.customhtml-banner-image {
position: relative;
}
.customhtml-banner-image--gradient {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 1;
}
/* Alert */
.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;
}
.customhtml-alert-btn {
margin-left: 0.75rem;
white-space: nowrap;
}
.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;
}
/* 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;
}
.accordion-icon-plus {
display: none;
}
.customhtml-faq-accordion .btn.collapsed .accordion-icon-minus {
display: none;
}
.customhtml-faq-accordion .btn.collapsed .accordion-icon-plus {
display: block;
}
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
View File
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+64
View File
@@ -0,0 +1,64 @@
$(document).ready(function () {
window.prestashop.component.initComponents([
'TinyMCEEditor',
'TranslatableField',
'TranslatableInput',
]);
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 toggleGroupDefaultClassHint() {
var hint = $('#customhtmlblock-group-default-class-hint');
var defaultClass = $('#custom_html_block_id_ws_customhtml_group option:selected').data('defaultBlockClass');
if (defaultClass) {
hint.text(hint.data('template').replace('%s%', defaultClass)).show();
} else {
hint.hide();
}
}
$('#custom_html_block_id_ws_customhtml_group').on('change', toggleGroupDefaultClassHint);
toggleGroupDefaultClassHint();
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 = $('<div class="' + itemClass + ' card mb-2"><div class="card-body"></div></div>');
item.find('.card-body')
.append(prototype)
.append(
'<button type="button" class="btn btn-outline-danger btn-sm ' + removeButtonClass + '">' +
'<i class="material-icons">delete</i> ' + removeLabel + '</button>'
);
container.append(item);
container.data('index', index + 1);
});
container.on('click', '.' + removeButtonClass, function () {
$(this).closest('.' + itemClass).remove();
});
}
initRepeater('#customhtmlblock-faq-items', '#customhtmlblock-add-faq', 'customhtmlblock-faq-item', 'customhtmlblock-remove-faq');
initRepeater('#customhtmlblock-slide-items', '#customhtmlblock-add-slide', 'customhtmlblock-slide-item', 'customhtmlblock-remove-slide');
});
+9
View File
@@ -0,0 +1,9 @@
$(document).ready(function () {
const customhtmlblockGrid = new window.prestashop.component.Grid('customhtmlblock');
customhtmlblockGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
customhtmlblockGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
customhtmlblockGrid.addExtension(new window.prestashop.component.GridExtensions.ColumnTogglingExtension());
customhtmlblockGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
customhtmlblockGrid.addExtension(new window.prestashop.component.GridExtensions.PositionExtension());
});
+6
View File
@@ -0,0 +1,6 @@
$(document).ready(function () {
window.prestashop.component.initComponents([
'TranslatableField',
'TranslatableInput',
]);
});
+9
View File
@@ -0,0 +1,9 @@
$(document).ready(function () {
const customhtmlgroupGrid = new window.prestashop.component.Grid('customhtmlgroup');
customhtmlgroupGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
customhtmlgroupGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
customhtmlgroupGrid.addExtension(new window.prestashop.component.GridExtensions.ColumnTogglingExtension());
customhtmlgroupGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
customhtmlgroupGrid.addExtension(new window.prestashop.component.GridExtensions.PositionExtension());
});
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+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();
});
}
});
});
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
@@ -1,6 +1,23 @@
{% import '@PrestaShop/Admin/macros.html.twig' as ps %}
{% form_theme customHtmlBlockForm '@PrestaShop/Admin/TwigTemplateForm/prestashop_ui_kit.html.twig' %}
{% macro faqFields(faqForm) %}
{{ form_row(faqForm.question) }}
{{ form_row(faqForm.answer) }}
{% endmacro %}
{% import _self as faqMacros %}
{% macro slideFields(slideForm) %}
{{ form_widget(slideForm.id) }}
{{ form_row(slideForm.title) }}
{{ form_row(slideForm.description) }}
{{ form_row(slideForm.extra_description) }}
{{ form_row(slideForm.link) }}
{{ form_row(slideForm.link_text) }}
{{ form_row(slideForm.image) }}
{% endmacro %}
{% import _self as slideMacros %}
{% block customhtmlblock_form_block %}
<div class="card">
{{ form_start(customHtmlBlockForm) }}
@@ -10,10 +27,102 @@
</h3>
</div>
<div class="card-body">
{{ form_row(customHtmlBlockForm.id_ws_customhtml_group) }}
{{ form_row(customHtmlBlockForm.type) }}
<div class="row">
{{ form_row(customHtmlBlockForm.title) }}
{{ form_row(customHtmlBlockForm.show_title) }}
</div>
{{ form_row(customHtmlBlockForm.content) }}
{% if currentImageUrl is defined and currentImageUrl %}
<div class="customhtmlblock-type-field customhtmlblock-type-banner customhtmlblock-type-usp customhtmlblock-current-image">
<label class="form-control-label">{{ 'Current image'|trans({}, 'Modules.Wscustomhtml.Admin') }}</label>
<div><img src="{{ currentImageUrl }}" alt="" style="max-width: 200px; max-height: 200px;"></div>
</div>
{% endif %}
{{ form_row(customHtmlBlockForm.image) }}
<div class="customhtmlblock-type-field customhtmlblock-type-faq">
<label class="form-control-label">
{{ 'FAQ items'|trans({}, 'Modules.Wscustomhtml.Admin') }}
</label>
<div
id="customhtmlblock-faq-items"
data-prototype="{{ faqMacros.faqFields(customHtmlBlockForm.faqs.vars.prototype)|e('html_attr') }}"
data-index="{{ customHtmlBlockForm.faqs|length }}"
data-remove-label="{{ 'Remove'|trans({}, 'Admin.Actions')|e('html_attr') }}"
>
{% for faqRow in customHtmlBlockForm.faqs %}
<div class="customhtmlblock-faq-item card mb-2">
<div class="card-body">
{{ faqMacros.faqFields(faqRow) }}
<button type="button" class="btn btn-outline-danger btn-sm customhtmlblock-remove-faq">
<i class="material-icons">delete</i> {{ 'Remove'|trans({}, 'Admin.Actions') }}
</button>
</div>
</div>
{% endfor %}
</div>
<button type="button" id="customhtmlblock-add-faq" class="btn btn-outline-primary btn-sm">
<i class="material-icons">add</i> {{ 'Add FAQ item'|trans({}, 'Modules.Wscustomhtml.Admin') }}
</button>
</div>
<div class="customhtmlblock-type-field customhtmlblock-type-slider">
<label class="form-control-label">
{{ 'Slides'|trans({}, 'Modules.Wscustomhtml.Admin') }}
</label>
<div
id="customhtmlblock-slide-items"
data-prototype="{{ slideMacros.slideFields(customHtmlBlockForm.slides.vars.prototype)|e('html_attr') }}"
data-index="{{ customHtmlBlockForm.slides|length }}"
data-remove-label="{{ 'Remove'|trans({}, 'Admin.Actions')|e('html_attr') }}"
>
{% for slideRow in customHtmlBlockForm.slides %}
<div class="customhtmlblock-slide-item card mb-2">
<div class="card-body">
{{ slideMacros.slideFields(slideRow) }}
<button type="button" class="btn btn-outline-danger btn-sm customhtmlblock-remove-slide">
<i class="material-icons">delete</i> {{ 'Remove'|trans({}, 'Admin.Actions') }}
</button>
</div>
</div>
{% endfor %}
</div>
<button type="button" id="customhtmlblock-add-slide" class="btn btn-outline-primary btn-sm">
<i class="material-icons">add</i> {{ 'Add slide'|trans({}, 'Modules.Wscustomhtml.Admin') }}
</button>
</div>
{{ form_row(customHtmlBlockForm.slider_mode) }}
<div class="row">
{{ form_row(customHtmlBlockForm.slides_per_view_mobile) }}
{{ form_row(customHtmlBlockForm.slides_per_view_tablet) }}
{{ form_row(customHtmlBlockForm.slides_per_view_desktop) }}
</div>
{{ form_row(customHtmlBlockForm.autoplay_delay) }}
{{ form_row(customHtmlBlockForm.alert_type) }}
{{ form_row(customHtmlBlockForm.timeout) }}
{{ form_row(customHtmlBlockForm.description) }}
{{ form_row(customHtmlBlockForm.extra_description) }}
{{ form_row(customHtmlBlockForm.link) }}
{{ form_row(customHtmlBlockForm.link_text) }}
<p
id="customhtmlblock-group-default-class-hint"
class="alert alert-warning"
data-template="{{ 'The group that this block belongs to applies the classes "%s%" to every block. Edit the group to change this - it is stored under the "Default block class" input.'|trans({}, 'Modules.Wscustomhtml.Admin')|e('html_attr') }}"
></p>
{{ form_row(customHtmlBlockForm.custom_class) }}
{{ form_row(customHtmlBlockForm.active) }}
{{ form_rest(customHtmlBlockForm) }}
</div>
<div class="card-footer">
<a href="{{ path('customhtmlblock_index') }}" class="btn btn-outline-secondary">
<a href="{{ path('customhtmlblock_index', idGroup is defined and idGroup ? {'idGroup': idGroup} : {}) }}" class="btn btn-outline-secondary">
{{ 'Cancel'|trans({}, 'Admin.Actions') }}
</a>
<button class="btn btn-primary float-right">
@@ -17,6 +17,9 @@
{{ 'Cancel'|trans({}, 'Admin.Actions') }}
</a>
{% if idCustomHtmlGroup is defined %}
<a href="{{ path('customhtmlblock_index', {'idGroup': idCustomHtmlGroup}) }}" class="btn btn-outline-primary">
{{ 'View blocks'|trans({}, 'Modules.Wscustomhtml.Admin') }}
</a>
<a href="{{ path('customhtmlblock_create', {'idGroup': idCustomHtmlGroup}) }}" class="btn btn-outline-primary">
{{ 'Add block'|trans({}, 'Modules.Wscustomhtml.Admin') }}
</a>
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
@@ -8,11 +8,5 @@
{% block javascripts %}
{{ parent() }}
<script>
$(document).ready(function () {
window.prestashop.component.initComponents([
'TinyMCEEditor',
]);
});
</script>
<script src="{{ asset('../modules/ws_customhtml/views/js/admin/customhtmlblock_form.js') }}"></script>
{% endblock %}
@@ -8,11 +8,5 @@
{% block javascripts %}
{{ parent() }}
<script>
$(document).ready(function () {
window.prestashop.component.initComponents([
'TinyMCEEditor',
]);
});
</script>
<script src="{{ asset('../modules/ws_customhtml/views/js/admin/customhtmlblock_form.js') }}"></script>
{% endblock %}
@@ -5,3 +5,8 @@
{% include '@Modules/ws_customhtml/views/templates/admin/form/blocks/group_form.html.twig' %}
{% endblock %}
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script src="{{ asset('../modules/ws_customhtml/views/js/admin/customhtmlgroup_form.js') }}"></script>
{% endblock %}
@@ -5,3 +5,8 @@
{% include '@Modules/ws_customhtml/views/templates/admin/form/blocks/group_form.html.twig' %}
{% endblock %}
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script src="{{ asset('../modules/ws_customhtml/views/js/admin/customhtmlgroup_form.js') }}"></script>
{% endblock %}
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
@@ -12,14 +12,6 @@
{% block javascripts %}
{{ parent() }}
<script>
$(document).ready(function () {
const customhtmlblockGrid = new window.prestashop.component.Grid('customhtmlblock');
customhtmlblockGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
customhtmlblockGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
customhtmlblockGrid.addExtension(new window.prestashop.component.GridExtensions.ColumnTogglingExtension());
customhtmlblockGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
});
</script>
<script src="{{ asset('themes/default/js/bundle/pagination.js') }}"></script>
<script src="{{ asset('../modules/ws_customhtml/views/js/admin/customhtmlblock_grid.js') }}"></script>
{% endblock %}
@@ -12,14 +12,6 @@
{% block javascripts %}
{{ parent() }}
<script>
$(document).ready(function () {
const customhtmlgroupGrid = new window.prestashop.component.Grid('customhtmlgroup');
customhtmlgroupGrid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
customhtmlgroupGrid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
customhtmlgroupGrid.addExtension(new window.prestashop.component.GridExtensions.ColumnTogglingExtension());
customhtmlgroupGrid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
});
</script>
<script src="{{ asset('themes/default/js/bundle/pagination.js') }}"></script>
<script src="{{ asset('../modules/ws_customhtml/views/js/admin/customhtmlgroup_grid.js') }}"></script>
{% endblock %}
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+30
View File
@@ -0,0 +1,30 @@
{if !empty($block.description)}
<div
class="alert alert-{$block.alert_type|escape:'html':'UTF-8'} alert-dismissible customhtml-alert"
role="alert"
data-timeout="{$block.timeout|intval}"
>
<div class="row">
{block name=alert_content}
<div class="col d-flex align-items-center">
{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>
</div>
{/block}
{block name=alert_actions}
<div class="col-auto">
{if !empty($block.link)}
<a href="{$block.link|escape:'html':'UTF-8'}" class="btn btn-{$block.alert_type|escape:'html':'UTF-8'} customhtml-alert-btn">
{if !empty($block.link_text)}{$block.link_text|escape:'html':'UTF-8'}{else}{l s='Learn more' d='Modules.Wscustomhtml.Shop'}{/if}
</a>
{/if}
<button type="button" class="close customhtml-alert-close" aria-label="{l s='Close' d='Modules.Wscustomhtml.Shop'}">
<span aria-hidden="true">&times;</span>
</button>
</div>
{/block}
</div>
</div>
{/if}
+38
View File
@@ -0,0 +1,38 @@
<div class="customhtml-banner row flex-column position-relative">
{block name=banner_image}
<div class="customhtml-banner-image">
<div class="customhtml-banner-image--gradient"></div>
{if !empty($block.image_url)}
<img src="{$block.image_url|escape:'html':'UTF-8'}" alt="{$block.title|escape:'html':'UTF-8'}">
{else}
<img
src="{$urls.img_ps_url}noimageavailable.jpg"
alt="{$block.title|escape:'html':'UTF-8'}"
width="100%"
class="img-fluid rounded lazyload"
/>
{/if}
</div>
{/block}
{block name=banner_content}
<div class="customhtml-banner-content">
{if !empty($count)}
<span class="customhtml-banner-count font-monospace">{$count}</span>
{/if}
{if $block.show_title && !empty($block.title)}
<h3 class="customhtml-banner-title">{$block.title|escape:'html':'UTF-8'}</h3>
{/if}
{if !empty($block.description)}
<div class="customhtml-banner-description">{$block.description|escape:'html':'UTF-8'|nl2br}</div>
{/if}
{if !empty($block.extra_description)}
<div class="customhtml-banner-extra-description">{$block.extra_description|escape:'html':'UTF-8'|nl2br}</div>
{/if}
{if !empty($block.link)}
<a href="{$block.link|escape:'html':'UTF-8'}" class="btn btn-primary customhtml-banner-link">
{if !empty($block.link_text)}{$block.link_text|escape:'html':'UTF-8'}{else}{l s='Learn more' d='Modules.Wscustomhtml.Shop'}{/if}
</a>
{/if}
</div>
{/block}
</div>
+61
View File
@@ -0,0 +1,61 @@
{if !empty($block.faqs)}
<div class="accordion customhtml-faq-accordion" id="customhtml-faq-{$block.id_ws_customhtml|intval}">
{foreach from=$block.faqs item="faq" name="customhtmlFaqLoop"}
<div class="card">
{block name=faq_question}
<div class="card-header" id="customhtml-faq-heading-{$block.id_ws_customhtml|intval}-{$faq.id_ws_customhtml_faq|intval}">
{if !empty($faq.answer)}
<button
class="btn btn-link col-12 d-flex text-left{if !$smarty.foreach.customhtmlFaqLoop.first} collapsed{/if}"
type="button"
data-toggle="collapse"
data-target="#customhtml-faq-collapse-{$block.id_ws_customhtml|intval}-{$faq.id_ws_customhtml_faq|intval}"
aria-expanded="{if $smarty.foreach.customhtmlFaqLoop.first}true{else}false{/if}"
aria-controls="customhtml-faq-collapse-{$block.id_ws_customhtml|intval}-{$faq.id_ws_customhtml_faq|intval}"
>
{/if}
<div class="faq-question col pl-0">
{$faq.question|escape:'html':'UTF-8'}
</div>
{if !empty($faq.answer)}
<div class="col-auto d-flex align-items-center">
<span class="accordion-icons accordion-icon-minus">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="2" viewBox="0 0 28 2">
<line id="Line_18" data-name="Line 18" x2="26" transform="translate(1 1)" fill="none" stroke="#272727" stroke-linecap="round" stroke-width="2"/>
</svg>
</span>
<span class="accordion-icons accordion-icon-plus">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 28 28">
<g id="Group_31" data-name="Group 31" transform="translate(-1615 -4031.5)">
<line id="Line_21" data-name="Line 21" x2="26" transform="translate(1616 4045.5)" fill="none" stroke="#272727" stroke-linecap="round" stroke-width="2"/>
<line id="Line_22" data-name="Line 22" x2="26" transform="translate(1629 4032.5) rotate(90)" fill="none" stroke="#272727" stroke-linecap="round" stroke-width="2"/>
</g>
</svg>
</span>
</div>
</button>
{/if}
</div>
{/block}
{if !empty($faq.answer)}
{block name=faq_answer}
<div
id="customhtml-faq-collapse-{$block.id_ws_customhtml|intval}-{$faq.id_ws_customhtml_faq|intval}"
class="collapse{if $smarty.foreach.customhtmlFaqLoop.first} show{/if}"
aria-labelledby="customhtml-faq-heading-{$block.id_ws_customhtml|intval}-{$faq.id_ws_customhtml_faq|intval}"
data-parent="#customhtml-faq-{$block.id_ws_customhtml|intval}"
>
<div class="card-body">
{$faq.answer nofilter}
</div>
</div>
{/block}
{/if}
</div>
{/foreach}
</div>
{/if}
+1
View File
@@ -0,0 +1 @@
{extends file="module:ws_customhtml/views/templates/hook/_banner.tpl"}
+59
View File
@@ -0,0 +1,59 @@
{if !empty($block.slides)}
{if $block.slider_mode == 'responsive'}
{block name=slider_responsive}
<div class="d-lg-none">
<div class="swiper customhtml-slider" data-swiper="{$block.slider_config|escape:'html':'UTF-8'}">
<div class="swiper-wrapper">
{foreach from=$block.slides item="slide" name="slides"}
{assign var="count" value=$smarty.foreach.slides.iteration|string_format:"%02d"}
<div class="swiper-slide">
{assign var="customhtmlOverridePath" value="{$smarty.const._PS_THEME_DIR_}modules/ws_customhtml/views/templates/hook/{$hookName}/slide.tpl"}
{if file_exists($customhtmlOverridePath)}
{include file="module:ws_customhtml/views/templates/hook/`$hookName`/slide.tpl" block=$slide group=$group hookName=$hookName count=$count}
{else}
{include file="module:ws_customhtml/views/templates/hook/_slide.tpl" block=$slide count=$count}
{/if}
</div>
{/foreach}
</div>
</div>
</div>
<div class="row d-none d-lg-flex customhtml-slider-grid">
{foreach from=$block.slides item="slide" name="slides"}
{assign var="count" value=$smarty.foreach.slides.iteration|string_format:"%02d"}
{if !empty($block.slider_desktop_column_width)}
<div class="customhtml-slider-grid-item" style="flex: 0 0 {$block.slider_desktop_column_width|escape:'html':'UTF-8'}; max-width: {$block.slider_desktop_column_width|escape:'html':'UTF-8'};">
{else}
<div class="customhtml-slider-grid-item col">
{/if}
{assign var="customhtmlOverridePath" value="{$smarty.const._PS_THEME_DIR_}modules/ws_customhtml/views/templates/hook/{$hookName}/slide.tpl"}
{if file_exists($customhtmlOverridePath)}
{include file="module:ws_customhtml/views/templates/hook/`$hookName`/slide.tpl" block=$slide group=$group hookName=$hookName count=$count}
{else}
{include file="module:ws_customhtml/views/templates/hook/_slide.tpl" block=$slide count=$count}
{/if}
</div>
{/foreach}
</div>
{/block}
{/if}
{if $block.slider_mode != 'responsive'}
{block name=slider_always}
<div class="swiper customhtml-slider" data-swiper="{$block.slider_config|escape:'html':'UTF-8'}">
<div class="swiper-wrapper">
{foreach from=$block.slides item="slide" name="slides"}
{assign var="count" value=$smarty.foreach.slides.iteration|string_format:"%02d"}
<div class="swiper-slide">
{assign var="customhtmlOverridePath" value="{$smarty.const._PS_THEME_DIR_}modules/ws_customhtml/views/templates/hook/{$hookName}/slide.tpl"}
{if file_exists($customhtmlOverridePath)}
{include file="module:ws_customhtml/views/templates/hook/`$hookName`/slide.tpl" block=$slide group=$group hookName=$hookName count=$count}
{else}
{include file="module:ws_customhtml/views/templates/hook/_slide.tpl" block=$slide count=$count}
{/if}
</div>
{/foreach}
</div>
</div>
{/block}
{/if}
{/if}
+27
View File
@@ -0,0 +1,27 @@
{if !empty($block.image_url) || ($block.show_title && !empty($block.title)) || !empty($block.description)}
{if !empty($block.link)}
<a href="{$block.link|escape:'html':'UTF-8'}" class="customhtml-usp d-flex align-items-center">
{else}
<div class="customhtml-usp d-flex align-items-center">
{/if}
{block name=usp_icon}
{if !empty($block.image_url)}
<span class="customhtml-usp-icon">
<img src="{$block.image_url|escape:'html':'UTF-8'}" alt="">
</span>
{/if}
{/block}
{block name=usp_content}
{if $block.show_title && !empty($block.title)}
<span class="customhtml-usp-text">{$block.title|escape:'html':'UTF-8'}</span>
{/if}
{if !empty($block.description)}
<span class="customhtml-usp-description">{$block.description|escape:'html':'UTF-8'|nl2br}</span>
{/if}
{/block}
{if !empty($block.link)}
</a>
{else}
</div>
{/if}
{/if}
+25 -3
View File
@@ -1,13 +1,35 @@
{if !empty($groupList)}
<div class="customhtml-container {$hookName|escape:'html':'UTF-8'}--container">
{foreach from=$groupList item="group"}
<div class="customhtml-group">
{foreach from=$group.blocks item="block"}
<div class="customhtml-block">
<div class="customhtml-group row{if !empty($group.custom_class)} {$group.custom_class|escape:'html':'UTF-8'}{/if}">
{foreach from=$group.blocks item="block" name="blocks"}
<div class="customhtml-block col{if !empty($group.default_block_class)} {$group.default_block_class|escape:'html':'UTF-8'}{/if}{if !empty($block.custom_class)} {$block.custom_class|escape:'html':'UTF-8'}{/if}">
{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 iteration=$smarty.foreach.blocks.iteration}
{elseif $block.type == 'faq'}
{if $block.show_title && !empty($block.title)}
<div class="customhtml-title">{$block.title|escape:'html':'UTF-8'}</div>
{/if}
{include file="module:ws_customhtml/views/templates/hook/_faq_accordion.tpl" block=$block}
{elseif $block.type == 'banner'}
{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}
{elseif $block.type == 'slider'}
{if $block.show_title && !empty($block.title)}
<div class="customhtml-title">{$block.title|escape:'html':'UTF-8'}</div>
{/if}
{include file="module:ws_customhtml/views/templates/hook/_slider.tpl" block=$block}
{else}
{if $block.show_title && !empty($block.title)}
<div class="customhtml-title">{$block.title|escape:'html':'UTF-8'}</div>
{/if}
<div class="customhtml-content">{$block.content nofilter}</div>
{/if}
</div>
{/foreach}
</div>
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+8
View File
@@ -0,0 +1,8 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
+1 -1
View File
@@ -29,7 +29,7 @@ class Ws_CustomHtml extends Module implements WidgetInterface
public function __construct()
{
$this->name = 'ws_customhtml';
$this->version = '1.1.0';
$this->version = '1.24.0';
$this->author = 'Isabelle Oving-Anno | De Websmid BV';
$this->need_instance = 0;
$this->bootstrap = true;