216 lines
9.8 KiB
PHP
216 lines
9.8 KiB
PHP
<?php
|
||
require_once __DIR__ . '/../includes/bootstrap.php';
|
||
$staff = require_staff_login();
|
||
$pdo = get_db();
|
||
|
||
$errors = [];
|
||
$organizations = $pdo->query('SELECT * FROM organizations ORDER BY name')->fetchAll();
|
||
|
||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||
verify_csrf();
|
||
$action = $_POST['action'] ?? '';
|
||
|
||
if ($action === 'assign_org' && $staff['role'] === 'admin') {
|
||
$userId = (int) $_POST['user_id'];
|
||
$orgId = $_POST['organization_id'] !== '' ? (int) $_POST['organization_id'] : null;
|
||
$pdo->prepare('UPDATE users SET organization_id = ? WHERE id = ?')->execute([$orgId, $userId]);
|
||
audit_log($staff['id'], "Organisation zugeordnet für Kunde #{$userId}");
|
||
flash('success', 'Organisation zugeordnet.');
|
||
redirect('/staff/customers.php' . ($_SERVER['QUERY_STRING'] ? '?' . $_SERVER['QUERY_STRING'] : ''));
|
||
}
|
||
|
||
if ($action === 'ban_email') {
|
||
$email = trim($_POST['email'] ?? '');
|
||
$matchType = ($_POST['match_type'] ?? 'email') === 'domain' ? 'domain' : 'email';
|
||
$value = $matchType === 'domain' ? (substr(strrchr($email, '@'), 1) ?: $email) : $email;
|
||
if ($value !== '') {
|
||
// Nur einfügen wenn noch nicht vorhanden
|
||
$exists = $pdo->prepare('SELECT COUNT(*) FROM banlist WHERE match_type=? AND LOWER(value)=?');
|
||
$exists->execute([$matchType, strtolower($value)]);
|
||
if (!(int)$exists->fetchColumn()) {
|
||
$pdo->prepare('INSERT INTO banlist (match_type, value, reason, created_by, created_at) VALUES (?,?,?,?,?)')
|
||
->execute([$matchType, strtolower($value), 'Gesperrt aus Kundenverwaltung', $staff['id'], db_now()]);
|
||
audit_log($staff['id'], "Kunde gesperrt: {$value} ({$matchType})");
|
||
flash('success', '"' . $value . '" wurde zur Sperrliste hinzugefuegt.');
|
||
} else {
|
||
flash('info', '"' . $value . '" ist bereits auf der Sperrliste.');
|
||
}
|
||
}
|
||
redirect('/staff/customers.php' . ($_SERVER['QUERY_STRING'] ? '?' . $_SERVER['QUERY_STRING'] : ''));
|
||
}
|
||
|
||
if ($action === 'unban_email') {
|
||
$email = strtolower(trim($_POST['email'] ?? ''));
|
||
$domain = substr(strrchr($email, '@'), 1) ?: '';
|
||
$pdo->prepare('DELETE FROM banlist WHERE (match_type=\'email\' AND LOWER(value)=?) OR (match_type=\'domain\' AND LOWER(value)=?)')
|
||
->execute([$email, $domain]);
|
||
audit_log($staff['id'], "Kundensperrung aufgehoben: {$email}");
|
||
flash('success', "Sperrung aufgehoben fuer: {$email}");
|
||
}
|
||
}
|
||
|
||
$search = trim($_GET['q'] ?? '');
|
||
$like = '%' . $search . '%';
|
||
|
||
// Registrierte Kunden
|
||
$regSql = "SELECT u.*, o.name AS organization_name,
|
||
(SELECT COUNT(*) FROM tickets t WHERE t.user_id = u.id) AS ticket_count,
|
||
(SELECT MAX(t2.requester_ip) FROM tickets t2 WHERE t2.user_id = u.id AND t2.requester_ip IS NOT NULL) AS last_ip
|
||
FROM users u LEFT JOIN organizations o ON o.id = u.organization_id";
|
||
$regParams = [];
|
||
if ($search !== '') {
|
||
$regSql .= ' WHERE u.name LIKE ? OR u.email LIKE ?';
|
||
$regParams = [$like, $like];
|
||
}
|
||
$regSql .= ' ORDER BY u.name LIMIT 200';
|
||
$regStmt = $pdo->prepare($regSql);
|
||
$regStmt->execute($regParams);
|
||
$registered = array_map(fn($c) => array_merge($c, ['is_guest' => false]), $regStmt->fetchAll());
|
||
|
||
// Gast-Kunden
|
||
$guestSql = "SELECT requester_email AS email, MAX(requester_name) AS name,
|
||
COUNT(*) AS ticket_count, MIN(created_at) AS created_at,
|
||
MAX(requester_ip) AS last_ip
|
||
FROM tickets WHERE user_id IS NULL";
|
||
$guestParams = [];
|
||
if ($search !== '') {
|
||
$guestSql .= ' AND (requester_name LIKE ? OR requester_email LIKE ?)';
|
||
$guestParams = [$like, $like];
|
||
}
|
||
$guestSql .= ' GROUP BY requester_email ORDER BY requester_email LIMIT 200';
|
||
$guestStmt = $pdo->prepare($guestSql);
|
||
$guestStmt->execute($guestParams);
|
||
$guests = array_map(fn($g) => array_merge($g, [
|
||
'id' => null, 'organization_id' => null, 'organization_name' => null, 'is_guest' => true,
|
||
]), $guestStmt->fetchAll());
|
||
|
||
$customers = array_slice(
|
||
usort_return(array_merge($registered, $guests), fn($a, $b) => strcasecmp($a['name'] ?? '', $b['name'] ?? '')),
|
||
0, 200
|
||
);
|
||
|
||
// Alle aktuell gesperrten E-Mails/Domains aus der Sperrliste vorausberechnen
|
||
$bannedEmails = $pdo->query("SELECT LOWER(value) FROM banlist WHERE match_type='email'")->fetchAll(PDO::FETCH_COLUMN);
|
||
$bannedDomains = $pdo->query("SELECT LOWER(value) FROM banlist WHERE match_type='domain'")->fetchAll(PDO::FETCH_COLUMN);
|
||
$bannedEmailSet = array_flip($bannedEmails);
|
||
$bannedDomainSet = array_flip($bannedDomains);
|
||
|
||
function is_customer_banned(string $email, array $bannedEmailSet, array $bannedDomainSet): bool
|
||
{
|
||
$email = strtolower($email);
|
||
$domain = substr(strrchr($email, '@'), 1) ?: '';
|
||
return isset($bannedEmailSet[$email]) || isset($bannedDomainSet[$domain]);
|
||
}
|
||
|
||
function usort_return(array $arr, callable $fn): array { usort($arr, $fn); return $arr; }
|
||
|
||
$pageTitle = 'Kunden';
|
||
$activePage = 'customers';
|
||
require __DIR__ . '/_header.php';
|
||
?>
|
||
|
||
<h1>Kunden</h1>
|
||
<p class="muted">Registrierte Kundenkonten und Gast-Anfrager (ohne Konto) aus allen Tickets.</p>
|
||
|
||
<form method="get" class="filters card">
|
||
<div style="flex:2">
|
||
<label>Suche</label>
|
||
<input type="search" name="q" value="<?= e($search) ?>" placeholder="Name oder E-Mail">
|
||
</div>
|
||
<div><button type="submit" class="btn secondary">Suchen</button></div>
|
||
<?php if ($search): ?>
|
||
<div><a href="/staff/customers.php" class="btn secondary">Zurücksetzen</a></div>
|
||
<?php endif; ?>
|
||
</form>
|
||
|
||
<div class="card">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Name</th>
|
||
<th>E-Mail</th>
|
||
<th>IP-Adresse</th>
|
||
<th>Typ</th>
|
||
<th>Organisation</th>
|
||
<th>Tickets</th>
|
||
<th>Seit</th>
|
||
<th>Status</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($customers as $c):
|
||
$banned = is_customer_banned($c['email'], $bannedEmailSet, $bannedDomainSet);
|
||
?>
|
||
<tr style="<?= $banned ? 'opacity:.65' : '' ?>">
|
||
<td style="font-weight:500"><a href="/staff/customer_profile.php?<?= $c['is_guest'] ? 'email=' . urlencode($c['email']) : 'id=' . (int) $c['id'] ?>"><?= e($c['name']) ?></a></td>
|
||
<td><?= e($c['email']) ?></td>
|
||
<td class="muted" style="font-family:var(--font-mono); font-size:.8rem">
|
||
<?= $c['last_ip'] ? e($c['last_ip']) : '<span style="color:var(--muted-lt)">–</span>' ?>
|
||
</td>
|
||
<td>
|
||
<?= $c['is_guest']
|
||
? '<span class="badge" style="background:#64748b">Gast</span>'
|
||
: '<span class="badge" style="background:#0f6f6a">Konto</span>' ?>
|
||
</td>
|
||
<td>
|
||
<?php if ($c['is_guest']): ?>
|
||
<span class="muted" style="font-size:.82rem">– (kein Konto)</span>
|
||
<?php else: ?>
|
||
<form method="post" style="display:flex; gap:6px; align-items:center">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="action" value="assign_org">
|
||
<input type="hidden" name="user_id" value="<?= $c['id'] ?>">
|
||
<select name="organization_id" onchange="this.form.submit()" style="padding:4px 8px; font-size:.82rem">
|
||
<option value="">– keine –</option>
|
||
<?php foreach ($organizations as $o): ?>
|
||
<option value="<?= $o['id'] ?>" <?= $c['organization_id'] == $o['id'] ? 'selected' : '' ?>><?= e($o['name']) ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</form>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td>
|
||
<?php if ((int)$c['ticket_count'] > 0): ?>
|
||
<a href="/staff/tickets.php?q=<?= urlencode($c['email']) ?>" style="font-weight:600"><?= (int)$c['ticket_count'] ?></a>
|
||
<?php else: ?>
|
||
<span class="muted">0</span>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td class="muted" style="white-space:nowrap"><?= format_dt($c['created_at']) ?></td>
|
||
<td>
|
||
<?php if ($banned): ?>
|
||
<span class="badge" style="background:var(--danger)">Gesperrt</span>
|
||
<?php else: ?>
|
||
<span class="badge" style="background:var(--success)">Aktiv</span>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td style="white-space:nowrap">
|
||
<?php if ($banned): ?>
|
||
<form method="post" style="display:inline" onsubmit="return confirm('<?= e(addslashes($c['email'])) ?> wirklich aufheben?');">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="action" value="unban_email">
|
||
<input type="hidden" name="email" value="<?= e($c['email']) ?>">
|
||
<button type="submit" class="btn secondary" style="font-size:.78rem; padding:5px 10px; color:var(--success); border-color:var(--success)">✓ Entsperren</button>
|
||
</form>
|
||
<?php else: ?>
|
||
<form method="post" style="display:inline" onsubmit="return confirm('<?= e(addslashes($c['name'])) ?>" sperren? Zukünftige Tickets von <?= e(addslashes($c['email'])) ?> werden abgewiesen.');">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="action" value="ban_email">
|
||
<input type="hidden" name="email" value="<?= e($c['email']) ?>">
|
||
<input type="hidden" name="match_type" value="email">
|
||
<button type="submit" class="btn secondary" style="font-size:.78rem; padding:5px 10px; color:var(--danger); border-color:var(--danger)">🚫 Sperren</button>
|
||
</form>
|
||
<?php endif; ?>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
<?php if (!$customers): ?>
|
||
<tr><td colspan="9" class="muted" style="text-align:center; padding:30px">Keine Kunden gefunden.</td></tr>
|
||
<?php endif; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<?php require __DIR__ . '/_footer.php'; ?>
|