Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb641fc236 | ||
|
|
d0da05845a | ||
|
|
1493081602 |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
@@ -33,6 +33,14 @@ services:
|
||||
arguments:
|
||||
- '@websmid.customhtml.form.customhtmlgroup_data_handler'
|
||||
|
||||
websmid.customhtml.form.type.customhtml_faq:
|
||||
public: true
|
||||
class: 'Websmid\CustomHtml\Form\Type\CustomHtmlFaqType'
|
||||
arguments:
|
||||
- '@translator'
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
websmid.customhtml.form.type.customhtml_block:
|
||||
public: true
|
||||
class: 'Websmid\CustomHtml\Form\Type\CustomHtmlBlockType'
|
||||
|
||||
@@ -68,3 +68,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
|
||||
|
||||
@@ -71,3 +71,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'
|
||||
|
||||
@@ -6,6 +6,13 @@ class Config
|
||||
{
|
||||
const HOOKS = [
|
||||
'actionAdminControllerSetMedia',
|
||||
'actionCustomhtmlblockGridDataModifier',
|
||||
'actionFrontControllerSetMedia',
|
||||
];
|
||||
|
||||
const BLOCK_TYPES = [
|
||||
'html' => 'HTML',
|
||||
'faq' => 'FAQ Accordion',
|
||||
];
|
||||
|
||||
const ADMIN_CONTROLLERS = [
|
||||
|
||||
@@ -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,6 +14,7 @@ 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
|
||||
@@ -107,12 +111,35 @@ class CustomHtmlBlockController extends FrameworkBundleAdminController
|
||||
return $this->redirectToRoute('customhtmlblock_index');
|
||||
}
|
||||
|
||||
CustomHtmlFaq::deleteByBlockId($idCustomHtmlBlock);
|
||||
$block->delete();
|
||||
$this->addFlash('success', $this->trans('Successful deletion.', 'Admin.Notifications.Success'));
|
||||
|
||||
return $this->redirectToRoute('customhtmlblock_index');
|
||||
}
|
||||
|
||||
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): array
|
||||
{
|
||||
$toolbarButtons = [];
|
||||
|
||||
@@ -4,24 +4,32 @@ namespace Websmid\CustomHtml\Form\DataHandler;
|
||||
|
||||
use PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataHandler\FormDataHandlerInterface;
|
||||
use Websmid\CustomHtml\Model\CustomHtmlBlock;
|
||||
use Websmid\CustomHtml\Model\CustomHtmlFaq;
|
||||
use Db;
|
||||
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->id_ws_customhtml_group = $idGroup;
|
||||
$block->title = (string) $formData['title'];
|
||||
$block->type = (string) $formData['type'];
|
||||
$block->show_title = (int) $formData['show_title'];
|
||||
$block->custom_class = (string) ($formData['custom_class'] ?? '');
|
||||
$block->content = (string) $formData['content'];
|
||||
$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'] ?? []);
|
||||
|
||||
return $block->id;
|
||||
}
|
||||
|
||||
@@ -35,10 +43,46 @@ final class CustomHtmlBlockDataHandler implements FormDataHandlerInterface
|
||||
|
||||
$block->id_ws_customhtml_group = (int) $formData['id_ws_customhtml_group'];
|
||||
$block->title = (string) $formData['title'];
|
||||
$block->type = (string) $formData['type'];
|
||||
$block->show_title = (int) $formData['show_title'];
|
||||
$block->custom_class = (string) ($formData['custom_class'] ?? '');
|
||||
$block->content = (string) $formData['content'];
|
||||
$block->active = (int) $formData['active'];
|
||||
|
||||
return (bool) $block->update();
|
||||
if (!$block->update()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->saveFaqs((int) $id, $formData['faqs'] ?? []);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getNextOrdering(int $idGroup): int
|
||||
{
|
||||
$maxOrdering = Db::getInstance()->getValue(
|
||||
'SELECT MAX(`ordering`) FROM `' . _DB_PREFIX_ . 'ws_customhtml`
|
||||
WHERE `id_ws_customhtml_group` = ' . $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) {
|
||||
if (empty($faqData['question'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$faq = new CustomHtmlFaq();
|
||||
$faq->id_ws_customhtml = $idBlock;
|
||||
$faq->question = (string) $faqData['question'];
|
||||
$faq->answer = (string) ($faqData['answer'] ?? '');
|
||||
$faq->ordering = $position;
|
||||
$faq->add();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Websmid\CustomHtml\Form\DataProvider;
|
||||
|
||||
use PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataProvider\FormDataProviderInterface;
|
||||
use Websmid\CustomHtml\Model\CustomHtmlBlock;
|
||||
use Websmid\CustomHtml\Model\CustomHtmlFaq;
|
||||
use Validate;
|
||||
|
||||
final class CustomHtmlBlockDataProvider implements FormDataProviderInterface
|
||||
@@ -19,8 +20,11 @@ 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,
|
||||
'faqs' => $this->loadFaqs((int) $id),
|
||||
'active' => (bool) $block->active,
|
||||
];
|
||||
}
|
||||
@@ -30,9 +34,22 @@ final class CustomHtmlBlockDataProvider implements FormDataProviderInterface
|
||||
return [
|
||||
'id_ws_customhtml_group' => 0,
|
||||
'title' => '',
|
||||
'type' => 'html',
|
||||
'show_title' => false,
|
||||
'custom_class' => '',
|
||||
'content' => '',
|
||||
'faqs' => [],
|
||||
'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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,13 @@ 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\TextType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use PrestaShop\PrestaShop\Core\Form\ConfigurableFormChoiceProviderInterface;
|
||||
use PrestaShopBundle\Form\Admin\Type\SwitchType;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Websmid\CustomHtml\Config\Config;
|
||||
|
||||
class CustomHtmlBlockType extends AbstractType
|
||||
{
|
||||
@@ -37,14 +39,33 @@ class CustomHtmlBlockType extends AbstractType
|
||||
'label' => $this->translator->trans('Title', [], 'Modules.Wscustomhtml.Admin'),
|
||||
'required' => true,
|
||||
])
|
||||
->add('type', ChoiceType::class, [
|
||||
'label' => $this->translator->trans('Block type', [], 'Modules.Wscustomhtml.Admin'),
|
||||
'choices' => array_flip(Config::BLOCK_TYPES),
|
||||
'required' => true,
|
||||
])
|
||||
->add('show_title', SwitchType::class, [
|
||||
'label' => $this->translator->trans('Show title', [], 'Modules.Wscustomhtml.Admin'),
|
||||
'required' => false,
|
||||
])
|
||||
->add('custom_class', TextType::class, [
|
||||
'label' => $this->translator->trans('Custom classes', [], 'Modules.Wscustomhtml.Admin'),
|
||||
'required' => false,
|
||||
'attr' => ['placeholder' => 'col-md-6'],
|
||||
])
|
||||
->add('content', TextareaType::class, [
|
||||
'label' => $this->translator->trans('Content', [], 'Modules.Wscustomhtml.Admin'),
|
||||
'required' => false,
|
||||
'attr' => ['class' => 'autoload_rte'],
|
||||
'row_attr' => ['class' => 'customhtmlblock-type-field customhtmlblock-type-html'],
|
||||
])
|
||||
->add('faqs', CollectionType::class, [
|
||||
'entry_type' => CustomHtmlFaqType::class,
|
||||
'allow_add' => true,
|
||||
'allow_delete' => true,
|
||||
'by_reference' => false,
|
||||
'prototype' => true,
|
||||
'required' => false,
|
||||
])
|
||||
->add('active', SwitchType::class, [
|
||||
'label' => $this->translator->trans('Active', [], 'Modules.Wscustomhtml.Admin'),
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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 Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class CustomHtmlFaqType extends AbstractType
|
||||
{
|
||||
private $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $formBuilder, array $options): void
|
||||
{
|
||||
$formBuilder
|
||||
->add('question', TextType::class, [
|
||||
'label' => $this->translator->trans('Question', [], 'Modules.Wscustomhtml.Admin'),
|
||||
'required' => false,
|
||||
])
|
||||
->add('answer', TextareaType::class, [
|
||||
'label' => $this->translator->trans('Answer', [], 'Modules.Wscustomhtml.Admin'),
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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')
|
||||
|
||||
@@ -14,7 +14,7 @@ final class CustomHtmlBlockFilters extends Filters
|
||||
return [
|
||||
'limit' => 10,
|
||||
'offset' => 0,
|
||||
'orderBy' => 'wchb.id_ws_customhtml',
|
||||
'orderBy' => 'position',
|
||||
'sortOrder' => 'asc',
|
||||
'filters' => [],
|
||||
];
|
||||
|
||||
@@ -43,8 +43,10 @@ final class CustomHtmlBlockQueryBuilder extends AbstractDoctrineQueryBuilder
|
||||
|
||||
$qb->select(
|
||||
'wchb.`id_ws_customhtml`',
|
||||
'wchb.`id_ws_customhtml_group`',
|
||||
'wchb.`title`',
|
||||
'wchg.`title` AS group_title',
|
||||
'wchb.`type`',
|
||||
'wchb.`active`',
|
||||
'wchb.`ordering`',
|
||||
'wchb.`date_add`'
|
||||
@@ -52,8 +54,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());
|
||||
|
||||
@@ -108,6 +112,12 @@ final class CustomHtmlBlockQueryBuilder extends AbstractDoctrineQueryBuilder
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($filterName === 'type') {
|
||||
$qb->andWhere('wchb.`type` = :type')
|
||||
->setParameter('type', pSQL($filterValue));
|
||||
continue;
|
||||
}
|
||||
|
||||
$qb->andWhere('wchb.`' . $filterName . '` LIKE :' . $filterName)
|
||||
->setParameter($filterName, '%' . pSQL($filterValue) . '%');
|
||||
}
|
||||
|
||||
@@ -16,9 +16,15 @@ class CustomHtmlBlock extends ObjectModel
|
||||
/** @var string */
|
||||
public $title = '';
|
||||
|
||||
/** @var string */
|
||||
public $type = 'html';
|
||||
|
||||
/** @var string */
|
||||
public $content = '';
|
||||
|
||||
/** @var string */
|
||||
public $custom_class = '';
|
||||
|
||||
/** @var int */
|
||||
public $active = 1;
|
||||
|
||||
@@ -40,7 +46,9 @@ class CustomHtmlBlock extends ObjectModel
|
||||
'fields' => [
|
||||
'id_ws_customhtml_group' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'title' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true, 'size' => 256],
|
||||
'type' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true, 'size' => 32],
|
||||
'content' => ['type' => self::TYPE_HTML, 'validate' => 'isCleanHtml'],
|
||||
'custom_class' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => false, 'size' => 128],
|
||||
'active' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
|
||||
'show_title' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
|
||||
'ordering' => ['type' => self::TYPE_INT, 'validate' => 'isInt'],
|
||||
@@ -58,7 +66,7 @@ class CustomHtmlBlock extends ObjectModel
|
||||
}
|
||||
|
||||
$result = Db::getInstance()->executeS(
|
||||
'SELECT `id_ws_customhtml`, `id_ws_customhtml_group`, `title`, `content`, `active`, `show_title`, `ordering`
|
||||
'SELECT `id_ws_customhtml`, `id_ws_customhtml_group`, `title`, `type`, `content`, `custom_class`, `active`, `show_title`, `ordering`
|
||||
FROM `' . _DB_PREFIX_ . 'ws_customhtml`
|
||||
WHERE `id_ws_customhtml_group` IN (' . implode(',', $groupIds) . ')
|
||||
AND `active` = 1
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<?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',
|
||||
'fields' => [
|
||||
'id_ws_customhtml' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'question' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true, 'size' => 512],
|
||||
'answer' => ['type' => self::TYPE_HTML, 'validate' => 'isCleanHtml'],
|
||||
'ordering' => ['type' => self::TYPE_INT, 'validate' => 'isInt'],
|
||||
'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
|
||||
'date_upd' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
|
||||
],
|
||||
];
|
||||
|
||||
public static function getByBlockId(int $idBlock): array
|
||||
{
|
||||
$result = Db::getInstance()->executeS(
|
||||
'SELECT `id_ws_customhtml_faq`, `question`, `answer`, `ordering`
|
||||
FROM `' . _DB_PREFIX_ . 'ws_customhtml_faq`
|
||||
WHERE `id_ws_customhtml` = ' . (int) $idBlock . '
|
||||
ORDER BY `ordering` ASC'
|
||||
);
|
||||
|
||||
return $result ?: [];
|
||||
}
|
||||
|
||||
public static function deleteByBlockId(int $idBlock): void
|
||||
{
|
||||
Db::getInstance()->delete('ws_customhtml_faq', '`id_ws_customhtml` = ' . (int) $idBlock);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace Websmid\CustomHtml\Module;
|
||||
|
||||
use Websmid\CustomHtml\Config\Config;
|
||||
use PrestaShop\PrestaShop\Core\Grid\Data\GridData;
|
||||
use PrestaShop\PrestaShop\Core\Grid\Record\RecordCollection;
|
||||
use Tools;
|
||||
|
||||
trait AdminHooks
|
||||
@@ -27,4 +29,17 @@ trait AdminHooks
|
||||
|
||||
$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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,20 @@
|
||||
namespace Websmid\CustomHtml\Module;
|
||||
|
||||
use Websmid\CustomHtml\Model\CustomHtmlBlock;
|
||||
use Websmid\CustomHtml\Model\CustomHtmlFaq;
|
||||
use Websmid\CustomHtml\Model\CustomHtmlGroup;
|
||||
use Context;
|
||||
|
||||
trait DisplayHooks
|
||||
{
|
||||
public function hookActionFrontControllerSetMedia(): void
|
||||
{
|
||||
$this->context->controller->registerStylesheet(
|
||||
'ws_customhtml_front',
|
||||
'/modules/' . $this->name . '/views/css/front/customhtml.css'
|
||||
);
|
||||
}
|
||||
|
||||
public function renderWidget($hookName = null, array $configuration = [])
|
||||
{
|
||||
if (empty($hookName)) {
|
||||
@@ -54,6 +63,7 @@ trait DisplayHooks
|
||||
}
|
||||
|
||||
$blocksByGroup = CustomHtmlBlock::getActiveByGroupIds(array_column($groups, 'id_ws_customhtml_group'));
|
||||
$blocksByGroup = $this->attachFaqsToBlocks($blocksByGroup);
|
||||
|
||||
$groups = array_map(static function (array $group) use ($blocksByGroup) {
|
||||
$group['blocks'] = $blocksByGroup[(int) $group['id_ws_customhtml_group']] ?? [];
|
||||
@@ -65,4 +75,17 @@ trait DisplayHooks
|
||||
return !empty($group['blocks']);
|
||||
}));
|
||||
}
|
||||
|
||||
private function attachFaqsToBlocks(array $blocksByGroup): array
|
||||
{
|
||||
return array_map(static function (array $blocks) {
|
||||
return array_map(static function (array $block) {
|
||||
if ($block['type'] === 'faq') {
|
||||
$block['faqs'] = CustomHtmlFaq::getByBlockId((int) $block['id_ws_customhtml']);
|
||||
}
|
||||
|
||||
return $block;
|
||||
}, $blocks);
|
||||
}, $blocksByGroup);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -55,7 +62,9 @@ trait Install
|
||||
`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 \'\',
|
||||
`type` VARCHAR(32) NOT NULL DEFAULT \'html\',
|
||||
`content` LONGTEXT,
|
||||
`custom_class` VARCHAR(128) NOT NULL DEFAULT \'\',
|
||||
`active` TINYINT(1) NOT NULL DEFAULT 1,
|
||||
`show_title` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`ordering` INT(11) NOT NULL DEFAULT 0,
|
||||
@@ -65,6 +74,19 @@ trait Install
|
||||
);
|
||||
';
|
||||
|
||||
$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,
|
||||
`question` VARCHAR(512) NOT NULL DEFAULT \'\',
|
||||
`answer` TEXT,
|
||||
`ordering` INT(11) NOT NULL DEFAULT 0,
|
||||
`date_add` DATETIME NOT NULL,
|
||||
`date_upd` DATETIME NOT NULL,
|
||||
KEY `id_ws_customhtml` (`id_ws_customhtml`)
|
||||
);
|
||||
';
|
||||
|
||||
foreach ($sqlToExec as $sqlBlock) {
|
||||
Db::getInstance()->execute(trim($sqlBlock));
|
||||
}
|
||||
@@ -83,6 +105,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);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
function upgrade_module_1_2_0($module)
|
||||
{
|
||||
$columns = Db::getInstance()->executeS(
|
||||
'SHOW COLUMNS FROM `' . _DB_PREFIX_ . 'ws_customhtml` LIKE \'type\''
|
||||
);
|
||||
|
||||
if (empty($columns)) {
|
||||
if (!Db::getInstance()->execute(
|
||||
'ALTER TABLE `' . _DB_PREFIX_ . 'ws_customhtml`
|
||||
ADD COLUMN `type` VARCHAR(32) NOT NULL DEFAULT \'html\' AFTER `title`'
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Db::getInstance()->execute(
|
||||
'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,
|
||||
`question` VARCHAR(512) NOT NULL DEFAULT \'\',
|
||||
`answer` TEXT,
|
||||
`ordering` INT(11) NOT NULL DEFAULT 0,
|
||||
`date_add` DATETIME NOT NULL,
|
||||
`date_upd` DATETIME NOT NULL,
|
||||
KEY `id_ws_customhtml` (`id_ws_customhtml`)
|
||||
)'
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $module->registerHook('actionCustomhtmlblockGridDataModifier');
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
function upgrade_module_1_3_0($module)
|
||||
{
|
||||
$columns = Db::getInstance()->executeS(
|
||||
'SHOW COLUMNS FROM `' . _DB_PREFIX_ . 'ws_customhtml` LIKE \'column_class\''
|
||||
);
|
||||
|
||||
if (empty($columns)) {
|
||||
if (!Db::getInstance()->execute(
|
||||
'ALTER TABLE `' . _DB_PREFIX_ . 'ws_customhtml`
|
||||
ADD COLUMN `column_class` VARCHAR(128) NOT NULL DEFAULT \'col-12\' AFTER `content`'
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
function upgrade_module_1_4_0($module)
|
||||
{
|
||||
if (!Hook::isModuleRegisteredOnHook($module, 'actionFrontControllerSetMedia', (int) Context::getContext()->shop->id)) {
|
||||
return $module->registerHook('actionFrontControllerSetMedia');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
function upgrade_module_1_5_0($module)
|
||||
{
|
||||
$newColumn = Db::getInstance()->executeS(
|
||||
'SHOW COLUMNS FROM `' . _DB_PREFIX_ . 'ws_customhtml` LIKE \'custom_class\''
|
||||
);
|
||||
|
||||
if (!empty($newColumn)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$oldColumn = Db::getInstance()->executeS(
|
||||
'SHOW COLUMNS FROM `' . _DB_PREFIX_ . 'ws_customhtml` LIKE \'column_class\''
|
||||
);
|
||||
|
||||
if (!empty($oldColumn)) {
|
||||
return Db::getInstance()->execute(
|
||||
'ALTER TABLE `' . _DB_PREFIX_ . 'ws_customhtml`
|
||||
CHANGE `column_class` `custom_class` VARCHAR(128) NOT NULL DEFAULT \'\''
|
||||
);
|
||||
}
|
||||
|
||||
return Db::getInstance()->execute(
|
||||
'ALTER TABLE `' . _DB_PREFIX_ . 'ws_customhtml`
|
||||
ADD COLUMN `custom_class` VARCHAR(128) NOT NULL DEFAULT \'\' AFTER `content`'
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
.customhtml-block img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
{% 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 %}
|
||||
|
||||
{% block customhtmlblock_form_block %}
|
||||
<div class="card">
|
||||
{{ form_start(customHtmlBlockForm) }}
|
||||
@@ -10,6 +16,39 @@
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{ form_row(customHtmlBlockForm.id_ws_customhtml_group) }}
|
||||
{{ form_row(customHtmlBlockForm.title) }}
|
||||
{{ form_row(customHtmlBlockForm.type) }}
|
||||
{{ form_row(customHtmlBlockForm.show_title) }}
|
||||
{{ form_row(customHtmlBlockForm.custom_class) }}
|
||||
{{ form_row(customHtmlBlockForm.content) }}
|
||||
|
||||
<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 }}"
|
||||
>
|
||||
{% 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>
|
||||
|
||||
{{ form_row(customHtmlBlockForm.active) }}
|
||||
{{ form_rest(customHtmlBlockForm) }}
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
|
||||
@@ -13,6 +13,44 @@
|
||||
window.prestashop.component.initComponents([
|
||||
'TinyMCEEditor',
|
||||
]);
|
||||
|
||||
function toggleBlockTypeFields() {
|
||||
var type = $('#custom_html_block_type').val();
|
||||
|
||||
if (type === 'html') {
|
||||
$('.customhtmlblock-type-html').show();
|
||||
$('.customhtmlblock-type-faq').hide();
|
||||
} else {
|
||||
$('.customhtmlblock-type-html').hide();
|
||||
$('.customhtmlblock-type-faq').show();
|
||||
}
|
||||
}
|
||||
|
||||
$('#custom_html_block_type').on('change', toggleBlockTypeFields);
|
||||
toggleBlockTypeFields();
|
||||
|
||||
var removeFaqLabel = '{{ 'Remove'|trans({}, 'Admin.Actions')|e('js') }}';
|
||||
var faqContainer = $('#customhtmlblock-faq-items');
|
||||
|
||||
$('#customhtmlblock-add-faq').on('click', function () {
|
||||
var index = faqContainer.data('index');
|
||||
var prototype = faqContainer.data('prototype').replace(/__name__/g, index);
|
||||
|
||||
var item = $('<div class="customhtmlblock-faq-item 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 customhtmlblock-remove-faq">' +
|
||||
'<i class="material-icons">delete</i> ' + removeFaqLabel + '</button>'
|
||||
);
|
||||
|
||||
faqContainer.append(item);
|
||||
faqContainer.data('index', index + 1);
|
||||
});
|
||||
|
||||
faqContainer.on('click', '.customhtmlblock-remove-faq', function () {
|
||||
$(this).closest('.customhtmlblock-faq-item').remove();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -13,6 +13,44 @@
|
||||
window.prestashop.component.initComponents([
|
||||
'TinyMCEEditor',
|
||||
]);
|
||||
|
||||
function toggleBlockTypeFields() {
|
||||
var type = $('#custom_html_block_type').val();
|
||||
|
||||
if (type === 'html') {
|
||||
$('.customhtmlblock-type-html').show();
|
||||
$('.customhtmlblock-type-faq').hide();
|
||||
} else {
|
||||
$('.customhtmlblock-type-html').hide();
|
||||
$('.customhtmlblock-type-faq').show();
|
||||
}
|
||||
}
|
||||
|
||||
$('#custom_html_block_type').on('change', toggleBlockTypeFields);
|
||||
toggleBlockTypeFields();
|
||||
|
||||
var removeFaqLabel = '{{ 'Remove'|trans({}, 'Admin.Actions')|e('js') }}';
|
||||
var faqContainer = $('#customhtmlblock-faq-items');
|
||||
|
||||
$('#customhtmlblock-add-faq').on('click', function () {
|
||||
var index = faqContainer.data('index');
|
||||
var prototype = faqContainer.data('prototype').replace(/__name__/g, index);
|
||||
|
||||
var item = $('<div class="customhtmlblock-faq-item 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 customhtmlblock-remove-faq">' +
|
||||
'<i class="material-icons">delete</i> ' + removeFaqLabel + '</button>'
|
||||
);
|
||||
|
||||
faqContainer.append(item);
|
||||
faqContainer.data('index', index + 1);
|
||||
});
|
||||
|
||||
faqContainer.on('click', '.customhtmlblock-remove-faq', function () {
|
||||
$(this).closest('.customhtmlblock-faq-item').remove();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
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());
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
{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">
|
||||
<div class="card-header" id="customhtml-faq-heading-{$block.id_ws_customhtml|intval}-{$faq.id_ws_customhtml_faq|intval}">
|
||||
<button
|
||||
class="btn btn-link{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}"
|
||||
>
|
||||
{$faq.question|escape:'html':'UTF-8'}
|
||||
</button>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
{/foreach}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -1,13 +1,17 @@
|
||||
{if !empty($groupList)}
|
||||
<div class="customhtml-container {$hookName|escape:'html':'UTF-8'}--container">
|
||||
{foreach from=$groupList item="group"}
|
||||
<div class="customhtml-group">
|
||||
<div class="customhtml-group customhtml-group-{$group.id_ws_customhtml_group|intval} row">
|
||||
{foreach from=$group.blocks item="block"}
|
||||
<div class="customhtml-block">
|
||||
<div class="customhtml-block col{if !empty($block.custom_class)} {$block.custom_class|escape:'html':'UTF-8'}{/if}">
|
||||
{if $block.show_title && !empty($block.title)}
|
||||
<div class="customhtml-title">{$block.title|escape:'html':'UTF-8'}</div>
|
||||
{/if}
|
||||
{if $block.type == 'faq'}
|
||||
{include file="module:ws_customhtml/views/templates/hook/_faq_accordion.tpl" block=$block}
|
||||
{else}
|
||||
<div class="customhtml-content">{$block.content nofilter}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/foreach}
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -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.5.0';
|
||||
$this->author = 'Isabelle Oving-Anno | De Websmid BV';
|
||||
$this->need_instance = 0;
|
||||
$this->bootstrap = true;
|
||||
|
||||
Reference in New Issue
Block a user