diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57872d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..21f25fc --- /dev/null +++ b/composer.json @@ -0,0 +1,25 @@ +{ + "name": "websmid/ws_customhtml", + "description": "Create and manage custom HTML blocks for PrestaShop frontend hooks", + "authors": [ + { + "name": "Isabelle Oving-Anno - De Websmid BV", + "email": "isabelle@dewebsmid.nl" + } + ], + "require": {}, + "autoload": { + "psr-4": { + "Websmid\\CustomHtml\\": "src/" + }, + "classmap": ["ws_customhtml.php"], + "exclude-from-classmap": [] + }, + "config": { + "preferred-install": "dist", + "prepend-autoloader": false + }, + "type": "prestashop-module", + "author": "Isabelle Oving-Anno - De Websmid BV", + "license": "MIT" +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..2bcb9c7 --- /dev/null +++ b/composer.lock @@ -0,0 +1,18 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "477c629bc0a02ebb297b14e975a6d2e5", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/config/form.yml b/config/form.yml new file mode 100644 index 0000000..e9e8dfa --- /dev/null +++ b/config/form.yml @@ -0,0 +1,34 @@ +services: + websmid.customhtml.form.type.customhtml: + public: true + class: 'Websmid\CustomHtml\Form\Type\CustomHtmlType' + parent: 'form.type.translatable.aware' + tags: + - { name: form.type } + + websmid.customhtml.form.customhtml_data_provider: + public: true + class: 'Websmid\CustomHtml\Form\DataProvider\CustomHtmlDataProvider' + + websmid.customhtml.form.customhtml_data_form_builder: + public: true + class: 'PrestaShop\PrestaShop\Core\Form\IdentifiableObject\Builder\FormBuilder' + factory: + - '@prestashop.core.form.builder.form_builder_factory' + - 'create' + arguments: + - 'Websmid\CustomHtml\Form\Type\CustomHtmlType' + - '@websmid.customhtml.form.customhtml_data_provider' + + websmid.customhtml.form.customhtml_data_handler: + public: true + class: 'Websmid\CustomHtml\Form\DataHandler\CustomHtmlDataHandler' + + websmid.customhtml.form.customhtml_form_handler: + public: true + class: 'PrestaShop\PrestaShop\Core\Form\IdentifiableObject\Handler\FormHandler' + factory: + - '@prestashop.core.form.identifiable_object.handler.form_handler_factory' + - 'create' + arguments: + - '@websmid.customhtml.form.customhtml_data_handler' diff --git a/config/grid.yml b/config/grid.yml new file mode 100644 index 0000000..573817d --- /dev/null +++ b/config/grid.yml @@ -0,0 +1,35 @@ +services: + websmid.customhtml.grid.definition.factory.customhtml_definition_factory: + public: true + class: 'Websmid\CustomHtml\Grid\Definition\Factory\CustomHtmlGridDefinitionFactory' + parent: 'prestashop.core.grid.definition.factory.abstract_grid_definition' + + websmid.customhtml.grid.query.customhtml_query_builder: + public: true + class: 'Websmid\CustomHtml\Grid\Query\CustomHtmlQueryBuilder' + parent: 'prestashop.core.grid.abstract_query_builder' + arguments: + - '@prestashop.core.query.doctrine_search_criteria_applicator' + - "@=service('prestashop.adapter.legacy.context').getContext().language.id" + - "@=service('prestashop.adapter.legacy.context').getContext().shop.id" + - "@=service('prestashop.adapter.legacy.context').getContext().shop.id_shop_group" + - '@prestashop.core.grid.query.filter.doctrine_filter_applicator' + - '@prestashop.adapter.legacy.configuration' + + websmid.customhtml.grid.data.factory.customhtml_data_factory: + public: true + class: '%prestashop.core.grid.data.factory.doctrine_grid_data_factory%' + arguments: + - '@websmid.customhtml.grid.query.customhtml_query_builder' + - '@prestashop.core.hook.dispatcher' + - '@prestashop.core.grid.query.doctrine_query_parser' + - 'customhtml' + + websmid.customhtml.grid.customhtml_grid_factory: + public: true + class: 'PrestaShop\PrestaShop\Core\Grid\GridFactory' + arguments: + - '@websmid.customhtml.grid.definition.factory.customhtml_definition_factory' + - '@websmid.customhtml.grid.data.factory.customhtml_data_factory' + - '@prestashop.core.grid.filter.form_factory' + - '@prestashop.core.hook.dispatcher' diff --git a/config/routes.yml b/config/routes.yml new file mode 100644 index 0000000..6f5af42 --- /dev/null +++ b/config/routes.yml @@ -0,0 +1,32 @@ +customhtml_index: + path: ws_customhtml + methods: [GET] + defaults: + _controller: 'Websmid\CustomHtml\Controller\CustomHtmlController::indexAction' + _legacy_controller: AdminCustomHtmlOverview + _legacy_link: AdminCustomHtmlOverview:index + +customhtml_search: + path: ws_customhtml + methods: [POST] + defaults: + _controller: 'Websmid\CustomHtml\Controller\CustomHtmlController::searchAction' + _legacy_controller: AdminCustomHtmlOverview + +customhtml_create: + path: ws_customhtml/create + methods: [POST, GET] + defaults: + _controller: 'Websmid\CustomHtml\Controller\CustomHtmlController::createAction' + +customhtml_edit: + path: ws_customhtml/edit/{idCustomHtml} + methods: [POST, GET] + defaults: + _controller: 'Websmid\CustomHtml\Controller\CustomHtmlController::editAction' + +customhtml_delete: + path: ws_customhtml/delete/{idCustomHtml} + methods: [DELETE, GET, POST] + defaults: + _controller: 'Websmid\CustomHtml\Controller\CustomHtmlController::deleteAction' diff --git a/config/services.yml b/config/services.yml new file mode 100644 index 0000000..7aec7d4 --- /dev/null +++ b/config/services.yml @@ -0,0 +1,3 @@ +imports: + - { resource: form.yml } + - { resource: grid.yml } diff --git a/index.php b/index.php new file mode 100644 index 0000000..30c3516 --- /dev/null +++ b/index.php @@ -0,0 +1,8 @@ + 'AdminCustomHtmlOverview', + 'parent_class_name' => 'AdminParentThemes', + 'name' => [ + 'en' => 'Custom HTML', + 'nl' => 'Custom HTML', + ], + 'module' => 'ws_customhtml', + 'icon' => 'code', + 'route_name' => 'customhtml_index', + 'active' => 1, + ], + ]; +} diff --git a/src/Controller/CustomHtmlController.php b/src/Controller/CustomHtmlController.php new file mode 100644 index 0000000..a482c5e --- /dev/null +++ b/src/Controller/CustomHtmlController.php @@ -0,0 +1,124 @@ +get('websmid.customhtml.grid.customhtml_grid_factory'); + $grid = $gridFactory->getGrid($filters); + + return $this->render('@Modules/ws_customhtml/views/templates/admin/grid/customhtml.html.twig', [ + 'customHtmlGrid' => $this->presentGrid($grid), + 'layoutHeaderToolbarBtn' => $this->getToolbarButtons($request->get('_legacy_controller')), + ]); + } + + public function searchAction(Request $request): RedirectResponse + { + return $this->get('prestashop.bundle.grid.response_builder')->buildSearchResponse( + $this->get('websmid.customhtml.grid.definition.factory.customhtml_definition_factory'), + $request, + CustomHtmlGridDefinitionFactory::GRID_ID, + 'customhtml_index' + ); + } + + public function createAction(Request $request): Response + { + $formBuilder = $this->get('websmid.customhtml.form.customhtml_data_form_builder'); + $formHandler = $this->get('websmid.customhtml.form.customhtml_form_handler'); + + $form = $formBuilder->getForm(); + $form->handleRequest($request); + + try { + $handlerResult = $formHandler->handle($form); + + if (null !== $handlerResult->getIdentifiableObjectId()) { + $this->addFlash('success', $this->trans('Successful creation.', 'Admin.Notifications.Success')); + + return $this->redirectToRoute('customhtml_index'); + } + } catch (Exception $e) { + $this->addFlash('error', $this->getErrorMessageForException($e, [])); + } + + return $this->render('@Modules/ws_customhtml/views/templates/admin/form/customhtml_create.html.twig', [ + 'customHtmlForm' => $form->createView(), + ]); + } + + public function editAction(Request $request, int $idCustomHtml): Response + { + $formBuilder = $this->get('websmid.customhtml.form.customhtml_data_form_builder'); + $formHandler = $this->get('websmid.customhtml.form.customhtml_form_handler'); + + $form = $formBuilder->getFormFor($idCustomHtml); + + try { + $form->handleRequest($request); + $handlerResult = $formHandler->handleFor($idCustomHtml, $form); + + if ($handlerResult->isSubmitted() && $handlerResult->isValid()) { + $this->addFlash('success', $this->trans('Successful update.', 'Admin.Notifications.Success')); + + return $this->redirectToRoute('customhtml_index'); + } + } catch (Exception $e) { + $this->addFlash('error', $this->getErrorMessageForException($e, [])); + } + + return $this->render('@Modules/ws_customhtml/views/templates/admin/form/customhtml_edit.html.twig', [ + 'customHtmlForm' => $form->createView(), + 'idCustomHtml' => $idCustomHtml, + ]); + } + + public function deleteAction(Request $request, int $idCustomHtml): RedirectResponse + { + $block = new CustomHtml($idCustomHtml); + + if (!Validate::isLoadedObject($block)) { + $this->addFlash('error', $this->trans('Unable to find the block.', 'Modules.Wscustomhtml.Admin')); + + return $this->redirectToRoute('customhtml_index'); + } + + $block->delete(); + $this->addFlash('success', $this->trans('Successful deletion.', 'Admin.Notifications.Success')); + + return $this->redirectToRoute('customhtml_index'); + } + + private function getToolbarButtons(?string $securitySubject): array + { + $toolbarButtons = []; + + if (!$securitySubject || !$this->isGranted(PageVoter::CREATE, $securitySubject)) { + return $toolbarButtons; + } + + $toolbarButtons['customhtml_create'] = [ + 'href' => $this->generateUrl('customhtml_create'), + 'desc' => $this->trans('New block', 'Admin.Actions'), + 'icon' => 'add_circle_outline', + 'class' => 'btn-primary', + 'floating_class' => '', + ]; + + return $toolbarButtons; + } +} diff --git a/src/Form/DataHandler/CustomHtmlDataHandler.php b/src/Form/DataHandler/CustomHtmlDataHandler.php new file mode 100644 index 0000000..03f4bbf --- /dev/null +++ b/src/Form/DataHandler/CustomHtmlDataHandler.php @@ -0,0 +1,65 @@ +hook_name = (string) $formData['hook_name']; + $block->title = (string) $formData['title']; + $block->show_title = (int) $formData['show_title']; + $block->content = (string) $formData['content']; + $block->active = (int) $formData['active']; + $block->ordering = 0; + + if (!$block->add()) { + return false; + } + + $this->checkAndRegisterHookForModule((string) $formData['hook_name']); + + return $block->id; + } + + public function update($id, array $formData) + { + $block = new CustomHtml((int) $id); + + if (!Validate::isLoadedObject($block)) { + return false; + } + + $block->hook_name = (string) $formData['hook_name']; + $block->title = (string) $formData['title']; + $block->show_title = (int) $formData['show_title']; + $block->content = (string) $formData['content']; + $block->active = (int) $formData['active']; + + if (!$block->update()) { + return false; + } + + $this->checkAndRegisterHookForModule((string) $formData['hook_name']); + + return true; + } + + private function checkAndRegisterHookForModule(string $hookName): void + { + $module = Module::getInstanceByName('ws_customhtml'); + $idShop = (int) Context::getContext()->shop->id; + + if (!Hook::isModuleRegisteredOnHook($module, $hookName, $idShop)) { + Hook::registerHook($module, $hookName); + } + } +} diff --git a/src/Form/DataProvider/CustomHtmlDataProvider.php b/src/Form/DataProvider/CustomHtmlDataProvider.php new file mode 100644 index 0000000..d448b2f --- /dev/null +++ b/src/Form/DataProvider/CustomHtmlDataProvider.php @@ -0,0 +1,38 @@ +getDefaultData(); + } + + return [ + 'hook_name' => $block->hook_name, + 'title' => $block->title, + 'show_title' => (bool) $block->show_title, + 'content' => $block->content, + 'active' => (bool) $block->active, + ]; + } + + public function getDefaultData() + { + return [ + 'hook_name' => '', + 'title' => '', + 'show_title' => false, + 'content' => '', + 'active' => true, + ]; + } +} diff --git a/src/Form/Type/CustomHtmlType.php b/src/Form/Type/CustomHtmlType.php new file mode 100644 index 0000000..1bde9da --- /dev/null +++ b/src/Form/Type/CustomHtmlType.php @@ -0,0 +1,46 @@ +translator = $translator; + } + + public function buildForm(FormBuilderInterface $formBuilder, array $options): void + { + $formBuilder + ->add('hook_name', TextType::class, [ + 'label' => $this->translator->trans('Hook name', [], 'Modules.Wscustomhtml.Admin'), + 'required' => true, + ]) + ->add('title', TextType::class, [ + 'label' => $this->translator->trans('Title', [], 'Modules.Wscustomhtml.Admin'), + 'required' => true, + ]) + ->add('show_title', SwitchType::class, [ + 'label' => $this->translator->trans('Show title', [], 'Modules.Wscustomhtml.Admin'), + 'required' => false, + ]) + ->add('content', TextareaType::class, [ + 'label' => $this->translator->trans('Content', [], 'Modules.Wscustomhtml.Admin'), + 'required' => false, + 'attr' => ['class' => 'autoload_rte'], + ]) + ->add('active', SwitchType::class, [ + 'label' => $this->translator->trans('Active', [], 'Modules.Wscustomhtml.Admin'), + 'required' => false, + ]); + } +} diff --git a/src/Grid/Definition/Factory/CustomHtmlGridDefinitionFactory.php b/src/Grid/Definition/Factory/CustomHtmlGridDefinitionFactory.php new file mode 100644 index 0000000..8d132ff --- /dev/null +++ b/src/Grid/Definition/Factory/CustomHtmlGridDefinitionFactory.php @@ -0,0 +1,153 @@ +trans('Custom HTML Blocks', [], 'Modules.Wscustomhtml.Admin'); + } + + protected function getGridActions() + { + return (new GridActionCollection()) + ->add( + (new SimpleGridAction('common_refresh_list')) + ->setName($this->trans('Refresh list', [], 'Admin.Catalog.Feature')) + ->setIcon('refresh') + ) + ->add( + (new SimpleGridAction('common_show_query')) + ->setName($this->trans('Show SQL query', [], 'Admin.Catalog.Feature')) + ->setIcon('code') + ); + } + + protected function getColumns() + { + return (new ColumnCollection()) + ->add( + (new DataColumn('id_ws_customhtml')) + ->setName($this->trans('ID', [], 'Admin.Global')) + ->setOptions(['field' => 'id_ws_customhtml']) + ) + ->add( + (new DataColumn('title')) + ->setName($this->trans('Title', [], 'Modules.Wscustomhtml.Admin')) + ->setOptions(['field' => 'title']) + ) + ->add( + (new DataColumn('hook_name')) + ->setName($this->trans('Hook', [], 'Modules.Wscustomhtml.Admin')) + ->setOptions(['field' => 'hook_name']) + ) + ->add( + (new DataColumn('active')) + ->setName($this->trans('Active', [], 'Admin.Global')) + ->setOptions(['field' => 'active']) + ) + ->add( + (new DataColumn('date_add')) + ->setName($this->trans('Date added', [], 'Admin.Global')) + ->setOptions(['field' => 'date_add']) + ) + ->add( + (new ActionColumn('actions')) + ->setName($this->trans('Actions', [], 'Admin.Global')) + ->setOptions(['actions' => $this->buildRowActions()]) + ); + } + + protected function getFilters() + { + return (new FilterCollection()) + ->add( + (new Filter('id_ws_customhtml', TextType::class)) + ->setAssociatedColumn('id_ws_customhtml') + ->setTypeOptions([ + 'required' => false, + 'attr' => ['placeholder' => $this->trans('ID', [], 'Admin.Global')], + ]) + ) + ->add( + (new Filter('title', TextType::class)) + ->setAssociatedColumn('title') + ->setTypeOptions([ + 'required' => false, + 'attr' => ['placeholder' => $this->trans('Title', [], 'Modules.Wscustomhtml.Admin')], + ]) + ) + ->add( + (new Filter('hook_name', TextType::class)) + ->setAssociatedColumn('hook_name') + ->setTypeOptions([ + 'required' => false, + 'attr' => ['placeholder' => $this->trans('Hook', [], 'Modules.Wscustomhtml.Admin')], + ]) + ) + ->add( + (new Filter('actions', SearchAndResetType::class)) + ->setAssociatedColumn('actions') + ->setTypeOptions([ + 'reset_route' => 'admin_common_reset_search_by_filter_id', + 'reset_route_params' => ['filterId' => self::GRID_ID], + 'redirect_route' => 'customhtml_index', + ]) + ); + } + + private function buildRowActions(): RowActionCollection + { + return (new RowActionCollection()) + ->add( + (new LinkRowAction('edit')) + ->setName($this->trans('Edit', [], 'Admin.Actions')) + ->setIcon('edit') + ->setOptions([ + 'route' => 'customhtml_edit', + 'route_param_name' => 'idCustomHtml', + 'route_param_field' => 'id_ws_customhtml', + 'use_inline_display' => true, + 'clickable_row' => true, + ]) + ) + ->add( + (new LinkRowAction('delete')) + ->setName($this->trans('Delete', [], 'Admin.Actions')) + ->setIcon('delete') + ->setOptions([ + 'route' => 'customhtml_delete', + 'route_param_name' => 'idCustomHtml', + 'route_param_field' => 'id_ws_customhtml', + 'use_inline_display' => true, + ]) + ); + } +} diff --git a/src/Grid/Filters/CustomHtmlFilters.php b/src/Grid/Filters/CustomHtmlFilters.php new file mode 100644 index 0000000..10a6f1d --- /dev/null +++ b/src/Grid/Filters/CustomHtmlFilters.php @@ -0,0 +1,22 @@ + 10, + 'offset' => 0, + 'orderBy' => 'wch.id_ws_customhtml', + 'sortOrder' => 'asc', + 'filters' => [], + ]; + } +} diff --git a/src/Grid/Query/CustomHtmlQueryBuilder.php b/src/Grid/Query/CustomHtmlQueryBuilder.php new file mode 100644 index 0000000..22ddd7f --- /dev/null +++ b/src/Grid/Query/CustomHtmlQueryBuilder.php @@ -0,0 +1,96 @@ +searchCriteriaApplicator = $searchCriteriaApplicator; + $this->contextLanguageId = $contextLanguageId; + $this->contextShopId = $contextShopId; + $this->contextShopGroupId = $contextShopGroupId; + $this->filterApplicator = $filterApplicator; + $this->configuration = $configuration; + } + + public function getSearchQueryBuilder(SearchCriteriaInterface $searchCriteria) + { + $qb = $this->getBaseQuery(); + + $qb->select( + 'wch.`id_ws_customhtml`', + 'wch.`title`', + 'wch.`hook_name`', + 'wch.`active`', + 'wch.`date_add`' + ); + + $this->applyFilters($qb, $searchCriteria); + + $qb + ->orderBy($searchCriteria->getOrderBy(), $searchCriteria->getOrderWay()) + ->setFirstResult($searchCriteria->getOffset() ?? 0) + ->setMaxResults($searchCriteria->getLimit()); + + return $qb; + } + + public function getCountQueryBuilder(SearchCriteriaInterface $searchCriteria) + { + $qb = $this->getBaseQuery(); + $qb->select('COUNT(wch.`id_ws_customhtml`)'); + + $this->applyFilters($qb, $searchCriteria); + + return $qb; + } + + private function getBaseQuery() + { + return $this->connection + ->createQueryBuilder() + ->from($this->dbPrefix . 'ws_customhtml', 'wch'); + } + + private function applyFilters($qb, SearchCriteriaInterface $searchCriteria): void + { + foreach ($searchCriteria->getFilters() as $filterName => $filterValue) { + if ($filterName === 'actions') { + continue; + } + + if ($filterName === 'id_ws_customhtml') { + $qb->andWhere('wch.`id_ws_customhtml` = :id_ws_customhtml') + ->setParameter('id_ws_customhtml', (int) $filterValue); + continue; + } + + $qb->andWhere('wch.`' . $filterName . '` LIKE :' . $filterName) + ->setParameter($filterName, '%' . pSQL($filterValue) . '%'); + } + } +} diff --git a/src/Model/CustomHtml.php b/src/Model/CustomHtml.php new file mode 100644 index 0000000..edc0137 --- /dev/null +++ b/src/Model/CustomHtml.php @@ -0,0 +1,64 @@ + 'ws_customhtml', + 'primary' => 'id_ws_customhtml', + 'fields' => [ + 'hook_name' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true, 'size' => 64], + 'title' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true, 'size' => 256], + 'content' => ['type' => self::TYPE_HTML, 'validate' => 'isCleanHtml'], + 'active' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], + 'show_title' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], + '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 getActiveByHookName(string $hookName): array + { + $result = Db::getInstance()->executeS( + 'SELECT `id_ws_customhtml`, `title`, `content`, `active`, `show_title`, `ordering` + FROM `' . _DB_PREFIX_ . 'ws_customhtml` + WHERE `hook_name` = \'' . pSQL($hookName) . '\' + AND `active` = 1 + ORDER BY `ordering` ASC' + ); + + return $result ?: []; + } +} diff --git a/src/Module/AdminHooks.php b/src/Module/AdminHooks.php new file mode 100644 index 0000000..e005555 --- /dev/null +++ b/src/Module/AdminHooks.php @@ -0,0 +1,11 @@ +language->id; + $templateFile = 'module:ws_customhtml/views/templates/hook/customhtml_block.tpl'; + $cacheId = $this->getCacheId('ws_customhtml_' . $idLang . '_' . $hookName); + + if (!$this->isCached($templateFile, $cacheId)) { + $variables = $this->getWidgetVariables($hookName, $configuration); + + if (empty($variables)) { + return false; + } + + $this->smarty->assign($variables); + } + + return $this->fetch($templateFile, $cacheId); + } + + public function getWidgetVariables($hookName = null, array $configuration = []) + { + $blockList = CustomHtml::getActiveByHookName((string) $hookName); + + if (empty($blockList)) { + return []; + } + + return [ + 'hookName' => $hookName, + 'blockList' => $blockList, + ]; + } +} diff --git a/src/Module/Install.php b/src/Module/Install.php new file mode 100644 index 0000000..58fb8dc --- /dev/null +++ b/src/Module/Install.php @@ -0,0 +1,118 @@ +createTables()) { + error_log('[ws_customhtml] createTables() failed — DB error: ' . Db::getInstance()->getMsgError()); + return false; + } + + if (!$this->registerTabs(Config::TABS)) { + error_log('[ws_customhtml] registerTabs() failed'); + return false; + } + + if (!$this->registerHooks()) { + error_log('[ws_customhtml] registerHooks() failed'); + return false; + } + + return true; + } + + private function createTables(): bool + { + $sqlToExec = []; + + $sqlToExec[] = ' + CREATE TABLE IF NOT EXISTS ' . _DB_PREFIX_ . 'ws_customhtml ( + `id_ws_customhtml` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, + `hook_name` VARCHAR(64) NOT NULL, + `title` VARCHAR(256) NOT NULL DEFAULT \'\', + `content` LONGTEXT, + `active` TINYINT(1) NOT NULL DEFAULT 1, + `show_title` TINYINT(1) NOT NULL DEFAULT 0, + `ordering` INT(11) NOT NULL DEFAULT 0, + `date_add` DATETIME NOT NULL, + `date_upd` DATETIME NOT NULL + ); + '; + + foreach ($sqlToExec as $sqlBlock) { + Db::getInstance()->execute(trim($sqlBlock)); + } + + return true; + } + + private function registerHooks(): bool + { + foreach (Config::HOOKS as $hook) { + if (!$this->registerHook($hook)) { + return false; + } + } + + return true; + } + + private function registerTabs(array $moduleTabs): bool + { + $languages = Language::getLanguages(false); + + foreach ($moduleTabs as $moduleTab) { + if (Tab::getIdFromClassName($moduleTab['class_name'])) { + continue; + } + + $tab = new Tab(); + $tab->class_name = $moduleTab['class_name']; + $tab->module = $moduleTab['module']; + $tab->active = 1; + $tab->route_name = $moduleTab['route_name']; + + if (isset($moduleTab['icon'])) { + $tab->icon = $moduleTab['icon']; + } + + foreach ($languages as $language) { + if (is_array($moduleTab['name'])) { + $tab->name[$language['id_lang']] = $moduleTab['name'][$language['iso_code']] + ?? $moduleTab['name']['en']; + } else { + $tab->name[$language['id_lang']] = $moduleTab['name']; + } + } + + if (isset($moduleTab['parent_class_name'])) { + $tab->id_parent = (int) Tab::getIdFromClassName($moduleTab['parent_class_name']); + } elseif (isset($moduleTab['id_parent'])) { + $tab->id_parent = (int) $moduleTab['id_parent']; + } else { + $tab->id_parent = -1; + } + + $tab->add(); + + if (!$tab->id) { + return false; + } + } + + return true; + } +} diff --git a/views/templates/admin/form/blocks/form.html.twig b/views/templates/admin/form/blocks/form.html.twig new file mode 100644 index 0000000..a563c4f --- /dev/null +++ b/views/templates/admin/form/blocks/form.html.twig @@ -0,0 +1,25 @@ +{% import '@PrestaShop/Admin/macros.html.twig' as ps %} +{% form_theme customHtmlForm '@PrestaShop/Admin/TwigTemplateForm/prestashop_ui_kit.html.twig' %} + +{% block customhtml_form_block %} +
+ {{ form_start(customHtmlForm) }} +
+

