From 81ad4b82c9c807ab221f81f3fb4ccae8fbf3a075 Mon Sep 17 00:00:00 2001 From: PBBFA24AAL Date: Sun, 9 Aug 2026 02:29:13 +0200 Subject: [PATCH] Add protected reminder management --- app/Controllers/ReminderController.php | 187 +++++++++++++++++++++++++ app/Models/Reminder.php | 96 +++++++++++++ app/Views/reminders/form.php | 69 +++++++++ public/index.php | 8 ++ 4 files changed, 360 insertions(+) create mode 100644 app/Controllers/ReminderController.php create mode 100644 app/Models/Reminder.php create mode 100644 app/Views/reminders/form.php diff --git a/app/Controllers/ReminderController.php b/app/Controllers/ReminderController.php new file mode 100644 index 0000000..2651425 --- /dev/null +++ b/app/Controllers/ReminderController.php @@ -0,0 +1,187 @@ +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) : ''; + } +} diff --git a/app/Models/Reminder.php b/app/Models/Reminder.php new file mode 100644 index 0000000..daebd66 --- /dev/null +++ b/app/Models/Reminder.php @@ -0,0 +1,96 @@ +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]); + } +} diff --git a/app/Views/reminders/form.php b/app/Views/reminders/form.php new file mode 100644 index 0000000..0b6086a --- /dev/null +++ b/app/Views/reminders/form.php @@ -0,0 +1,69 @@ + 'Montag', + '2' => 'Dienstag', + '3' => 'Mittwoch', + '4' => 'Donnerstag', + '5' => 'Freitag', + '6' => 'Samstag', + '7' => 'Sonntag', +]; +?> + +
+

+

Habit:

+ +
+ + + + + + + + + + + + + + + + Ohne Auswahl wird die Erinnerung taeglich angezeigt. + + + + + + + + + + + +
+ +

Zurueck zum Dashboard

+
diff --git a/public/index.php b/public/index.php index 0880de6..d171ea8 100644 --- a/public/index.php +++ b/public/index.php @@ -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'], ], ];