Add protected reminder management

This commit is contained in:
2026-08-09 02:29:13 +02:00
parent dc8407767e
commit 81ad4b82c9
4 changed files with 360 additions and 0 deletions
+187
View File
@@ -0,0 +1,187 @@
<?php
class ReminderController
{
public function create(): void
{
require_login();
$habit = Habit::findForUser($this->queryId('habit_id'), current_user_id());
if (!$habit) {
flash('error', 'Der Habit fuer die Erinnerung wurde nicht gefunden.');
redirect('dashboard');
}
View::render('reminders/form', [
'habit' => $habit,
'reminder' => ['habit_id' => $habit['id'], 'is_active' => 1],
'errors' => [],
]);
}
public function store(): void
{
require_login();
verify_csrf_token();
$habit = Habit::findForUser($this->postId('habit_id'), current_user_id());
if (!$habit) {
flash('error', 'Der Habit fuer die Erinnerung wurde nicht gefunden.');
redirect('dashboard');
}
$data = $this->validatedData();
if (!empty($data['errors'])) {
View::render('reminders/form', [
'habit' => $habit,
'reminder' => array_merge($_POST, $data),
'errors' => $data['errors'],
]);
return;
}
if (!Reminder::createForHabit((int) $habit['id'], current_user_id(), $data)) {
flash('error', 'Die Erinnerung konnte nicht gespeichert werden.');
redirect('dashboard');
}
flash('success', 'Erinnerung wurde erstellt.');
redirect('dashboard');
}
public function edit(): void
{
require_login();
$reminder = Reminder::findForUser($this->queryId('id'), current_user_id());
if (!$reminder) {
flash('error', 'Erinnerung wurde nicht gefunden.');
redirect('dashboard');
}
$habit = Habit::findForUser((int) $reminder['habit_id'], current_user_id());
View::render('reminders/form', [
'habit' => $habit,
'reminder' => $reminder,
'errors' => [],
]);
}
public function update(): void
{
require_login();
verify_csrf_token();
$reminder = Reminder::findForUser($this->postId('id'), current_user_id());
if (!$reminder) {
flash('error', 'Erinnerung wurde nicht gefunden.');
redirect('dashboard');
}
$habit = Habit::findForUser((int) $reminder['habit_id'], current_user_id());
$data = $this->validatedData();
if (!empty($data['errors'])) {
View::render('reminders/form', [
'habit' => $habit,
'reminder' => array_merge($reminder, $data),
'errors' => $data['errors'],
]);
return;
}
if (!Reminder::update((int) $reminder['id'], current_user_id(), $data)) {
flash('error', 'Die Erinnerung konnte nicht gespeichert werden.');
redirect('dashboard');
}
flash('success', 'Erinnerung wurde gespeichert.');
redirect('dashboard');
}
public function delete(): void
{
require_login();
verify_csrf_token();
$reminder = Reminder::findForUser($this->postId('id'), current_user_id());
if (!$reminder || !Reminder::delete((int) $reminder['id'], current_user_id())) {
flash('error', 'Die Erinnerung konnte nicht geloescht werden.');
redirect('dashboard');
}
flash('success', 'Erinnerung wurde geloescht.');
redirect('dashboard');
}
public function toggle(): void
{
require_login();
verify_csrf_token();
$reminder = Reminder::findForUser($this->postId('id'), current_user_id());
if (!$reminder || !Reminder::toggle((int) $reminder['id'], current_user_id())) {
flash('error', 'Der Status der Erinnerung konnte nicht geaendert werden.');
redirect('dashboard');
}
flash('success', $reminder['is_active'] ? 'Erinnerung pausiert.' : 'Erinnerung aktiviert.');
redirect('dashboard');
}
private function validatedData(): array
{
$time = $this->postString('reminder_time');
$weekdayValue = $this->postString('weekday');
$activeValue = $this->postString('is_active');
$errors = [];
if (!preg_match('/^(?:[01]\\d|2[0-3]):[0-5]\\d$/', $time)) {
$errors['reminder_time'] = 'Bitte gib eine Uhrzeit im Format HH:MM ein.';
}
if ($weekdayValue !== '' && !preg_match('/^[1-7]$/', $weekdayValue)) {
$errors['weekday'] = 'Bitte waehle einen gueltigen Wochentag aus.';
}
if (!in_array($activeValue, ['0', '1'], true)) {
$errors['is_active'] = 'Bitte waehle einen gueltigen Status aus.';
}
return [
'reminder_time' => $time,
'weekday' => $weekdayValue === '' ? null : (int) $weekdayValue,
'is_active' => $activeValue === '0' ? 0 : 1,
'errors' => $errors,
];
}
private function queryId(string $key): int
{
$value = $_GET[$key] ?? '';
return is_string($value) && preg_match('/^[1-9]\\d*$/', $value) ? (int) $value : 0;
}
private function postId(string $key): int
{
$value = $_POST[$key] ?? '';
return is_string($value) && preg_match('/^[1-9]\\d*$/', $value) ? (int) $value : 0;
}
private function postString(string $key): string
{
$value = $_POST[$key] ?? '';
return is_string($value) ? trim($value) : '';
}
}
+96
View File
@@ -0,0 +1,96 @@
<?php
class Reminder
{
public static function findForUser(int $id, int $userId): ?array
{
$statement = Database::connection()->prepare(
'SELECT r.*, h.title AS habit_title
FROM reminders r
INNER JOIN habits h ON h.id = r.habit_id
WHERE r.id = :id AND h.user_id = :user_id
LIMIT 1'
);
$statement->execute(['id' => $id, 'user_id' => $userId]);
$reminder = $statement->fetch();
return $reminder ?: null;
}
public static function allForUser(int $userId): array
{
$statement = Database::connection()->prepare(
'SELECT r.*, h.title AS habit_title
FROM reminders r
INNER JOIN habits h ON h.id = r.habit_id
WHERE h.user_id = :user_id
ORDER BY h.title ASC, r.is_active DESC, r.weekday ASC, r.reminder_time ASC'
);
$statement->execute(['user_id' => $userId]);
return $statement->fetchAll();
}
public static function createForHabit(int $habitId, int $userId, array $data): bool
{
$statement = Database::connection()->prepare(
'INSERT INTO reminders (habit_id, reminder_time, weekday, is_active)
SELECT id, :reminder_time, :weekday, :is_active
FROM habits
WHERE id = :habit_id AND user_id = :user_id'
);
$statement->execute([
'habit_id' => $habitId,
'user_id' => $userId,
'reminder_time' => $data['reminder_time'],
'weekday' => $data['weekday'],
'is_active' => $data['is_active'],
]);
return $statement->rowCount() === 1;
}
public static function update(int $id, int $userId, array $data): bool
{
$statement = Database::connection()->prepare(
'UPDATE reminders r
INNER JOIN habits h ON h.id = r.habit_id
SET r.reminder_time = :reminder_time,
r.weekday = :weekday,
r.is_active = :is_active
WHERE r.id = :id AND h.user_id = :user_id'
);
return $statement->execute([
'id' => $id,
'user_id' => $userId,
'reminder_time' => $data['reminder_time'],
'weekday' => $data['weekday'],
'is_active' => $data['is_active'],
]);
}
public static function delete(int $id, int $userId): bool
{
$statement = Database::connection()->prepare(
'DELETE r
FROM reminders r
INNER JOIN habits h ON h.id = r.habit_id
WHERE r.id = :id AND h.user_id = :user_id'
);
return $statement->execute(['id' => $id, 'user_id' => $userId]);
}
public static function toggle(int $id, int $userId): bool
{
$statement = Database::connection()->prepare(
'UPDATE reminders r
INNER JOIN habits h ON h.id = r.habit_id
SET r.is_active = IF(r.is_active = 1, 0, 1)
WHERE r.id = :id AND h.user_id = :user_id'
);
return $statement->execute(['id' => $id, 'user_id' => $userId]);
}
}
+69
View File
@@ -0,0 +1,69 @@
<?php
$isEdit = !empty($reminder['id']);
$selectedWeekday = (string) ($reminder['weekday'] ?? '');
$selectedActive = (string) ($reminder['is_active'] ?? '1');
$time = substr((string) ($reminder['reminder_time'] ?? ''), 0, 5);
$weekdays = [
'1' => 'Montag',
'2' => 'Dienstag',
'3' => 'Mittwoch',
'4' => 'Donnerstag',
'5' => 'Freitag',
'6' => 'Samstag',
'7' => 'Sonntag',
];
?>
<section class="form-panel">
<h1><?= $isEdit ? 'Erinnerung bearbeiten' : 'Erinnerung erstellen' ?></h1>
<p class="form-help">Habit: <strong><?= e($habit['title']) ?></strong></p>
<form method="post" action="index.php?route=<?= $isEdit ? 'reminders/update' : 'reminders/store' ?>" class="form">
<?= csrf_field() ?>
<?php if ($isEdit): ?>
<input type="hidden" name="id" value="<?= (int) $reminder['id'] ?>">
<?php else: ?>
<input type="hidden" name="habit_id" value="<?= (int) $habit['id'] ?>">
<?php endif; ?>
<label for="reminder_time">Uhrzeit</label>
<input
id="reminder_time"
name="reminder_time"
type="time"
value="<?= e($time) ?>"
class="<?= !empty($errors['reminder_time']) ? 'input-error' : '' ?>"
required
aria-describedby="reminder-time-error"
>
<?php if (!empty($errors['reminder_time'])): ?>
<small id="reminder-time-error" class="field-error"><?= e($errors['reminder_time']) ?></small>
<?php endif; ?>
<label for="weekday">Wochentag</label>
<select id="weekday" name="weekday" class="<?= !empty($errors['weekday']) ? 'input-error' : '' ?>" aria-describedby="weekday-help weekday-error">
<option value="">Taeglich</option>
<?php foreach ($weekdays as $value => $label): ?>
<option value="<?= $value ?>" <?= $selectedWeekday === $value ? 'selected' : '' ?>><?= $label ?></option>
<?php endforeach; ?>
</select>
<small id="weekday-help">Ohne Auswahl wird die Erinnerung taeglich angezeigt.</small>
<?php if (!empty($errors['weekday'])): ?>
<small id="weekday-error" class="field-error"><?= e($errors['weekday']) ?></small>
<?php endif; ?>
<label for="is_active">Status</label>
<select id="is_active" name="is_active" class="<?= !empty($errors['is_active']) ? 'input-error' : '' ?>" aria-describedby="status-error">
<option value="1" <?= $selectedActive === '1' ? 'selected' : '' ?>>Aktiv</option>
<option value="0" <?= $selectedActive === '0' ? 'selected' : '' ?>>Pausiert</option>
</select>
<?php if (!empty($errors['is_active'])): ?>
<small id="status-error" class="field-error"><?= e($errors['is_active']) ?></small>
<?php endif; ?>
<button type="submit"><?= $isEdit ? 'Speichern' : 'Erinnerung erstellen' ?></button>
</form>
<p><a href="index.php?route=dashboard">Zurueck zum Dashboard</a></p>
</section>
+8
View File
@@ -15,8 +15,10 @@ require __DIR__ . '/../app/Core/View.php';
require __DIR__ . '/../app/Models/User.php';
require __DIR__ . '/../app/Models/Category.php';
require __DIR__ . '/../app/Models/Habit.php';
require __DIR__ . '/../app/Models/Reminder.php';
require __DIR__ . '/../app/Controllers/AuthController.php';
require __DIR__ . '/../app/Controllers/HabitController.php';
require __DIR__ . '/../app/Controllers/ReminderController.php';
$route = $_GET['route'] ?? 'dashboard';
$method = $_SERVER['REQUEST_METHOD'];
@@ -28,6 +30,8 @@ $routes = [
'dashboard' => [HabitController::class, 'dashboard'],
'habits/create' => [HabitController::class, 'create'],
'habits/edit' => [HabitController::class, 'edit'],
'reminders/create' => [ReminderController::class, 'create'],
'reminders/edit' => [ReminderController::class, 'edit'],
],
'POST' => [
'login' => [AuthController::class, 'login'],
@@ -37,6 +41,10 @@ $routes = [
'habits/update' => [HabitController::class, 'update'],
'habits/delete' => [HabitController::class, 'delete'],
'habits/toggle' => [HabitController::class, 'toggle'],
'reminders/store' => [ReminderController::class, 'store'],
'reminders/update' => [ReminderController::class, 'update'],
'reminders/delete' => [ReminderController::class, 'delete'],
'reminders/toggle' => [ReminderController::class, 'toggle'],
],
];