diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..48b8bf9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +vendor/ diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..9f14da9 --- /dev/null +++ b/composer.json @@ -0,0 +1,28 @@ +{ + "name": "websmid/giftdropdown", + "description": "Adds a gift paper style selector to the checkout", + "type": "prestashop-module", + "authors": [ + { + "name": "Isabelle Oving-Anno - De Websmid BV", + "email": "isabelle@dewebsmid.nl" + } + ], + "require": { + "php": ">=7.2.0" + }, + "autoload": { + "psr-4": { + "Websmid\\GiftDropdown\\": "src/" + }, + "classmap": [ + "ws_giftdropdown.php" + ], + "exclude-from-classmap": [] + }, + "config": { + "preferred-install": "dist", + "prepend-autoloader": false + }, + "license": "MIT" +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..e11dbba --- /dev/null +++ b/composer.lock @@ -0,0 +1,20 @@ +{ + "_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": "1e297fbe2fb6a5b7310b2215a227d48e", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.2.0" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/config_nl.xml b/config_nl.xml new file mode 100644 index 0000000..456a6e8 --- /dev/null +++ b/config_nl.xml @@ -0,0 +1,11 @@ + + + ws_giftdropdown + + + + + + 1 + 0 + \ No newline at end of file diff --git a/controllers/front/savepaper.php b/controllers/front/savepaper.php new file mode 100644 index 0000000..9c6a33b --- /dev/null +++ b/controllers/front/savepaper.php @@ -0,0 +1,54 @@ +ajax) { + http_response_code(400); + exit; + } + + $choice = Tools::getValue('gift_paper', ''); + + $saved = Configuration::get(Config::CONFIG_KEY); + $options = $saved ? json_decode($saved, true) : Config::DEFAULT_OPTIONS; + + if (!in_array($choice, $options, true)) { + $this->ajaxRender(json_encode(['success' => false, 'error' => 'Invalid option'])); + exit; + } + + $idCart = (int) Context::getContext()->cart->id; + + if (!$idCart) { + $this->ajaxRender(json_encode(['success' => false, 'error' => 'No cart'])); + exit; + } + + Db::getInstance()->execute( + 'INSERT INTO `' . _DB_PREFIX_ . 'ws_gift_dropdown` + (`id_cart`, `id_order`, `paper_choice`, `date_add`) + VALUES + (' . (int) $idCart . ', 0, \'' . pSQL($choice) . '\', NOW()) + ON DUPLICATE KEY UPDATE + `paper_choice` = \'' . pSQL($choice) . '\', + `date_add` = NOW()' + ); + + $this->ajaxRender(json_encode(['success' => true])); + exit; + } +} diff --git a/index.php b/index.php new file mode 100644 index 0000000..51fd894 --- /dev/null +++ b/index.php @@ -0,0 +1,34 @@ + +* @copyright PrestaShop SA +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +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; diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..53257a4 Binary files /dev/null and b/logo.png differ diff --git a/src/Config/Config.php b/src/Config/Config.php new file mode 100644 index 0000000..ae3a462 --- /dev/null +++ b/src/Config/Config.php @@ -0,0 +1,27 @@ +displayConfirmation($this->trans('Settings saved.', [], 'Admin.Notifications.Success')); + } + + $saved = Configuration::get(Config::CONFIG_KEY); + $options = $saved ? json_decode($saved, true) : Config::DEFAULT_OPTIONS; + + $this->context->smarty->assign([ + 'gift_options' => implode("\n", $options), + 'form_action' => $this->context->link->getAdminLink('AdminModules') . '&configure=' . $this->name, + 'confirmation' => $confirmation, + ]); + + return $this->fetch('module:ws_giftdropdown/views/templates/admin/configuration.tpl'); + } + + public function hookActionFrontControllerSetMedia() + { + if (Tools::getValue('module') !== 'thecheckout' && $this->context->controller->php_self !== 'order') { + return; + } + + $ajaxUrl = $this->context->link->getModuleLink($this->name, 'savepaper', [], true); + Media::addJsDef(['wsGiftDropdownAjaxUrl' => $ajaxUrl]); + + $this->context->controller->registerStylesheet( + 'ws-giftdropdown-css', + 'modules/' . $this->name . '/views/css/giftwrap.css' + ); + + $this->context->controller->registerJavascript( + 'ws-giftdropdown-js', + 'modules/' . $this->name . '/views/js/giftwrap.js', + ['position' => 'bottom', 'priority' => 200] + ); + } + + public function hookDisplayAdminOrderMainBottom(array $params) + { + $idOrder = (int) $params['id_order']; + + $row = Db::getInstance()->getRow( + 'SELECT `paper_choice` FROM `' . _DB_PREFIX_ . 'ws_gift_dropdown` + WHERE `id_order` = ' . (int) $idOrder + ); + + if (!$row) { + return ''; + } + + $order = new Order($idOrder); + if (!(bool) $order->gift) { + return ''; + } + + $this->context->smarty->assign([ + 'gift_paper_choice' => $row['paper_choice'], + ]); + + $this->context->controller->addJS($this->getPathUri() . 'views/js/admin_order.js'); + + return $this->fetch('module:ws_giftdropdown/views/templates/admin/order_detail.tpl'); + } +} diff --git a/src/Module/DisplayHooks.php b/src/Module/DisplayHooks.php new file mode 100644 index 0000000..901fa4a --- /dev/null +++ b/src/Module/DisplayHooks.php @@ -0,0 +1,100 @@ +context->controller->php_self !== 'order') { + return; + } + + $saved = Configuration::get(Config::CONFIG_KEY); + $options = $saved ? json_decode($saved, true) : Config::DEFAULT_OPTIONS; + + $this->context->smarty->assign('gift_paper_options', $options); + + return $this->display(__FILE__, 'views/templates/hook/your_template.tpl'); + } + + public function hookActionGetExtraMailTemplateVars(array $params): void + { + $paperChoice = $this->loadGiftPaperChoice((int) $params['template_vars']['{id_order}']); + if ($paperChoice === null) { + return; + } + + $params['extra_template_vars']['{ws_gift_paper_choice}'] = $paperChoice; + } + + public function hookActionValidateOrder(array $params) + { + /** @var Order $order */ + $order = $params['order']; + $idCart = (int) $params['cart']->id; + + if (!(bool) $order->gift) { + Db::getInstance()->execute( + 'DELETE FROM `' . _DB_PREFIX_ . 'ws_gift_dropdown` + WHERE `id_cart` = ' . (int) $idCart . ' AND `id_order` = 0' + ); + return; + } + + Db::getInstance()->execute( + 'UPDATE `' . _DB_PREFIX_ . 'ws_gift_dropdown` + SET `id_order` = ' . (int) $order->id . ' + WHERE `id_cart` = ' . (int) $idCart + ); + + $paperChoice = Db::getInstance()->getValue( + 'SELECT `paper_choice` FROM `' . _DB_PREFIX_ . 'ws_gift_dropdown` + WHERE `id_cart` = ' . (int) $idCart + ); + + if ($paperChoice) { + $note = 'Cadeaupapier: ' . $paperChoice; + $order->note = $order->note ? $order->note . "\n" . $note : $note; + $order->update(); + } + } + + public function hookDisplayPDFDeliverySlip(array $params) + { + return $this->hookDisplayPDFInvoice($params); + } + + public function hookDisplayPDFInvoice(array $params) + { + /** @var OrderInvoice $orderInvoice */ + $orderInvoice = $params['object']; + $idOrder = (int) $orderInvoice->id_order; + + $paperChoice = Db::getInstance()->getValue( + 'SELECT `paper_choice` FROM `' . _DB_PREFIX_ . 'ws_gift_dropdown` + WHERE `id_order` = ' . (int) $idOrder + ); + + $params['smarty']->assign('gift_paper_choice', $paperChoice ?: ''); + + return ''; + } + + private function loadGiftPaperChoice(int $idOrder): ?string + { + $value = Db::getInstance()->getValue( + 'SELECT `paper_choice` FROM `' . _DB_PREFIX_ . 'ws_gift_dropdown` + WHERE `id_order` = ' . $idOrder + ); + + return $value !== false ? (string) $value : null; + } +} diff --git a/src/Module/Install.php b/src/Module/Install.php new file mode 100644 index 0000000..d445dab --- /dev/null +++ b/src/Module/Install.php @@ -0,0 +1,82 @@ +checkDependency()) { + return false; + } + + if (!$this->createTables()) { + return false; + } + + Configuration::updateValue(Config::CONFIG_KEY, json_encode(Config::DEFAULT_OPTIONS)); + + if (!$this->registerHooks()) { + return false; + } + + return true; + } + + private function checkDependency(): bool + { + $isInstalled = (bool) Db::getInstance()->getValue( + 'SELECT `active` FROM `' . _DB_PREFIX_ . 'module` WHERE `name` = "thecheckout"' + ); + + if (!$isInstalled) { + $this->_errors[] = $this->trans( + 'This module requires TheCheckout to be installed.', + [], + 'Modules.Ws_GiftDropdown.Admin' + ); + + return false; + } + + return true; + } + + private function createTables() + { + $sql = ' + CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'ws_gift_dropdown` ( + `id_ws_gift_dropdown` INT(11) NOT NULL AUTO_INCREMENT, + `id_cart` INT(11) NOT NULL DEFAULT 0, + `id_order` INT(11) NOT NULL DEFAULT 0, + `paper_choice` VARCHAR(64) NOT NULL, + `date_add` DATETIME NOT NULL, + PRIMARY KEY (`id_ws_gift_dropdown`), + UNIQUE KEY `id_cart` (`id_cart`), + KEY `id_order` (`id_order`) + ); + '; + + return Db::getInstance()->execute($sql); + } + + public function registerHooks() + { + foreach (Config::HOOKS as $hook) { + if (!$this->registerHook($hook)) { + return false; + } + } + + return true; + } +} diff --git a/views/css/giftwrap.css b/views/css/giftwrap.css new file mode 100644 index 0000000..b8fbdc6 --- /dev/null +++ b/views/css/giftwrap.css @@ -0,0 +1,32 @@ +#gift-paper-choice { + margin-top: 12px; + padding: 12px 16px; + background: #f9f9f9; + border: 1px solid #e0e0e0; + border-radius: 4px; +} + +#gift-paper-choice legend, +#gift-paper-choice .gift-paper-label { + font-weight: 600; + margin-bottom: 8px; + display: block; +} + +#gift-paper-choice .gift-paper-options { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +#gift-paper-choice .gift-paper-option { + display: flex; + align-items: center; + gap: 6px; + cursor: pointer; +} + +#gift-paper-choice .gift-paper-option input[type="radio"] { + margin: 0; + cursor: pointer; +} diff --git a/views/js/admin_order.js b/views/js/admin_order.js new file mode 100644 index 0000000..046ad90 --- /dev/null +++ b/views/js/admin_order.js @@ -0,0 +1,6 @@ +document.addEventListener("DOMContentLoaded", function () { + var panel = document.getElementById("ws-gift-panel"); + var badge = document.querySelector('[data-badge="gift"]'); + if (!panel || !badge) return; + badge.textContent = "Cadeauverpakking - " + panel.dataset.paperChoice; +}); diff --git a/views/js/giftwrap.js b/views/js/giftwrap.js new file mode 100644 index 0000000..f7f7f7a --- /dev/null +++ b/views/js/giftwrap.js @@ -0,0 +1,33 @@ +(function () { + 'use strict'; + + function savePaperChoice(choice) { + var ajaxUrl = window.wsGiftDropdownAjaxUrl || ''; + if (!ajaxUrl || !choice) { + return; + } + var xhr = new XMLHttpRequest(); + xhr.open('POST', ajaxUrl, true); + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + xhr.send('gift_paper=' + encodeURIComponent(choice) + '&ajax=1'); + } + + function init() { + var checked = document.querySelector('input[name="gift_paper"]:checked'); + if (checked) { + savePaperChoice(checked.value); + } + + document.addEventListener('change', function (e) { + if (e.target && e.target.name === 'gift_paper') { + savePaperChoice(e.target.value); + } + }); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +}()); diff --git a/views/templates/admin/configuration.tpl b/views/templates/admin/configuration.tpl new file mode 100644 index 0000000..15f424d --- /dev/null +++ b/views/templates/admin/configuration.tpl @@ -0,0 +1,35 @@ +{if $confirmation} + {$confirmation nofilter} +{/if} + +
+
+ + {l s='Gift Paper Options' mod='ws_giftdropdown'} +
+
+

+ {l s='Enter one paper style per line. These options will appear as radio buttons in the checkout when the customer selects gift wrapping.' mod='ws_giftdropdown'} +

+
+
+ + +
+ +
+
+
diff --git a/views/templates/admin/order_detail.tpl b/views/templates/admin/order_detail.tpl new file mode 100644 index 0000000..ccbabf6 --- /dev/null +++ b/views/templates/admin/order_detail.tpl @@ -0,0 +1,12 @@ +
+
+ + {l s='Gift wrapping' mod='ws_giftdropdown'} +
+
+
+
{l s='Paper style' mod='ws_giftdropdown'}
+
{$gift_paper_choice|escape:'htmlall':'UTF-8'}
+
+
+
diff --git a/ws_giftdropdown.php b/ws_giftdropdown.php new file mode 100644 index 0000000..710052b --- /dev/null +++ b/ws_giftdropdown.php @@ -0,0 +1,55 @@ + + * @copyright 2007-2025 De Websmid BV + * @license Valid for 1 website (or project) for each purchase of license + * International Registered Trademark & Property of De Websmid BV + */ + +use Websmid\GiftDropdown\Module\Install; +use Websmid\GiftDropdown\Module\AdminHooks; +use Websmid\GiftDropdown\Module\DisplayHooks; + +if (!defined('_PS_VERSION_')) { + exit; +} + +if (file_exists(__DIR__ . '/vendor/autoload.php')) { + require_once __DIR__ . '/vendor/autoload.php'; +} + +class Ws_GiftDropdown extends Module +{ + use Install; + use AdminHooks; + use DisplayHooks; + + public function __construct() + { + $this->name = 'ws_giftdropdown'; + $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('Gift Paper Selector', [], 'Modules.Ws_GiftDropdown.Admin'); + $this->description = $this->trans('Adds a paper style selector to the gift wrap option at checkout', [], 'Modules.Ws_GiftDropdown.Description'); + $this->ps_versions_compliancy = ['min' => '1.7.0.0', 'max' => _PS_VERSION_]; + } +}