110 lines
2.9 KiB
PHP
110 lines
2.9 KiB
PHP
<?php
|
||
/**
|
||
* Carteasy – RevocationHelper
|
||
*
|
||
* Static helpers für Widerruf nach EU-RL 2026/2673.
|
||
* Bewusst ohne DB-State, nur Pure-Functions.
|
||
*
|
||
* @copyright Wlanium / Thomas Bartelt
|
||
* @since 2026-04-19
|
||
*/
|
||
|
||
include_once $_SERVER['DOCUMENT_ROOT'].'/core/config/revocation_config.inc.php';
|
||
|
||
class RevocationHelper {
|
||
|
||
/**
|
||
* Prüft, ob eine Kundengruppe zum elektronischen Widerruf berechtigt ist.
|
||
*/
|
||
public static function is_b2c($customer_group_id) {
|
||
$b2c = unserialize(REVOCATION_B2C_GROUP_IDS);
|
||
return in_array((int)$customer_group_id, $b2c, true);
|
||
}
|
||
|
||
/**
|
||
* Liefert die customer_group_id für eine Bestellung.
|
||
* Bei Gast (customer_id NULL) → REVOCATION_GUEST_GROUP_ID (103).
|
||
* Sonst aus customer-Tabelle gezogen.
|
||
*/
|
||
public static function resolve_group_id($db, $customer_id) {
|
||
if (empty($customer_id)) {
|
||
return REVOCATION_GUEST_GROUP_ID;
|
||
}
|
||
$customer_id = (int)$customer_id;
|
||
$sql = "SELECT customer_group_id FROM customers WHERE id = $customer_id LIMIT 1";
|
||
$result = $db->query($sql);
|
||
if ($result && $result->num_rows > 0) {
|
||
$row = $result->fetch_object();
|
||
return (int)$row->customer_group_id;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* Erzeugt ein kryptographisch zufälliges Token (Hex).
|
||
*/
|
||
public static function generate_token() {
|
||
return bin2hex(random_bytes(REVOCATION_TOKEN_BYTES));
|
||
}
|
||
|
||
/**
|
||
* SHA-256 des Klartext-Tokens. Nur dieser Hash wird persistiert.
|
||
*/
|
||
public static function hash_token($token_plain) {
|
||
return hash('sha256', $token_plain);
|
||
}
|
||
|
||
/**
|
||
* Baut die vollständige Widerrufs-URL für eine Bestell-Mail.
|
||
*/
|
||
public static function build_link($token_plain, $host = null) {
|
||
if ($host === null) {
|
||
$host = (isset($_SERVER['HTTP_HOST']) && $_SERVER['HTTP_HOST'])
|
||
? $_SERVER['HTTP_HOST']
|
||
: 'www.intelectra.de';
|
||
}
|
||
return 'https://' . $host . REVOCATION_LANDING_PATH
|
||
. '?t=' . urlencode($token_plain);
|
||
}
|
||
|
||
/**
|
||
* DSGVO-konforme IP-Anonymisierung.
|
||
* IPv4: letztes Oktett auf 0 (/24)
|
||
* IPv6: letzte 80 Bits auf 0 (/48)
|
||
* Ungültige Eingabe → NULL.
|
||
*/
|
||
public static function anonymize_ip($ip) {
|
||
if (empty($ip)) {
|
||
return null;
|
||
}
|
||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||
$parts = explode('.', $ip);
|
||
$parts[3] = '0';
|
||
return implode('.', $parts);
|
||
}
|
||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||
$bin = inet_pton($ip);
|
||
if ($bin === false) return null;
|
||
// /48: erste 6 Bytes behalten, Rest auf 0
|
||
$masked = substr($bin, 0, 6) . str_repeat("\0", 10);
|
||
return inet_ntop($masked);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Berechnet den expires_at-Timestamp ab einem Basis-Datum.
|
||
* $base_date: 'YYYY-MM-DD' aus orders.order_date, oder null → jetzt.
|
||
*/
|
||
public static function calc_expires_at($base_date = null) {
|
||
$days = (int) REVOCATION_TOKEN_VALIDITY_DAYS;
|
||
if (!empty($base_date)) {
|
||
$ts = strtotime($base_date);
|
||
if ($ts === false) $ts = time();
|
||
} else {
|
||
$ts = time();
|
||
}
|
||
return date('Y-m-d H:i:s', $ts + $days * 86400);
|
||
}
|
||
}
|