058937bd64
- Fix CSS loading: register stylesheet via actionFrontControllerSetMedia hook instead of initContent() — guaranteed to fire before page render - Upgrade script registers new hook on existing installations - Feature 1: pre-fill order_reference from ?ref= URL parameter - Feature 2: pre-fill customer name/email from logged-in account when navigating with ?ref= (already worked; now order ref is also pre-filled) - Feature 4: compute Widerrufsfrist (order date +14 days) from DB and display on form (when ref in URL), confirm, and success pages - Feature 3: EN mail templates were already present Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
650 lines
24 KiB
PHP
650 lines
24 KiB
PHP
<?php
|
|
/**
|
|
* Minimal electronic withdrawal button for PrestaShop.
|
|
*
|
|
* This module implements a minimal two-step withdrawal declaration flow:
|
|
* - public footer link "Vertrag widerrufen"
|
|
* - public form for full or partial withdrawal
|
|
* - confirmation step with button "Widerruf bestätigen"
|
|
* - database record
|
|
* - email confirmation to customer and notification to shop owner
|
|
*/
|
|
|
|
if (!defined('_PS_VERSION_')) {
|
|
exit;
|
|
}
|
|
|
|
class Simple_withdrawalbutton extends Module
|
|
{
|
|
public const TABLE_REQUEST = 'simple_withdrawal_request';
|
|
public const CONF_SHOP_EMAIL = 'SIMPLE_WITHDRAWAL_SHOP_EMAIL';
|
|
public const CONF_RATE_LIMIT = 'SIMPLE_WITHDRAWAL_RATE_LIMIT';
|
|
public const CONF_PRIVACY_URL = 'SIMPLE_WITHDRAWAL_PRIVACY_URL';
|
|
public const CONF_REVOCATION_URL = 'SIMPLE_WITHDRAWAL_REVOCATION_URL';
|
|
public const CONF_RETENTION_MONTHS = 'SIMPLE_WITHDRAWAL_RETENTION_MONTHS';
|
|
|
|
public function __construct()
|
|
{
|
|
$this->name = 'simple_withdrawalbutton';
|
|
$this->tab = 'administration';
|
|
$this->version = '0.1.4';
|
|
$this->author = 'Arne Weiss';
|
|
$this->need_instance = 0;
|
|
$this->bootstrap = true;
|
|
$this->ps_versions_compliancy = [
|
|
'min' => '1.7.8.0',
|
|
'max' => _PS_VERSION_,
|
|
];
|
|
|
|
parent::__construct();
|
|
|
|
$this->displayName = $this->l('Withdrawal button');
|
|
$this->description = $this->l('Adds a minimal two-step electronic withdrawal function for B2C orders.');
|
|
$this->confirmUninstall = $this->l('Uninstall the module? Existing withdrawal records will be kept in the database.');
|
|
}
|
|
|
|
public function install()
|
|
{
|
|
return parent::install()
|
|
&& $this->installSql()
|
|
&& $this->installTab()
|
|
&& $this->registerHook('displayFooter')
|
|
&& $this->registerHook('displayCustomerAccount')
|
|
&& $this->registerHook('actionFrontControllerSetMedia')
|
|
&& Configuration::updateValue(self::CONF_SHOP_EMAIL, (string) Configuration::get('PS_SHOP_EMAIL'))
|
|
&& Configuration::updateValue(self::CONF_RATE_LIMIT, '5')
|
|
&& Configuration::updateValue(self::CONF_PRIVACY_URL, '')
|
|
&& Configuration::updateValue(self::CONF_REVOCATION_URL, '')
|
|
&& Configuration::updateValue(self::CONF_RETENTION_MONTHS, '0');
|
|
}
|
|
|
|
public function uninstall()
|
|
{
|
|
// Keep withdrawal records intentionally. These may be legally relevant.
|
|
return $this->uninstallTab()
|
|
&& Configuration::deleteByName(self::CONF_SHOP_EMAIL)
|
|
&& Configuration::deleteByName(self::CONF_RATE_LIMIT)
|
|
&& Configuration::deleteByName(self::CONF_PRIVACY_URL)
|
|
&& Configuration::deleteByName(self::CONF_REVOCATION_URL)
|
|
&& Configuration::deleteByName(self::CONF_RETENTION_MONTHS)
|
|
&& parent::uninstall();
|
|
}
|
|
|
|
private function installSql()
|
|
{
|
|
$sql = 'CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . pSQL(self::TABLE_REQUEST) . '` (
|
|
`id_withdrawal_request` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
`id_order` INT UNSIGNED NULL,
|
|
`order_reference` VARCHAR(64) NOT NULL,
|
|
`customer_name` VARCHAR(255) NOT NULL,
|
|
`customer_email` VARCHAR(255) NOT NULL,
|
|
`withdrawal_scope` ENUM("full", "partial") NOT NULL,
|
|
`withdrawal_items_text` TEXT NULL,
|
|
`message` TEXT NULL,
|
|
`created_at` DATETIME NOT NULL,
|
|
`confirmation_sent_at` DATETIME NULL,
|
|
`status` ENUM("new", "processing", "closed") NOT NULL DEFAULT "new",
|
|
`id_shop` INT UNSIGNED NULL,
|
|
`id_lang` INT UNSIGNED NULL,
|
|
`customer_ip_hash` CHAR(64) NULL,
|
|
`user_agent_hash` CHAR(64) NULL,
|
|
PRIMARY KEY (`id_withdrawal_request`),
|
|
KEY `idx_order_reference` (`order_reference`),
|
|
KEY `idx_customer_email` (`customer_email`),
|
|
KEY `idx_created_at` (`created_at`),
|
|
KEY `idx_status` (`status`),
|
|
KEY `idx_shop` (`id_shop`)
|
|
) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;';
|
|
|
|
return Db::getInstance()->execute($sql);
|
|
}
|
|
|
|
private function installTab()
|
|
{
|
|
$className = 'AdminSimpleWithdrawal';
|
|
if ((int) Tab::getIdFromClassName($className) > 0) {
|
|
return true;
|
|
}
|
|
|
|
$tab = new Tab();
|
|
$tab->active = 1;
|
|
$tab->class_name = $className;
|
|
$tab->module = $this->name;
|
|
$tab->id_parent = $this->getOrdersParentTabId();
|
|
|
|
foreach (Language::getLanguages(false) as $lang) {
|
|
$tab->name[(int) $lang['id_lang']] = 'Widerrufe';
|
|
}
|
|
|
|
return (bool) $tab->add();
|
|
}
|
|
|
|
private function uninstallTab()
|
|
{
|
|
$idTab = (int) Tab::getIdFromClassName('AdminSimpleWithdrawal');
|
|
if ($idTab <= 0) {
|
|
return true;
|
|
}
|
|
|
|
$tab = new Tab($idTab);
|
|
return (bool) $tab->delete();
|
|
}
|
|
|
|
private function getOrdersParentTabId()
|
|
{
|
|
$candidates = [
|
|
'AdminParentOrders',
|
|
'AdminOrders',
|
|
'AdminParentModulesSf',
|
|
];
|
|
|
|
foreach ($candidates as $className) {
|
|
$id = (int) Tab::getIdFromClassName($className);
|
|
if ($id > 0) {
|
|
return $id;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
public function getContent()
|
|
{
|
|
$output = '';
|
|
|
|
if (Tools::isSubmit('submitCypWithdrawalSettings')) {
|
|
$email = trim((string) Tools::getValue(self::CONF_SHOP_EMAIL));
|
|
$rateLimit = (int) Tools::getValue(self::CONF_RATE_LIMIT);
|
|
$privacyUrl = trim((string) Tools::getValue(self::CONF_PRIVACY_URL));
|
|
$revocationUrl = trim((string) Tools::getValue(self::CONF_REVOCATION_URL));
|
|
$retentionMonths = (int) Tools::getValue(self::CONF_RETENTION_MONTHS);
|
|
|
|
if (!Validate::isEmail($email)) {
|
|
$output .= $this->displayError($this->l('Please enter a valid shop notification email.'));
|
|
} else {
|
|
Configuration::updateValue(self::CONF_SHOP_EMAIL, $email);
|
|
Configuration::updateValue(self::CONF_RATE_LIMIT, (string) max(1, min(50, $rateLimit)));
|
|
Configuration::updateValue(self::CONF_PRIVACY_URL, $privacyUrl);
|
|
Configuration::updateValue(self::CONF_REVOCATION_URL, $revocationUrl);
|
|
Configuration::updateValue(self::CONF_RETENTION_MONTHS, (string) max(0, $retentionMonths));
|
|
$output .= $this->displayConfirmation($this->l('Settings updated.'));
|
|
}
|
|
}
|
|
|
|
if (Tools::isSubmit('submitCypWithdrawalPurge')) {
|
|
$retentionMonths = (int) Configuration::get(self::CONF_RETENTION_MONTHS);
|
|
if ($retentionMonths > 0) {
|
|
$deleted = $this->purgeOldRecords($retentionMonths);
|
|
$output .= $this->displayConfirmation(
|
|
sprintf($this->l('%d withdrawal record(s) older than %d month(s) have been deleted.'), $deleted, $retentionMonths)
|
|
);
|
|
} else {
|
|
$output .= $this->displayError($this->l('No retention period configured. Set a value greater than 0 first.'));
|
|
}
|
|
}
|
|
|
|
$adminLink = $this->context->link->getAdminLink('AdminSimpleWithdrawal');
|
|
$withdrawalLink = $this->getWithdrawalLink();
|
|
|
|
$this->context->smarty->assign([
|
|
'admin_link' => $adminLink,
|
|
'withdrawal_link' => $withdrawalLink,
|
|
]);
|
|
|
|
return $output . $this->renderForm() . $this->display(__FILE__, 'views/templates/admin/config_info.tpl');
|
|
}
|
|
|
|
private function renderForm()
|
|
{
|
|
$helper = new HelperForm();
|
|
$helper->show_toolbar = false;
|
|
$helper->table = $this->table;
|
|
$helper->module = $this;
|
|
$helper->default_form_language = (int) Configuration::get('PS_LANG_DEFAULT');
|
|
$helper->allow_employee_form_lang = (int) Configuration::get('PS_BO_ALLOW_EMPLOYEE_FORM_LANG');
|
|
$helper->identifier = $this->name;
|
|
$helper->submit_action = 'submitCypWithdrawalSettings';
|
|
$helper->currentIndex = AdminController::$currentIndex . '&configure=' . $this->name;
|
|
$helper->token = Tools::getAdminTokenLite('AdminModules');
|
|
$helper->tpl_vars = [
|
|
'fields_value' => [
|
|
self::CONF_SHOP_EMAIL => (string) Configuration::get(self::CONF_SHOP_EMAIL),
|
|
self::CONF_RATE_LIMIT => (string) Configuration::get(self::CONF_RATE_LIMIT),
|
|
self::CONF_PRIVACY_URL => (string) Configuration::get(self::CONF_PRIVACY_URL),
|
|
self::CONF_REVOCATION_URL => (string) Configuration::get(self::CONF_REVOCATION_URL),
|
|
self::CONF_RETENTION_MONTHS => (string) Configuration::get(self::CONF_RETENTION_MONTHS),
|
|
],
|
|
'languages' => $this->context->controller->getLanguages(),
|
|
'id_language' => (int) $this->context->language->id,
|
|
];
|
|
|
|
$fieldsForm = [
|
|
'form' => [
|
|
'legend' => [
|
|
'title' => $this->l('Withdrawal button settings'),
|
|
'icon' => 'icon-undo',
|
|
],
|
|
'input' => [
|
|
[
|
|
'type' => 'text',
|
|
'label' => $this->l('Shop notification email'),
|
|
'name' => self::CONF_SHOP_EMAIL,
|
|
'required' => true,
|
|
'desc' => $this->l('New withdrawal declarations are sent to this address.'),
|
|
],
|
|
[
|
|
'type' => 'text',
|
|
'label' => $this->l('Rate limit per hour'),
|
|
'name' => self::CONF_RATE_LIMIT,
|
|
'required' => true,
|
|
'desc' => $this->l('Maximum saved withdrawal declarations per email or IP hash per hour.'),
|
|
],
|
|
[
|
|
'type' => 'text',
|
|
'label' => $this->l('Privacy policy URL'),
|
|
'name' => self::CONF_PRIVACY_URL,
|
|
'required' => false,
|
|
'desc' => $this->l('Link to your Datenschutzerklärung shown in the withdrawal form (GDPR Art. 13). Leave empty to show plain text without link.'),
|
|
],
|
|
[
|
|
'type' => 'text',
|
|
'label' => $this->l('Widerrufsbelehrung URL'),
|
|
'name' => self::CONF_REVOCATION_URL,
|
|
'required' => false,
|
|
'desc' => $this->l('Link to your revocation policy page shown above the withdrawal form. Leave empty to hide the link.'),
|
|
],
|
|
[
|
|
'type' => 'text',
|
|
'label' => $this->l('Retention period (months)'),
|
|
'name' => self::CONF_RETENTION_MONTHS,
|
|
'required' => false,
|
|
'desc' => $this->l('Delete withdrawal records older than this many months when purging. Set to 0 to keep records indefinitely (GDPR: define a retention period).'),
|
|
],
|
|
],
|
|
'buttons' => [
|
|
[
|
|
'title' => $this->l('Purge old records'),
|
|
'name' => 'submitCypWithdrawalPurge',
|
|
'icon' => 'process-icon-delete',
|
|
'class' => 'btn btn-default pull-right',
|
|
],
|
|
],
|
|
'submit' => [
|
|
'title' => $this->l('Save'),
|
|
],
|
|
],
|
|
];
|
|
|
|
return $helper->generateForm([$fieldsForm]);
|
|
}
|
|
|
|
public function hookDisplayFooter(array $params)
|
|
{
|
|
$this->context->smarty->assign([
|
|
'withdrawal_link' => $this->getWithdrawalLink(),
|
|
]);
|
|
|
|
return $this->display(__FILE__, 'views/templates/hook/footer.tpl');
|
|
}
|
|
|
|
public function hookDisplayCustomerAccount(array $params)
|
|
{
|
|
$this->context->smarty->assign([
|
|
'withdrawal_link' => $this->getWithdrawalLink(),
|
|
]);
|
|
|
|
return $this->display(__FILE__, 'views/templates/hook/customer_account.tpl');
|
|
}
|
|
|
|
public function hookActionFrontControllerSetMedia()
|
|
{
|
|
if (
|
|
Tools::getValue('module') === $this->name
|
|
&& Tools::getValue('controller') === 'withdraw'
|
|
) {
|
|
$this->context->controller->registerStylesheet(
|
|
'cyp-withdrawal',
|
|
'modules/' . $this->name . '/views/css/withdrawal.css',
|
|
['media' => 'all', 'priority' => 200]
|
|
);
|
|
}
|
|
}
|
|
|
|
public function getWithdrawalLink()
|
|
{
|
|
return $this->context->link->getModuleLink($this->name, 'withdraw', [], true);
|
|
}
|
|
|
|
public function getFrontToken()
|
|
{
|
|
$guestId = isset($this->context->cookie->id_guest) ? (string) $this->context->cookie->id_guest : '';
|
|
$customerId = isset($this->context->customer->id) ? (string) $this->context->customer->id : '';
|
|
|
|
return Tools::hash($this->name . '|withdraw|' . $guestId . '|' . $customerId . '|' . _COOKIE_KEY_);
|
|
}
|
|
|
|
public function isValidFrontToken($token)
|
|
{
|
|
return hash_equals($this->getFrontToken(), (string) $token);
|
|
}
|
|
|
|
public function cleanText($value, $maxLength = 255)
|
|
{
|
|
$value = trim((string) $value);
|
|
$value = strip_tags($value);
|
|
$value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $value);
|
|
if ($maxLength > 0 && Tools::strlen($value) > $maxLength) {
|
|
$value = Tools::substr($value, 0, $maxLength);
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
public function lookupOrderIdByReference($orderReference)
|
|
{
|
|
$orderReference = $this->cleanText($orderReference, 64);
|
|
if ($orderReference === '') {
|
|
return null;
|
|
}
|
|
|
|
$sql = 'SELECT `id_order`
|
|
FROM `' . _DB_PREFIX_ . 'orders`
|
|
WHERE `reference` = "' . pSQL($orderReference) . '"
|
|
AND `id_shop` = ' . (int) $this->context->shop->id . '
|
|
ORDER BY `id_order` DESC';
|
|
|
|
$idOrder = (int) Db::getInstance()->getValue($sql);
|
|
return $idOrder > 0 ? $idOrder : null;
|
|
}
|
|
|
|
public function lookupOrderInfoByReference($orderReference)
|
|
{
|
|
$orderReference = $this->cleanText($orderReference, 64);
|
|
if ($orderReference === '') {
|
|
return null;
|
|
}
|
|
|
|
$sql = 'SELECT `id_order`, `date_add`, `id_customer`
|
|
FROM `' . _DB_PREFIX_ . 'orders`
|
|
WHERE `reference` = "' . pSQL($orderReference) . '"
|
|
AND `id_shop` = ' . (int) $this->context->shop->id . '
|
|
ORDER BY `id_order` DESC';
|
|
|
|
$row = Db::getInstance()->getRow($sql);
|
|
if (!$row) {
|
|
return null;
|
|
}
|
|
|
|
return [
|
|
'id_order' => (int) $row['id_order'],
|
|
'date_add' => $row['date_add'],
|
|
'id_customer' => (int) $row['id_customer'],
|
|
];
|
|
}
|
|
|
|
public function computeWithdrawalDeadline($orderDateAdd, $isoCode = 'de')
|
|
{
|
|
$timestamp = strtotime((string) $orderDateAdd);
|
|
if (!$timestamp) {
|
|
return null;
|
|
}
|
|
|
|
$deadline = strtotime('+14 days', $timestamp);
|
|
|
|
return ($isoCode === 'de') ? date('d.m.Y', $deadline) : date('d/m/Y', $deadline);
|
|
}
|
|
|
|
public function hashForPrivacy($value)
|
|
{
|
|
$value = trim((string) $value);
|
|
if ($value === '') {
|
|
return null;
|
|
}
|
|
|
|
return hash('sha256', $value . '|' . _COOKIE_KEY_);
|
|
}
|
|
|
|
public function getClientIp()
|
|
{
|
|
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
|
|
return (string) $_SERVER['HTTP_CF_CONNECTING_IP'];
|
|
}
|
|
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
|
$parts = explode(',', (string) $_SERVER['HTTP_X_FORWARDED_FOR']);
|
|
return trim($parts[0]);
|
|
}
|
|
if (!empty($_SERVER['REMOTE_ADDR'])) {
|
|
return (string) $_SERVER['REMOTE_ADDR'];
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
public function isRateLimited($customerEmail, $ipHash)
|
|
{
|
|
$limit = (int) Configuration::get(self::CONF_RATE_LIMIT);
|
|
if ($limit <= 0) {
|
|
$limit = 5;
|
|
}
|
|
|
|
$conditions = ['`customer_email` = "' . pSQL($customerEmail) . '"'];
|
|
if ($ipHash) {
|
|
$conditions[] = '`customer_ip_hash` = "' . pSQL($ipHash) . '"';
|
|
}
|
|
|
|
$sql = 'SELECT COUNT(*)
|
|
FROM `' . _DB_PREFIX_ . pSQL(self::TABLE_REQUEST) . '`
|
|
WHERE (`created_at` >= DATE_SUB(NOW(), INTERVAL 1 HOUR))
|
|
AND (' . implode(' OR ', $conditions) . ')';
|
|
|
|
return (int) Db::getInstance()->getValue($sql) >= $limit;
|
|
}
|
|
|
|
public function saveWithdrawal(array $data)
|
|
{
|
|
$createdAt = date('Y-m-d H:i:s');
|
|
$ipHash = $this->hashForPrivacy($this->getClientIp());
|
|
$userAgentHash = $this->hashForPrivacy(isset($_SERVER['HTTP_USER_AGENT']) ? (string) $_SERVER['HTTP_USER_AGENT'] : '');
|
|
|
|
$idOrder = $this->lookupOrderIdByReference($data['order_reference']);
|
|
|
|
$insert = [
|
|
'id_order' => $idOrder ? (int) $idOrder : null,
|
|
'order_reference' => $data['order_reference'],
|
|
'customer_name' => $data['customer_name'],
|
|
'customer_email' => $data['customer_email'],
|
|
'withdrawal_scope' => $data['withdrawal_scope'],
|
|
'withdrawal_items_text' => $data['withdrawal_items_text'] !== '' ? $data['withdrawal_items_text'] : null,
|
|
'message' => $data['message'] !== '' ? $data['message'] : null,
|
|
'created_at' => $createdAt,
|
|
'status' => 'new',
|
|
'id_shop' => (int) $this->context->shop->id,
|
|
'id_lang' => (int) $this->context->language->id,
|
|
'customer_ip_hash' => $ipHash ?: null,
|
|
'user_agent_hash' => $userAgentHash ?: null,
|
|
];
|
|
|
|
$ok = Db::getInstance()->insert(self::TABLE_REQUEST, $insert, false, true, Db::INSERT, false);
|
|
if (!$ok) {
|
|
return false;
|
|
}
|
|
|
|
$id = (int) Db::getInstance()->Insert_ID();
|
|
return [
|
|
'id_withdrawal_request' => $id,
|
|
'created_at' => $createdAt,
|
|
'id_order' => $idOrder,
|
|
'customer_ip_hash' => $ipHash,
|
|
];
|
|
}
|
|
|
|
public function markConfirmationSent($idWithdrawalRequest)
|
|
{
|
|
return Db::getInstance()->update(
|
|
self::TABLE_REQUEST,
|
|
['confirmation_sent_at' => date('Y-m-d H:i:s')],
|
|
'`id_withdrawal_request` = ' . (int) $idWithdrawalRequest
|
|
);
|
|
}
|
|
|
|
public function sendCustomerConfirmation(array $data, $createdAt)
|
|
{
|
|
$idLang = (int) $this->context->language->id;
|
|
$idShop = (int) $this->context->shop->id;
|
|
$isoCode = $this->getLangIsoCode($idLang);
|
|
$shopName = (string) Configuration::get('PS_SHOP_NAME');
|
|
$shopEmail = (string) Configuration::get('PS_SHOP_EMAIL');
|
|
$scopeLabel = $this->getScopeLabel($data['withdrawal_scope'], $isoCode);
|
|
|
|
$templateVars = [
|
|
'{customer_name}' => $data['customer_name'],
|
|
'{order_reference}' => $data['order_reference'],
|
|
'{scope_label}' => $scopeLabel,
|
|
'{withdrawal_items_text}' => $data['withdrawal_items_text'] !== '' ? $data['withdrawal_items_text'] : '-',
|
|
'{message}' => $data['message'] !== '' ? $data['message'] : '-',
|
|
'{submitted_at}' => $this->formatDateTimeForMail($createdAt, $isoCode),
|
|
'{shop_name}' => $shopName,
|
|
];
|
|
|
|
try {
|
|
return Mail::Send(
|
|
$idLang,
|
|
'withdrawal_confirmation',
|
|
$this->getConfirmationSubject($isoCode),
|
|
$templateVars,
|
|
$data['customer_email'],
|
|
$data['customer_name'],
|
|
$shopEmail,
|
|
$shopName,
|
|
null,
|
|
null,
|
|
_PS_MODULE_DIR_ . $this->name . '/mails/',
|
|
false,
|
|
$idShop
|
|
);
|
|
} catch (\Exception $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function sendShopNotification(array $data, $createdAt, $idWithdrawalRequest)
|
|
{
|
|
$idLang = (int) $this->context->language->id;
|
|
$idShop = (int) $this->context->shop->id;
|
|
$isoCode = $this->getLangIsoCode($idLang);
|
|
$shopName = (string) Configuration::get('PS_SHOP_NAME');
|
|
$shopEmail = (string) Configuration::get('PS_SHOP_EMAIL');
|
|
$toEmail = (string) Configuration::get(self::CONF_SHOP_EMAIL);
|
|
if (!Validate::isEmail($toEmail)) {
|
|
$toEmail = $shopEmail;
|
|
}
|
|
|
|
$scopeLabel = $this->getScopeLabel($data['withdrawal_scope'], $isoCode);
|
|
$adminLink = $this->context->link->getAdminLink('AdminSimpleWithdrawal');
|
|
|
|
$templateVars = [
|
|
'{id_withdrawal_request}' => (string) $idWithdrawalRequest,
|
|
'{customer_name}' => $data['customer_name'],
|
|
'{customer_email}' => $data['customer_email'],
|
|
'{order_reference}' => $data['order_reference'],
|
|
'{scope_label}' => $scopeLabel,
|
|
'{withdrawal_items_text}' => $data['withdrawal_items_text'] !== '' ? $data['withdrawal_items_text'] : '-',
|
|
'{message}' => $data['message'] !== '' ? $data['message'] : '-',
|
|
'{submitted_at}' => $this->formatDateTimeForMail($createdAt, $isoCode),
|
|
'{admin_link}' => $adminLink,
|
|
'{shop_name}' => $shopName,
|
|
];
|
|
|
|
try {
|
|
return Mail::Send(
|
|
$idLang,
|
|
'withdrawal_notification',
|
|
$this->getNotificationSubject($data['order_reference'], $isoCode),
|
|
$templateVars,
|
|
$toEmail,
|
|
null,
|
|
$shopEmail,
|
|
$shopName,
|
|
null,
|
|
null,
|
|
_PS_MODULE_DIR_ . $this->name . '/mails/',
|
|
false,
|
|
$idShop
|
|
);
|
|
} catch (\Exception $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function purgeOldRecords($months)
|
|
{
|
|
$months = (int) $months;
|
|
if ($months <= 0) {
|
|
return 0;
|
|
}
|
|
|
|
$sql = 'SELECT COUNT(*) FROM `' . _DB_PREFIX_ . pSQL(self::TABLE_REQUEST) . '`
|
|
WHERE `created_at` < DATE_SUB(NOW(), INTERVAL ' . $months . ' MONTH)';
|
|
$count = (int) Db::getInstance()->getValue($sql);
|
|
|
|
if ($count > 0) {
|
|
Db::getInstance()->execute(
|
|
'DELETE FROM `' . _DB_PREFIX_ . pSQL(self::TABLE_REQUEST) . '`
|
|
WHERE `created_at` < DATE_SUB(NOW(), INTERVAL ' . $months . ' MONTH)'
|
|
);
|
|
}
|
|
|
|
return $count;
|
|
}
|
|
|
|
public function formatDateTimeForMail($dateTime, $isoCode = 'de')
|
|
{
|
|
$timestamp = strtotime((string) $dateTime);
|
|
if (!$timestamp) {
|
|
$timestamp = time();
|
|
}
|
|
|
|
if ($isoCode === 'de') {
|
|
return date('d.m.Y H:i:s', $timestamp) . ' Uhr';
|
|
}
|
|
|
|
return date('Y-m-d H:i:s', $timestamp);
|
|
}
|
|
|
|
private function getLangIsoCode($idLang)
|
|
{
|
|
$iso = Language::getIsoById((int) $idLang);
|
|
return $iso ?: 'de';
|
|
}
|
|
|
|
private function getScopeLabel($scope, $isoCode)
|
|
{
|
|
$labels = [
|
|
'de' => ['partial' => 'Teil der Bestellung', 'full' => 'gesamte Bestellung'],
|
|
'en' => ['partial' => 'part of the order', 'full' => 'full order'],
|
|
];
|
|
$map = isset($labels[$isoCode]) ? $labels[$isoCode] : $labels['de'];
|
|
|
|
return $scope === 'partial' ? $map['partial'] : $map['full'];
|
|
}
|
|
|
|
private function getConfirmationSubject($isoCode)
|
|
{
|
|
$subjects = [
|
|
'de' => 'Eingangsbestätigung Ihres Widerrufs',
|
|
'en' => 'Confirmation of receipt of your withdrawal',
|
|
];
|
|
|
|
return isset($subjects[$isoCode]) ? $subjects[$isoCode] : $subjects['de'];
|
|
}
|
|
|
|
private function getNotificationSubject($orderReference, $isoCode)
|
|
{
|
|
if ($isoCode === 'en') {
|
|
return 'New withdrawal: order ' . $orderReference;
|
|
}
|
|
|
|
return 'Neuer Widerruf: Bestellung ' . $orderReference;
|
|
}
|
|
}
|