+ {{ 'Custom HTML Block'|trans({}, 'Modules.Wscustomhtml.Admin') }} +

+
+
+ {{ form_rest(customHtmlForm) }} +
+ + {{ form_end(customHtmlForm) }} +
+{% endblock %} diff --git a/views/templates/admin/form/customhtml_create.html.twig b/views/templates/admin/form/customhtml_create.html.twig new file mode 100644 index 0000000..24fab21 --- /dev/null +++ b/views/templates/admin/form/customhtml_create.html.twig @@ -0,0 +1,27 @@ +{% extends '@PrestaShop/Admin/layout.html.twig' %} + +{% block content %} + {% block customhtml_create_form %} + {% include '@Modules/ws_customhtml/views/templates/admin/form/blocks/form.html.twig' %} + {% endblock %} +{% endblock %} + +{% block javascripts %} + {{ parent() }} + +{% endblock %} diff --git a/views/templates/admin/form/customhtml_edit.html.twig b/views/templates/admin/form/customhtml_edit.html.twig new file mode 100644 index 0000000..6b56de6 --- /dev/null +++ b/views/templates/admin/form/customhtml_edit.html.twig @@ -0,0 +1,27 @@ +{% extends '@PrestaShop/Admin/layout.html.twig' %} + +{% block content %} + {% block customhtml_edit_form %} + {% include '@Modules/ws_customhtml/views/templates/admin/form/blocks/form.html.twig' %} + {% endblock %} +{% endblock %} + +{% block javascripts %} + {{ parent() }} + +{% endblock %} diff --git a/views/templates/admin/grid/customhtml.html.twig b/views/templates/admin/grid/customhtml.html.twig new file mode 100644 index 0000000..d87abe5 --- /dev/null +++ b/views/templates/admin/grid/customhtml.html.twig @@ -0,0 +1,20 @@ +{% extends '@PrestaShop/Admin/layout.html.twig' %} + +{% block content %} + {% block customhtml_list %} +
+
+ {% include '@PrestaShop/Admin/Common/Grid/grid_panel.html.twig' with {'grid': customHtmlGrid} %} +
+
+ {% endblock %} +{% endblock %} + +{% block javascripts %} + {{ parent() }} + +{% endblock %} diff --git a/views/templates/hook/customhtml_block.tpl b/views/templates/hook/customhtml_block.tpl new file mode 100644 index 0000000..3dd5395 --- /dev/null +++ b/views/templates/hook/customhtml_block.tpl @@ -0,0 +1,12 @@ +{if !empty($blockList)} +
+ {foreach from=$blockList item="block"} +
+ {if $block.show_title && !empty($block.title)} +
{$block.title|escape:'html':'UTF-8'}
+ {/if} +
{$block.content nofilter}
+
+ {/foreach} +
+{/if} diff --git a/ws_customhtml.php b/ws_customhtml.php new file mode 100644 index 0000000..016c27c --- /dev/null +++ b/ws_customhtml.php @@ -0,0 +1,48 @@ + + * @copyright 2007-2024 De Websmid BV + * @license MIT + */ + +use Websmid\CustomHtml\Module\Install; +use Websmid\CustomHtml\Module\AdminHooks; +use Websmid\CustomHtml\Module\FrontHooks; +use PrestaShop\PrestaShop\Core\Module\WidgetInterface; + +if (!defined('_PS_VERSION_')) { + exit; +} + +if (file_exists(__DIR__ . '/vendor/autoload.php')) { + require_once __DIR__ . '/vendor/autoload.php'; +} + +class Ws_CustomHtml extends Module implements WidgetInterface +{ + use Install; + use AdminHooks; + use FrontHooks; + + public function __construct() + { + $this->name = 'ws_customhtml'; + $this->version = '1.0.0'; + $this->author = 'Isabelle Oving-Anno | De Websmid BV'; + $this->need_instance = 0; + $this->bootstrap = true; + + parent::__construct(); + + $this->displayName = $this->trans('Custom HTML', [], 'Modules.Wscustomhtml.Admin'); + $this->description = $this->trans('Create and manage custom HTML blocks for frontend hooks.', [], 'Modules.Wscustomhtml.Admin'); + $this->ps_versions_compliancy = ['min' => '1.7.0.0', 'max' => _PS_VERSION_]; + } + + public function getContent() + { + Tools::redirectAdmin($this->get('router')->generate('customhtml_index')); + } +}