Initial commit with all bug and compliance fixes

- Remove double pSQL() escaping in Db::insert/update calls (data corruption fix)
- Rename honeypot field from 'website' to 'cyp_hp_v1' to prevent browser autofill false positives
- Wrap Mail::Send() in try/catch; capture shop notification result to detect failures
- Scope order lookup to current shop (multi-shop fix)
- Translate scope_label and email subjects per language (de/en)
- Make formatDateTimeForMail() language-aware
- Add GDPR Art. 13 privacy notice to withdrawal form
- Add configurable privacy policy URL and Widerrufsbelehrung link
- Add configurable retention period and manual purge button in admin settings
- Show full submitted data on success page as proper receipt
- Fix admin redirect URL format in processStatusUpdate()
- Remove IP/UA hash display from admin detail view (GDPR data minimization)
This commit is contained in:
Arne Weiss
2026-06-01 07:30:29 +02:00
commit 346ee7b616
33 changed files with 1663 additions and 0 deletions
@@ -0,0 +1,153 @@
<?php
if (!defined('_PS_VERSION_')) {
exit;
}
class AdminCypWithdrawalController extends ModuleAdminController
{
public function __construct()
{
$this->bootstrap = true;
$this->table = Cyp_Withdrawalbutton::TABLE_REQUEST;
$this->identifier = 'id_withdrawal_request';
$this->lang = false;
$this->explicitSelect = true;
$this->_select = 'a.*';
$this->_defaultOrderBy = 'created_at';
$this->_defaultOrderWay = 'DESC';
$this->allow_export = true;
$this->list_no_link = false;
$this->actions = ['view'];
$this->fields_list = [
'id_withdrawal_request' => [
'title' => $this->l('ID'),
'align' => 'center',
'class' => 'fixed-width-xs',
],
'created_at' => [
'title' => $this->l('Received'),
'type' => 'datetime',
'filter_key' => 'a!created_at',
],
'order_reference' => [
'title' => $this->l('Order reference'),
'filter_key' => 'a!order_reference',
],
'customer_name' => [
'title' => $this->l('Name'),
'filter_key' => 'a!customer_name',
],
'customer_email' => [
'title' => $this->l('Email'),
'filter_key' => 'a!customer_email',
],
'withdrawal_scope' => [
'title' => $this->l('Scope'),
'callback' => 'renderScopeLabel',
'callback_object' => $this,
'filter_key' => 'a!withdrawal_scope',
],
'status' => [
'title' => $this->l('Status'),
'callback' => 'renderStatusLabel',
'callback_object' => $this,
'filter_key' => 'a!status',
],
];
parent::__construct();
}
public function renderScopeLabel($value, $row)
{
if ($value === 'partial') {
return '<span class="label label-info">' . $this->l('Partial') . '</span>';
}
return '<span class="label label-default">' . $this->l('Full order') . '</span>';
}
public function renderStatusLabel($value, $row)
{
$labels = [
'new' => ['class' => 'label-warning', 'text' => $this->l('New')],
'processing' => ['class' => 'label-info', 'text' => $this->l('Processing')],
'closed' => ['class' => 'label-success', 'text' => $this->l('Closed')],
];
$label = isset($labels[$value]) ? $labels[$value] : ['class' => 'label-default', 'text' => $value];
return '<span class="label ' . $label['class'] . '">' . Tools::safeOutput($label['text']) . '</span>';
}
public function postProcess()
{
if (Tools::isSubmit('submitCypWithdrawalStatus')) {
$this->processStatusUpdate();
return;
}
parent::postProcess();
}
private function processStatusUpdate()
{
$id = (int) Tools::getValue($this->identifier);
$status = (string) Tools::getValue('status');
$allowedStatuses = ['new', 'processing', 'closed'];
if ($id <= 0 || !in_array($status, $allowedStatuses, true)) {
$this->errors[] = $this->l('Invalid status update.');
return;
}
$ok = Db::getInstance()->update(
Cyp_Withdrawalbutton::TABLE_REQUEST,
['status' => $status],
'`id_withdrawal_request` = ' . (int) $id
);
if ($ok) {
Tools::redirectAdmin(self::$currentIndex . '&token=' . $this->token . '&conf=4&view' . $this->table . '=1&' . $this->identifier . '=' . (int) $id);
}
$this->errors[] = $this->l('The status could not be updated.');
}
public function renderView()
{
$id = (int) Tools::getValue($this->identifier);
$row = Db::getInstance()->getRow(
'SELECT * FROM `' . _DB_PREFIX_ . pSQL(Cyp_Withdrawalbutton::TABLE_REQUEST) . '`
WHERE `id_withdrawal_request` = ' . (int) $id
);
if (!$row) {
$this->errors[] = $this->l('Withdrawal request not found.');
return parent::renderList();
}
$orderLink = '';
if (!empty($row['id_order'])) {
$orderLink = $this->context->link->getAdminLink('AdminOrders', true, [], [
'id_order' => (int) $row['id_order'],
'vieworder' => 1,
]);
}
$this->context->smarty->assign([
'request' => $row,
'order_link' => $orderLink,
'current_index' => self::$currentIndex,
'token' => $this->token,
'identifier' => $this->identifier,
'back_link' => self::$currentIndex . '&token=' . $this->token,
]);
return $this->module->display(
_PS_MODULE_DIR_ . $this->module->name . '/' . $this->module->name . '.php',
'views/templates/admin/view.tpl'
);
}
}
+2
View File
@@ -0,0 +1,2 @@
<?php
/** Silence is golden. */