feat(main): Add FAQ accordion
This commit is contained in:
@@ -6,6 +6,12 @@ class Config
|
||||
{
|
||||
const HOOKS = [
|
||||
'actionAdminControllerSetMedia',
|
||||
'actionCustomhtmlblockGridDataModifier',
|
||||
];
|
||||
|
||||
const BLOCK_TYPES = [
|
||||
'html' => 'HTML',
|
||||
'faq' => 'FAQ Accordion',
|
||||
];
|
||||
|
||||
const ADMIN_CONTROLLERS = [
|
||||
|
||||
@@ -11,6 +11,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,6 +108,7 @@ class CustomHtmlBlockController extends FrameworkBundleAdminController
|
||||
return $this->redirectToRoute('customhtmlblock_index');
|
||||
}
|
||||
|
||||
CustomHtmlFaq::deleteByBlockId($idCustomHtmlBlock);
|
||||
$block->delete();
|
||||
$this->addFlash('success', $this->trans('Successful deletion.', 'Admin.Notifications.Success'));
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Websmid\CustomHtml\Form\DataHandler;
|
||||
|
||||
use PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataHandler\FormDataHandlerInterface;
|
||||
use Websmid\CustomHtml\Model\CustomHtmlBlock;
|
||||
use Websmid\CustomHtml\Model\CustomHtmlFaq;
|
||||
use Validate;
|
||||
|
||||
final class CustomHtmlBlockDataHandler implements FormDataHandlerInterface
|
||||
@@ -13,6 +14,7 @@ final class CustomHtmlBlockDataHandler implements FormDataHandlerInterface
|
||||
$block = new CustomHtmlBlock();
|
||||
$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->content = (string) $formData['content'];
|
||||
$block->active = (int) $formData['active'];
|
||||
@@ -22,6 +24,8 @@ final class CustomHtmlBlockDataHandler implements FormDataHandlerInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->saveFaqs((int) $block->id, $formData['faqs'] ?? []);
|
||||
|
||||
return $block->id;
|
||||
}
|
||||
|
||||
@@ -35,10 +39,35 @@ 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->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 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,10 @@ 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,
|
||||
'content' => $block->content,
|
||||
'faqs' => $this->loadFaqs((int) $id),
|
||||
'active' => (bool) $block->active,
|
||||
];
|
||||
}
|
||||
@@ -30,9 +33,21 @@ final class CustomHtmlBlockDataProvider implements FormDataProviderInterface
|
||||
return [
|
||||
'id_ws_customhtml_group' => 0,
|
||||
'title' => '',
|
||||
'type' => 'html',
|
||||
'show_title' => false,
|
||||
'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,6 +39,11 @@ 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,
|
||||
@@ -45,6 +52,15 @@ class CustomHtmlBlockType extends AbstractType
|
||||
'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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,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,6 +73,11 @@ 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'))
|
||||
@@ -120,6 +127,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')
|
||||
|
||||
@@ -45,6 +45,7 @@ final class CustomHtmlBlockQueryBuilder extends AbstractDoctrineQueryBuilder
|
||||
'wchb.`id_ws_customhtml`',
|
||||
'wchb.`title`',
|
||||
'wchg.`title` AS group_title',
|
||||
'wchb.`type`',
|
||||
'wchb.`active`',
|
||||
'wchb.`ordering`',
|
||||
'wchb.`date_add`'
|
||||
@@ -108,6 +109,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,6 +16,9 @@ class CustomHtmlBlock extends ObjectModel
|
||||
/** @var string */
|
||||
public $title = '';
|
||||
|
||||
/** @var string */
|
||||
public $type = 'html';
|
||||
|
||||
/** @var string */
|
||||
public $content = '';
|
||||
|
||||
@@ -40,6 +43,7 @@ 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'],
|
||||
'active' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
|
||||
'show_title' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
|
||||
@@ -58,7 +62,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`, `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,6 +3,7 @@
|
||||
namespace Websmid\CustomHtml\Module;
|
||||
|
||||
use Websmid\CustomHtml\Model\CustomHtmlBlock;
|
||||
use Websmid\CustomHtml\Model\CustomHtmlFaq;
|
||||
use Websmid\CustomHtml\Model\CustomHtmlGroup;
|
||||
use Context;
|
||||
|
||||
@@ -54,6 +55,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 +67,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,6 +62,7 @@ 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,
|
||||
`active` TINYINT(1) NOT NULL DEFAULT 1,
|
||||
`show_title` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
@@ -65,6 +73,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 +104,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);
|
||||
|
||||
Reference in New Issue
Block a user