design anpassung

This commit is contained in:
2026-06-23 00:06:31 +02:00
parent 1891ec0a51
commit cbea23083d
30 changed files with 785 additions and 547 deletions
+7 -7
View File
@@ -49,7 +49,7 @@ final class PlatformController
}
$this->view->render('home/index', [
'title' => 'Kaffeekasse fuer Teams, Vereine und Coworking',
'title' => 'Kaffeeliste für Teams, Vereine und Büroküchen',
'setupComplete' => $setup->isInstalled(),
'stats' => $stats,
'allowPublicRegistration' => $this->config['allow_public_registration'] ?? false,
@@ -77,7 +77,7 @@ final class PlatformController
try {
$setup->install($_POST);
$this->session->flash('success', 'Die Installation wurde abgeschlossen. Jetzt kannst du dich als Plattform-Admin anmelden.');
$this->session->flash('success', 'Die Installation wurde abgeschlossen. Sie können sich jetzt als Plattform-Administrator anmelden.');
Response::redirect('/admin/login');
} catch (\Throwable $throwable) {
remember_old_input($_POST);
@@ -93,12 +93,12 @@ final class PlatformController
}
if (!$this->allowsPublicRegistration()) {
$this->session->flash('error', 'Die offene Registrierung ist derzeit deaktiviert. Fuer einen Pilotzugang bitte direkt den Betreiber kontaktieren.');
$this->session->flash('error', 'Die offene Registrierung ist derzeit deaktiviert. Bitte kontaktieren Sie den Betreiber.');
Response::redirect('/');
}
$this->view->render('home/register', [
'title' => 'Mandant registrieren',
'title' => 'Kaffeeliste registrieren',
]);
}
@@ -122,7 +122,7 @@ final class PlatformController
$service = new TenantRegistrationService($pdo, $audit);
$slug = $service->register($_POST);
$this->clearScopes($rateLimiter, 'registration', $scopes);
$this->session->flash('success', 'Dein Mandant wurde angelegt. Du kannst dich jetzt anmelden.');
$this->session->flash('success', 'Die Kaffeeliste wurde angelegt. Sie können sich jetzt anmelden.');
Response::redirect('/t/' . rawurlencode($slug) . '/login');
} catch (\Throwable $throwable) {
if ($this->database->isConfigured()) {
@@ -141,7 +141,7 @@ final class PlatformController
public function adminLoginForm(): void
{
$this->view->render('admin/login', [
'title' => 'Plattform-Login',
'title' => 'Kaffeelisten verwalten',
]);
}
@@ -257,7 +257,7 @@ final class PlatformController
$passwordConfirm = (string) ($_POST['password_confirm'] ?? '');
if ($password !== $passwordConfirm) {
throw new RuntimeException('Die Passwort-Bestaetigung stimmt nicht ueberein.');
throw new RuntimeException('Die Passwort-Bestätigung stimmt nicht überein.');
}
$pdo = $this->database->pdo();
+34 -31
View File
@@ -33,7 +33,7 @@ final class TenantController
$tenant = $this->tenant($tenantSlug);
$this->view->render('tenant/login', [
'title' => 'Tenant-Login',
'title' => 'Kaffeelisten-Login',
'tenant' => $tenant,
]);
}
@@ -67,11 +67,11 @@ final class TenantController
if (!$this->isTenantManager($authState) && !$this->selfServiceAllowed((int) $tenant['id'])) {
$auth->logout();
throw new RuntimeException('Die digitale Selbstbuchung ist fuer diesen Mandanten derzeit deaktiviert.');
throw new RuntimeException('Die Buchung ist für diese Kaffeeliste derzeit deaktiviert.');
}
$this->clearRateLimitScopes($rateLimiter, $scopes);
$this->session->flash('success', 'Willkommen zur digitalen Kaffeeliste.');
$this->session->flash('success', 'Willkommen in Ihrer Kaffeeliste.');
Response::redirect(tenant_url($tenantSlug));
} catch (\Throwable $throwable) {
remember_old_input($_POST);
@@ -86,7 +86,7 @@ final class TenantController
$pdo = $this->database->pdo();
$auth = new Auth($pdo, $this->session);
$auth->logout();
$this->session->flash('success', 'Du wurdest abgemeldet.');
$this->session->flash('success', 'Sie wurden abgemeldet.');
Response::redirect(tenant_url($tenantSlug, 'login'));
}
@@ -125,6 +125,7 @@ final class TenantController
m.display_name AS member_name,
p.name AS product_name,
s.name AS source_name,
s.code AS source_code,
CASE WHEN reversal.id IS NULL THEN 0 ELSE 1 END AS is_reversed
FROM consumption_events c
INNER JOIN members m ON m.id = c.member_id
@@ -159,7 +160,7 @@ final class TenantController
$balancesStatement->execute(['tenant_id' => $tenant['id']]);
} else {
if ($currentMember === null) {
throw new RuntimeException('Fuer diesen Benutzer ist kein aktives Mitglied im Mandanten hinterlegt.');
throw new RuntimeException('Für diesen Benutzer ist kein aktives Mitglied in der Kaffeeliste hinterlegt.');
}
$summaryStatement = $pdo->prepare(
@@ -193,6 +194,7 @@ final class TenantController
m.display_name AS member_name,
p.name AS product_name,
s.name AS source_name,
s.code AS source_code,
CASE WHEN reversal.id IS NULL THEN 0 ELSE 1 END AS is_reversed
FROM consumption_events c
INNER JOIN members m ON m.id = c.member_id
@@ -234,7 +236,7 @@ final class TenantController
}
$this->view->render('tenant/dashboard', [
'title' => 'Dashboard',
'title' => 'Kaffeekasse',
'tenant' => $tenant,
'authState' => $authState,
'summary' => $summary,
@@ -271,11 +273,11 @@ final class TenantController
if ($email !== '') {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new RuntimeException('Die E-Mail-Adresse ist ungueltig.');
throw new RuntimeException('Die E-Mail-Adresse ist ungültig.');
}
if ($loginPassword === '' || mb_strlen($loginPassword) < 12) {
throw new RuntimeException('Fuer Login-faehige Mitglieder ist ein Passwort mit mindestens 12 Zeichen noetig.');
throw new RuntimeException('Für Mitglieder mit Login ist ein Passwort mit mindestens 12 Zeichen nötig.');
}
$existingUserStatement = $pdo->prepare('SELECT id FROM users WHERE email = :email LIMIT 1');
@@ -319,7 +321,7 @@ final class TenantController
]);
(new AuditService($pdo))->log((int) $tenant['id'], (int) $authState['user_id'], 'member.created', 'member', (int) $pdo->lastInsertId(), 'success');
$this->session->flash('success', 'Das Mitglied wurde gespeichert.');
$this->session->flash('success', 'Das Mitglied wurde angelegt.');
} catch (\Throwable $throwable) {
remember_old_input($_POST);
$this->session->flash('error', $throwable->getMessage());
@@ -406,7 +408,7 @@ final class TenantController
$productsStatement->execute(['tenant_id' => $tenant['id']]);
$this->view->render('tenant/products', [
'title' => 'Produkte',
'title' => 'Produkte und Preise',
'tenant' => $tenant,
'authState' => $authState,
'products' => $productsStatement->fetchAll(),
@@ -429,7 +431,7 @@ final class TenantController
if ($action === 'reverse_booking') {
if (!$isManager) {
throw new RuntimeException('Nur Admins duerfen Buchungen stornieren.');
throw new RuntimeException('Nur Administratorinnen und Administratoren dürfen Buchungen stornieren.');
}
$service->reverseLedgerEntry(
@@ -453,7 +455,7 @@ final class TenantController
(float) ($_POST['quantity'] ?? 1),
trim((string) ($_POST['notes'] ?? ''))
);
$this->session->flash('success', 'Die Buchung wurde gespeichert.');
$this->session->flash('success', 'Die Kaffeebuchung wurde gespeichert.');
}
} catch (\Throwable $throwable) {
remember_old_input($_POST);
@@ -472,6 +474,7 @@ final class TenantController
m.display_name AS member_name,
p.name AS product_name,
s.name AS source_name,
s.code AS source_code,
le.id AS ledger_entry_id,
CASE WHEN reversal.id IS NULL THEN 0 ELSE 1 END AS is_reversed
FROM consumption_events c
@@ -492,7 +495,7 @@ final class TenantController
if (!$isManager) {
if ($currentMember === null) {
throw new RuntimeException('Fuer diesen Benutzer ist kein aktives Mitglied im Mandanten hinterlegt.');
throw new RuntimeException('Für diesen Benutzer ist kein aktives Mitglied in der Kaffeeliste hinterlegt.');
}
$sql .= ' AND c.member_id = :member_id';
@@ -505,7 +508,7 @@ final class TenantController
$bookingsStatement->execute($params);
$this->view->render('tenant/bookings', [
'title' => 'Digitale Buchungen',
'title' => 'Kaffee buchen',
'tenant' => $tenant,
'authState' => $authState,
'members' => $members,
@@ -550,7 +553,7 @@ final class TenantController
}
} else {
if ($currentMember === null) {
throw new RuntimeException('Fuer diesen Benutzer ist kein aktives Mitglied im Mandanten hinterlegt.');
throw new RuntimeException('Für diesen Benutzer ist kein aktives Mitglied in der Kaffeeliste hinterlegt.');
}
$memberId = (int) $currentMember['id'];
@@ -614,7 +617,7 @@ final class TenantController
(int) $authState['user_id'],
trim((string) ($_POST['notes'] ?? ''))
);
$this->session->flash('success', 'Die Einzahlung wurde gebucht.');
$this->session->flash('success', 'Die Einzahlung wurde gutgeschrieben.');
}
} catch (\Throwable $throwable) {
remember_old_input($_POST);
@@ -645,7 +648,7 @@ final class TenantController
$paymentsStatement->execute(['tenant_id' => $tenant['id']]);
$this->view->render('tenant/payments', [
'title' => 'Einzahlungen',
'title' => 'Geld einzahlen',
'tenant' => $tenant,
'authState' => $authState,
'members' => $members,
@@ -688,10 +691,10 @@ final class TenantController
trim((string) ($_POST['note'] ?? '')) ?: null,
(int) $authState['user_id']
);
$this->session->flash('success', 'Die Papierlisten-Position wurde gespeichert.');
$this->session->flash('success', 'Der Eintrag aus der Papierliste wurde gespeichert.');
} elseif ($action === 'post_sheet') {
$service->postPaperSheet((int) $tenant['id'], (int) ($_POST['paper_sheet_id'] ?? 0), (int) $authState['user_id']);
$this->session->flash('success', 'Die Papierliste wurde ins Ledger uebernommen.');
$this->session->flash('success', 'Die Papierliste wurde als Buchungen übernommen.');
} elseif ($action === 'reverse_sheet') {
$service->reversePaperSheet(
(int) $tenant['id'],
@@ -699,7 +702,7 @@ final class TenantController
(int) $authState['user_id'],
trim((string) ($_POST['reason'] ?? '')) ?: null
);
$this->session->flash('success', 'Die verbuchte Papierliste wurde storniert.');
$this->session->flash('success', 'Die übernommene Papierliste wurde storniert.');
}
} catch (\Throwable $throwable) {
remember_old_input($_POST);
@@ -745,7 +748,7 @@ final class TenantController
$linesStatement->execute(['tenant_id' => $tenant['id']]);
$this->view->render('tenant/paper-sheets', [
'title' => 'Papierlisten',
'title' => 'Papierliste nachtragen',
'tenant' => $tenant,
'authState' => $authState,
'members' => $members,
@@ -775,7 +778,7 @@ final class TenantController
trim((string) ($_POST['location_label'] ?? '')) ?: null,
(int) $authState['user_id']
);
$this->session->flash('success', 'Das RFID-Geraet wurde vorbereitet. Token: ' . $token);
$this->session->flash('success', 'Der Kartenleser wurde angelegt. Das Geräte-Token wird einmalig angezeigt: ' . $token);
} elseif ($action === 'assign_tag') {
$service->assignTag(
(int) $tenant['id'],
@@ -784,7 +787,7 @@ final class TenantController
trim((string) ($_POST['label'] ?? '')) ?: null,
(int) $authState['user_id']
);
$this->session->flash('success', 'Die RFID-Karte wurde zugewiesen.');
$this->session->flash('success', 'Die Karte wurde zugewiesen.');
}
} catch (\Throwable $throwable) {
remember_old_input($_POST);
@@ -816,7 +819,7 @@ final class TenantController
$events->execute(['tenant_id' => $tenant['id']]);
$this->view->render('tenant/rfid', [
'title' => 'RFID-Roadmap',
'title' => 'Kartenleser einrichten',
'tenant' => $tenant,
'authState' => $authState,
'members' => $members,
@@ -862,7 +865,7 @@ final class TenantController
$tenant = (new TenantContext($pdo))->bySlug($tenantSlug);
if (!$tenant || $tenant['status'] === 'suspended') {
throw new RuntimeException('Der Mandant ist nicht verfuegbar.');
throw new RuntimeException('Die Kaffeeliste ist nicht verfügbar.');
}
return $tenant;
@@ -880,7 +883,7 @@ final class TenantController
}
if ($roles !== [] && !$auth->requireRole($roles)) {
$this->session->flash('error', 'Dafuer fehlen die passenden Rechte.');
$this->session->flash('error', 'Dafür fehlen die passenden Rechte.');
Response::redirect(tenant_url($tenantSlug));
}
@@ -893,7 +896,7 @@ final class TenantController
if (!$this->isTenantManager($authState) && !$this->selfServiceAllowed((int) $tenant['id'])) {
$auth->logout();
$this->session->flash('error', 'Die digitale Selbstbuchung ist fuer diesen Mandanten derzeit deaktiviert.');
$this->session->flash('error', 'Die Buchung ist für diese Kaffeeliste derzeit deaktiviert.');
Response::redirect(tenant_url($tenantSlug, 'login'));
}
@@ -968,18 +971,18 @@ final class TenantController
): int {
if ($this->isTenantManager($authState)) {
if ($postedMemberId <= 0) {
throw new RuntimeException('Bitte ein Mitglied fuer die Buchung auswaehlen.');
throw new RuntimeException('Bitte ein Mitglied für die Buchung auswählen.');
}
return $this->assertMemberIdBelongsToTenant($tenantId, $postedMemberId);
}
if ($currentMember === null) {
throw new RuntimeException('Fuer dieses Konto ist kein buchungsfaehiges Mitglied hinterlegt.');
throw new RuntimeException('Für dieses Konto ist kein buchungsfähiges Mitglied hinterlegt.');
}
if ($postedMemberId > 0 && $postedMemberId !== (int) $currentMember['id']) {
throw new RuntimeException('Du darfst nur fuer dein eigenes Mitglied buchen.');
throw new RuntimeException('Sie dürfen nur für Ihr eigenes Mitglied buchen.');
}
return (int) $currentMember['id'];
@@ -1001,7 +1004,7 @@ final class TenantController
]);
if (!$statement->fetchColumn()) {
throw new RuntimeException('Das angegebene Mitglied gehoert nicht zu diesem Mandanten.');
throw new RuntimeException('Das angegebene Mitglied gehört nicht zu dieser Kaffeeliste.');
}
return $memberId;
+4 -4
View File
@@ -271,7 +271,7 @@ final class ExportService
private function assertTenantId(int $tenantId): void
{
if ($tenantId <= 0) {
throw new RuntimeException('Die Tenant-ID muss groesser als 0 sein.');
throw new RuntimeException('Die Standort-ID muss größer als 0 sein.');
}
}
@@ -283,7 +283,7 @@ final class ExportService
private function openCsvOutput(string $filename)
{
if (headers_sent($file, $line)) {
throw new RuntimeException(sprintf('CSV-Header koennen nicht mehr gesendet werden (%s:%d).', $file, $line));
throw new RuntimeException(sprintf('Export-Header können nicht mehr gesendet werden (%s:%d).', $file, $line));
}
$safeFilename = $this->sanitizeFilename($filename);
@@ -299,7 +299,7 @@ final class ExportService
$handle = fopen('php://output', 'wb');
if ($handle === false) {
throw new RuntimeException('php://output konnte nicht geoeffnet werden.');
throw new RuntimeException('Die Exportausgabe konnte nicht geöffnet werden.');
}
fwrite($handle, "\xEF\xBB\xBF");
@@ -330,7 +330,7 @@ final class ExportService
}, $row);
if (fputcsv($handle, $values, self::CSV_DELIMITER) === false) {
throw new RuntimeException('Die CSV-Zeile konnte nicht geschrieben werden.');
throw new RuntimeException('Die Exportzeile konnte nicht geschrieben werden.');
}
}
+15 -15
View File
@@ -278,7 +278,7 @@ final class LedgerService
$this->assertTenantMembership($tenantId, $createdByUserId);
if (trim($title) === '') {
throw new RuntimeException('Bitte einen Titel fuer die Papierliste angeben.');
throw new RuntimeException('Bitte einen Titel für die Liste angeben.');
}
$statement = $this->pdo->prepare(
@@ -345,11 +345,11 @@ final class LedgerService
$paperSheet = $sheet->fetch();
if (!$paperSheet) {
throw new RuntimeException('Die Papierliste wurde nicht gefunden.');
throw new RuntimeException('Die Liste wurde nicht gefunden.');
}
if ($paperSheet['status'] !== 'draft') {
throw new RuntimeException('Nur Entwuerfe koennen erweitert werden.');
throw new RuntimeException('Nur Entwürfe können erweitert werden.');
}
$statement = $this->pdo->prepare(
@@ -402,11 +402,11 @@ final class LedgerService
$sheet = $sheetStatement->fetch();
if (!$sheet) {
throw new RuntimeException('Die Papierliste wurde nicht gefunden.');
throw new RuntimeException('Die Liste wurde nicht gefunden.');
}
if ($sheet['status'] !== 'draft') {
throw new RuntimeException('Die Papierliste wurde bereits verbucht.');
throw new RuntimeException('Die Liste wurde bereits verbucht.');
}
$linesStatement = $this->pdo->prepare(
@@ -419,7 +419,7 @@ final class LedgerService
$lines = $linesStatement->fetchAll();
if ($lines === []) {
throw new RuntimeException('Die Papierliste enthaelt noch keine Positionen.');
throw new RuntimeException('Die Liste enthält noch keine Positionen.');
}
foreach ($lines as $line) {
@@ -431,7 +431,7 @@ final class LedgerService
$line['effective_at'] ?: gmdate('Y-m-d H:i:s'),
$actorUserId,
(float) $line['quantity'],
$line['note'] ?: 'Papierliste',
$line['note'] ?: 'Liste',
(int) $line['id'],
'sheet:' . $sheetId
);
@@ -472,7 +472,7 @@ final class LedgerService
$lines = $linesStatement->fetchAll();
if ($lines === []) {
throw new RuntimeException('Es wurden keine Ledger-Zeilen fuer den Vorgang gefunden.');
throw new RuntimeException('Es wurden keine Buchungszeilen für den Vorgang gefunden.');
}
$description = 'Storno: ' . $entry['description'];
@@ -583,7 +583,7 @@ final class LedgerService
$entryIds = $statement->fetchAll(PDO::FETCH_COLUMN);
if ($entryIds === []) {
throw new RuntimeException('Zu dieser Papierliste wurden keine verbuchten Eintraege gefunden.');
throw new RuntimeException('Zu dieser Liste wurden keine verbuchten Einträge gefunden.');
}
$reversed = 0;
@@ -597,13 +597,13 @@ final class LedgerService
$tenantId,
(int) $entryId,
$actorUserId,
$reason !== null && trim($reason) !== '' ? $reason : 'Papierliste #' . $sheetId
$reason !== null && trim($reason) !== '' ? $reason : 'Liste #' . $sheetId
);
$reversed++;
}
if ($reversed === 0) {
throw new RuntimeException('Diese Papierliste wurde bereits vollstaendig storniert.');
throw new RuntimeException('Diese Liste wurde bereits vollständig storniert.');
}
$this->audit->log($tenantId, $actorUserId, 'paper_sheet.reversed', 'paper_sheet', $sheetId, 'success', [
@@ -633,7 +633,7 @@ final class LedgerService
$price = $statement->fetchColumn();
if ($price === false) {
throw new RuntimeException('Fuer das Produkt ist kein aktiver Preis hinterlegt.');
throw new RuntimeException('Für das Produkt ist kein aktiver Preis hinterlegt.');
}
return (int) $price;
@@ -668,7 +668,7 @@ final class LedgerService
]);
if (!$statement->fetchColumn()) {
throw new RuntimeException('Das gewaehlte Mitglied gehoert nicht zu diesem Mandanten.');
throw new RuntimeException('Das gewählte Mitglied gehört nicht zu diesem Standort.');
}
}
@@ -683,7 +683,7 @@ final class LedgerService
]);
if (!$statement->fetchColumn()) {
throw new RuntimeException('Das gewaehlte Produkt gehoert nicht zu diesem Mandanten.');
throw new RuntimeException('Das gewählte Produkt gehört nicht zu dieser Kaffeeliste.');
}
}
@@ -703,7 +703,7 @@ final class LedgerService
]);
if (!$statement->fetchColumn()) {
throw new RuntimeException('Der Benutzer ist fuer diesen Mandanten nicht aktiv.');
throw new RuntimeException('Der Benutzer ist für diesen Standort nicht aktiv.');
}
}
+3 -3
View File
@@ -20,11 +20,11 @@ final class MailerService
$textBody = trim($textBody);
if ($to === '' || !filter_var($to, FILTER_VALIDATE_EMAIL)) {
throw new RuntimeException('Die Zieladresse fuer den Mailversand ist ungueltig.');
throw new RuntimeException('Die Zieladresse für den Mailversand ist ungültig.');
}
if ($subject === '' || $textBody === '') {
throw new RuntimeException('Betreff und Inhalt fuer den Mailversand sind erforderlich.');
throw new RuntimeException('Betreff und Inhalt für den Mailversand sind erforderlich.');
}
$subjectLine = $this->encodeSubject($this->sanitizeHeaderValue($this->subjectPrefix() . $subject));
@@ -61,7 +61,7 @@ final class MailerService
'MIME-Version: 1.0',
'Content-Type: text/plain; charset=UTF-8',
'Content-Transfer-Encoding: 8bit',
'X-Mailer: Kaffeeliste-Neustart',
'X-Mailer: Kaffeeliste',
];
$from = trim((string) ($this->config['from'] ?? ''));
+11 -11
View File
@@ -40,13 +40,13 @@ final class PasswordResetService
public function createTokenForUser(int $userId, array $context = []): array
{
if ($userId <= 0) {
throw new RuntimeException('Der Benutzer fuer den Passwort-Reset ist ungueltig.');
throw new RuntimeException('Der Benutzer für den Passwortwechsel ist ungültig.');
}
$user = $this->findUserById($userId);
if (!$user) {
throw new RuntimeException('Der Benutzer fuer den Passwort-Reset wurde nicht gefunden.');
throw new RuntimeException('Der Benutzer für den Passwortwechsel wurde nicht gefunden.');
}
$nowTs = time();
@@ -204,7 +204,7 @@ final class PasswordResetService
$newPassword = (string) $newPassword;
if ($token === '') {
throw new RuntimeException('Der Reset-Link ist ungueltig oder abgelaufen.');
throw new RuntimeException('Der Link ist ungültig oder abgelaufen.');
}
if (mb_strlen($newPassword) < 12) {
@@ -229,7 +229,7 @@ final class PasswordResetService
$resetToken = $statement->fetch();
if (!$resetToken) {
throw new RuntimeException('Der Reset-Link ist ungueltig oder abgelaufen.');
throw new RuntimeException('Der Link ist ungültig oder abgelaufen.');
}
$expiresAtTs = $this->timestampFromDatabase($resetToken['expires_at']);
@@ -239,7 +239,7 @@ final class PasswordResetService
|| $expiresAtTs === null
|| $expiresAtTs < $nowTs
) {
throw new RuntimeException('Der Reset-Link ist ungueltig oder abgelaufen.');
throw new RuntimeException('Der Link ist ungültig oder abgelaufen.');
}
$updateUserStatement = $this->pdo->prepare(
@@ -314,14 +314,14 @@ final class PasswordResetService
private function sendResetMail(array $token): void
{
$name = trim((string) ($token['full_name'] ?? ''));
$greeting = $name !== '' ? 'Hallo ' . $name . ',' : 'Hallo,';
$greeting = $name !== '' ? 'Guten Tag ' . $name . ',' : 'Guten Tag,';
$body = implode("\n\n", [
$greeting,
'fuer dein Konto wurde ein Passwort-Reset angefragt.',
'Bitte nutze diesen Link, um ein neues Passwort zu setzen:',
'für Ihr Konto wurde ein Passwortwechsel angefragt.',
'Bitte nutzen Sie diesen Link, um ein neues Passwort zu setzen:',
(string) $token['reset_url'],
'Der Link ist gueltig bis ' . (string) $token['expires_at'] . ' UTC.',
'Falls du den Reset nicht angefragt hast, kannst du diese Nachricht ignorieren.',
'Der Link ist gültig bis ' . (string) $token['expires_at'] . '.',
'Falls Sie den Passwortwechsel nicht angefragt haben, können Sie diese Nachricht ignorieren.',
]);
$this->mailer->send(
@@ -410,7 +410,7 @@ final class PasswordResetService
{
$subject = trim((string) ($this->config['subject'] ?? ''));
return $subject !== '' ? $subject : 'Passwort zuruecksetzen';
return $subject !== '' ? $subject : 'Passwort zurücksetzen';
}
private function ttlMinutes(): int
+4 -4
View File
@@ -16,7 +16,7 @@ final class RfidService
public function createDevice(int $tenantId, string $name, ?string $location, int $actorUserId): string
{
if (trim($name) === '') {
throw new RuntimeException('Bitte einen Geraetenamen angeben.');
throw new RuntimeException('Bitte einen Namen für den Kartenleser angeben.');
}
$token = bin2hex(random_bytes(20));
@@ -52,7 +52,7 @@ final class RfidService
]);
if (!$memberStatement->fetchColumn()) {
throw new RuntimeException('Das ausgewaehlte Mitglied ist fuer RFID nicht verfuegbar.');
throw new RuntimeException('Das ausgewählte Mitglied ist für Karten nicht verfügbar.');
}
$statement = $this->pdo->prepare(
@@ -89,11 +89,11 @@ final class RfidService
$device = $deviceStatement->fetch();
if (!$device) {
throw new RuntimeException('Das RFID-Geraet ist unbekannt.');
throw new RuntimeException('Der Kartenleser ist unbekannt.');
}
if (($device['status'] ?? '') !== 'active') {
throw new RuntimeException('Das RFID-Geraet ist nicht aktiv.');
throw new RuntimeException('Der Kartenleser ist nicht aktiv.');
}
$status = 'received';
+6 -6
View File
@@ -31,12 +31,12 @@ final class SetupService
foreach ($required as $field) {
if (trim((string) ($input[$field] ?? '')) === '') {
throw new RuntimeException('Bitte alle Pflichtfelder ausfuellen.');
throw new RuntimeException('Bitte alle Pflichtfelder ausfüllen.');
}
}
if (!filter_var($input['admin_email'], FILTER_VALIDATE_EMAIL)) {
throw new RuntimeException('Die Admin-E-Mail ist ungueltig.');
throw new RuntimeException('Die Admin-E-Mail ist ungültig.');
}
if (mb_strlen($input['admin_password']) < 12) {
@@ -171,7 +171,7 @@ final class SetupService
$rfidSecret = bin2hex(random_bytes(24));
$lines = [
'APP_NAME="Kaffeekasse SaaS"',
'APP_NAME="Kaffeeliste"',
'APP_ENV=production',
'APP_DEBUG=0',
'APP_URL=' . $appUrl,
@@ -206,14 +206,14 @@ final class SetupService
'',
'MAIL_FROM=' . $mailFrom,
'MAIL_REPLY_TO=' . $mailFrom,
'MAIL_SUBJECT_PREFIX="[Kaffeekasse] "',
'MAIL_SUBJECT_PREFIX="[Kaffeeliste] "',
'MAIL_LOG_FALLBACK=1',
'MAIL_LOG_FILE=storage/logs/mail.log',
'',
'PASSWORD_RESET_URL=' . $appUrl . '/reset-password?token={{token}}',
'PASSWORD_RESET_TTL_MINUTES=60',
'PASSWORD_RESET_MAX_ACTIVE_TOKENS=3',
'PASSWORD_RESET_SUBJECT="Passwort zuruecksetzen"',
'PASSWORD_RESET_SUBJECT="Passwort zurücksetzen"',
'',
'RATE_LIMIT_LOGIN_MAX_ATTEMPTS=5',
'RATE_LIMIT_LOGIN_WINDOW_SECONDS=900',
@@ -242,7 +242,7 @@ final class SetupService
}
if (!filter_var($mailFrom, FILTER_VALIDATE_EMAIL)) {
throw new RuntimeException('Die Mail-Absenderadresse ist ungueltig.');
throw new RuntimeException('Die Mail-Absenderadresse ist ungültig.');
}
return $mailFrom;
+6 -6
View File
@@ -28,7 +28,7 @@ final class TenantRegistrationService
}
if (!filter_var($ownerEmail, FILTER_VALIDATE_EMAIL)) {
throw new RuntimeException('Die E-Mail-Adresse ist ungueltig.');
throw new RuntimeException('Die E-Mail-Adresse ist ungültig.');
}
if (mb_strlen($ownerPassword) < 12) {
@@ -52,7 +52,7 @@ final class TenantRegistrationService
$existingUser->execute(['email' => $ownerEmail]);
if ($existingUser->fetchColumn()) {
throw new RuntimeException('Die E-Mail-Adresse ist bereits vorhanden. Fuer weitere Tenants bitte spaeter Einladungen nutzen.');
throw new RuntimeException('Die E-Mail-Adresse ist bereits vorhanden. Bitte verwenden Sie eine andere Adresse.');
}
$now = gmdate('Y-m-d H:i:s');
@@ -131,10 +131,10 @@ final class TenantRegistrationService
);
foreach ([
['digital_self', 'Digitale Selbstbuchung', 'digital'],
['paper_sheet', 'Papierliste / Nacherfassung', 'paper'],
['admin_backoffice', 'Backoffice-Buchung', 'admin'],
['rfid_reader', 'RFID-Geraet', 'rfid'],
['digital_self', 'Selbst gebucht', 'digital'],
['paper_sheet', 'Papierliste / Nachtrag', 'paper'],
['admin_backoffice', 'Nachgetragen durch Verwaltung', 'admin'],
['rfid_reader', 'Kartenleser', 'rfid'],
] as [$code, $sourceName, $channel]) {
$sourceInsert->execute([
'tenant_id' => $tenantId,