From bda2bc85d02fb0ed8928c71462deee2bfd31c434 Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Thu, 29 Jan 2026 21:07:02 +0100 Subject: [PATCH] first commit --- .htaccess | 9 + .vscode/ftp-kr.json | 17 ++ .vscode/sftp.json | 12 ++ PROJEKT.md | 315 ++++++++++++++++++++++++++++ app/controllers/AuthController.php | 117 +++++++++++ app/controllers/EventController.php | 179 ++++++++++++++++ app/controllers/NoteController.php | 126 +++++++++++ app/models/Database.php | 22 ++ app/models/Event.php | 182 ++++++++++++++++ app/models/Note.php | 103 +++++++++ app/models/User.php | 61 ++++++ app/views/calendar/day.php | 164 +++++++++++++++ app/views/calendar/form.php | 72 +++++++ app/views/calendar/index.php | 200 ++++++++++++++++++ app/views/dashboard.php | 179 ++++++++++++++++ app/views/layout.php | 203 ++++++++++++++++++ app/views/login.php | 53 +++++ app/views/notes/form.php | 91 ++++++++ app/views/notes/index.php | 120 +++++++++++ app/views/verify.php | 74 +++++++ index.php | 132 ++++++++++++ init.php | 163 ++++++++++++++ 22 files changed, 2594 insertions(+) create mode 100644 .htaccess create mode 100644 .vscode/ftp-kr.json create mode 100644 .vscode/sftp.json create mode 100644 PROJEKT.md create mode 100644 app/controllers/AuthController.php create mode 100644 app/controllers/EventController.php create mode 100644 app/controllers/NoteController.php create mode 100644 app/models/Database.php create mode 100644 app/models/Event.php create mode 100644 app/models/Note.php create mode 100644 app/models/User.php create mode 100644 app/views/calendar/day.php create mode 100644 app/views/calendar/form.php create mode 100644 app/views/calendar/index.php create mode 100644 app/views/dashboard.php create mode 100644 app/views/layout.php create mode 100644 app/views/login.php create mode 100644 app/views/notes/form.php create mode 100644 app/views/notes/index.php create mode 100644 app/views/verify.php create mode 100644 index.php create mode 100644 init.php diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..d1ff241 --- /dev/null +++ b/.htaccess @@ -0,0 +1,9 @@ +RewriteEngine On +RewriteBase / + +# Jeśli plik lub katalog istnieje - nie przekierowuj +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d + +# Przekieruj wszystko do index.php +RewriteRule ^(.*)$ index.php?route=$1 [QSA,L] diff --git a/.vscode/ftp-kr.json b/.vscode/ftp-kr.json new file mode 100644 index 0000000..f591b7b --- /dev/null +++ b/.vscode/ftp-kr.json @@ -0,0 +1,17 @@ +{ + "host": "host117523.hostido.net.pl", + "username": "www@cmspro.it", + "password": "jWD4awMpyMxr7FAYkPL3", + "remotePath": "/public_html/", + "protocol": "ftp", + "port": 21, + "fileNameEncoding": "utf8", + "autoUpload": true, + "autoDelete": false, + "autoDownload": false, + "ignoreRemoteModification": true, + "ignore": [ + ".git", + "/.vscode" + ] +} \ No newline at end of file diff --git a/.vscode/sftp.json b/.vscode/sftp.json new file mode 100644 index 0000000..64fab2e --- /dev/null +++ b/.vscode/sftp.json @@ -0,0 +1,12 @@ +{ + "name": "host117523.hostido.net.pl", + "host": "host117523.hostido.net.pl", + "protocol": "ftp", + "port": 21, + "username": "www@cmspro.it", + "password": "jWD4awMpyMxr7FAYkPL3", + "remotePath": "/public_html/", + "uploadOnSave": false, + "useTempFile": false, + "openSsh": false +} diff --git a/PROJEKT.md b/PROJEKT.md new file mode 100644 index 0000000..b17875b --- /dev/null +++ b/PROJEKT.md @@ -0,0 +1,315 @@ +# Projekt: System 2FA z Notatnikiem i Kalendarzem + +## Informacje ogólne + +- **Technologie:** PHP 8+, SQLite, Bootstrap 5.3, Bootstrap Icons +- **Architektura:** Uproszczone MVC (kontrolery, modele, widoki w PHP) +- **Routing:** Przyjazne linki (SEO-friendly URLs) przez .htaccess + +--- + +## Struktura projektu + +``` +projektphp2/ +├── .htaccess # Reguły mod_rewrite +├── index.php # Front controller (routing) +├── init.php # Inicjalizacja bazy danych +├── PROJEKT.md # Ten plik - dokumentacja +├── app/ +│ ├── controllers/ +│ │ ├── AuthController.php # Logowanie, 2FA, wylogowanie +│ │ ├── NoteController.php # CRUD notatek +│ │ └── EventController.php # CRUD wydarzeń kalendarza +│ ├── models/ +│ │ ├── Database.php # Singleton PDO SQLite +│ │ ├── User.php # Model użytkownika + kody weryfikacyjne +│ │ ├── Note.php # Model notatek (auto-migracja) +│ │ └── Event.php # Model wydarzeń (auto-migracja) +│ └── views/ +│ ├── layout.php # Główny szablon (z/bez menu) +│ ├── login.php # Formularz logowania +│ ├── verify.php # Weryfikacja kodu 2FA +│ ├── dashboard.php # Pulpit użytkownika +│ ├── notes/ +│ │ ├── index.php # Lista notatek (karty) +│ │ └── form.php # Formularz dodawania/edycji notatki +│ └── calendar/ +│ ├── index.php # Widok kalendarza miesięcznego +│ ├── day.php # Lista wydarzeń danego dnia +│ └── form.php # Formularz dodawania/edycji wydarzenia +└── database/ + └── app.db # Baza SQLite (tworzona przez init.php) +``` + +--- + +## Routing (przyjazne linki) + +### Autoryzacja +| URL | Metoda | Akcja | +|-----|--------|-------| +| `/` lub `/logowanie` | GET | Formularz logowania | +| `/zaloguj` | POST | Weryfikacja login/hasło | +| `/weryfikacja` | GET | Formularz kodu 2FA | +| `/zweryfikuj` | POST | Weryfikacja kodu 2FA | +| `/panel` | GET | Pulpit (wymaga auth) | +| `/wyloguj-sie` | GET | Wylogowanie | + +### Notatnik (wymaga zalogowania) +| URL | Metoda | Akcja | +|-----|--------|-------| +| `/notatnik` | GET | Lista wszystkich notatek | +| `/notatnik/nowa` | GET | Formularz nowej notatki | +| `/notatnik/dodaj` | POST | Zapisanie nowej notatki | +| `/notatnik/edytuj/{id}` | GET | Formularz edycji | +| `/notatnik/zapisz/{id}` | POST | Zapisanie zmian | +| `/notatnik/usun/{id}` | POST | Usunięcie notatki | + +### Kalendarz (wymaga zalogowania) +| URL | Metoda | Akcja | +|-----|--------|-------| +| `/kalendarz` | GET | Widok kalendarza miesięcznego | +| `/kalendarz?rok=2024&miesiac=12` | GET | Kalendarz dla konkretnego miesiąca | +| `/kalendarz/dzien/{YYYY-MM-DD}` | GET | Lista wydarzeń danego dnia | +| `/kalendarz/nowe` | GET | Formularz nowego wydarzenia | +| `/kalendarz/nowe/{YYYY-MM-DD}` | GET | Formularz z predefiniowaną datą | +| `/kalendarz/dodaj` | POST | Zapisanie nowego wydarzenia | +| `/kalendarz/edytuj/{id}` | GET | Formularz edycji wydarzenia | +| `/kalendarz/zapisz/{id}` | POST | Zapisanie zmian | +| `/kalendarz/usun/{id}` | POST | Usunięcie wydarzenia | + +--- + +## Baza danych (SQLite) + +### Tabela: users +```sql +CREATE TABLE users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + login VARCHAR(50) UNIQUE NOT NULL, + password VARCHAR(255) NOT NULL, -- password_hash() + email VARCHAR(100), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +); +``` + +### Tabela: verification_codes +```sql +CREATE TABLE verification_codes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + code VARCHAR(6) NOT NULL, + expires_at DATETIME NOT NULL, -- +10 minut + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id), + UNIQUE(user_id) +); +``` + +### Tabela: notes (auto-tworzona przy pierwszym użyciu) +```sql +CREATE TABLE notes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + title VARCHAR(255) NOT NULL, + content TEXT, + color VARCHAR(20) DEFAULT 'primary', -- primary/success/danger/warning/info/secondary + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) +); +``` + +### Tabela: events (auto-tworzona przy pierwszym użyciu) +```sql +CREATE TABLE events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + title VARCHAR(255) NOT NULL, + content TEXT, + event_date DATE NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) +); +``` + +--- + +## Dane testowe + +- **Login:** `projectpro` +- **Hasło:** `testowehaslo` +- **Inicjalizacja:** wywołać `/init.php` + +--- + +## Funkcjonalności + +### Zrealizowane ✅ + +1. **Logowanie dwuskładnikowe (2FA)** + - Formularz login/hasło + - Generowanie 6-cyfrowego kodu + - Symulacja wysyłki email (kod wyświetlany na stronie) + - Weryfikacja kodu z czasem wygaśnięcia (10 min) + - Sesje PHP + +2. **Panel użytkownika (Pulpit)** + - Powitanie użytkownika + - Statystyki (status 2FA, liczba notatek, wydarzenia tego tygodnia, sesja) + - Szybkie akcje (nowa notatka, nowe wydarzenie, kalendarz) + - Lista nadchodzących wydarzeń z tego tygodnia + - Informacje o logowaniu + +3. **Notatnik** + - Lista notatek jako kolorowe karty + - Dodawanie notatek (tytuł, treść, kolor) + - Edycja notatek + - Usuwanie z potwierdzeniem (modal Bootstrap) + - 6 kolorów do wyboru + - Auto-migracja tabeli + +4. **Kalendarz** + - Widok miesięczny z nawigacją (poprzedni/następny miesiąc) + - Podgląd wydarzeń na poszczególnych dniach + - Kliknięcie w dzień otwiera listę wydarzeń + - Dodawanie wydarzeń (tytuł, treść, data) + - Edycja wydarzeń + - Usuwanie z potwierdzeniem (modal Bootstrap) + - Wyróżnienie dzisiejszego dnia + - Wyróżnienie dni z wydarzeniami + - Szybkie dodawanie z predefiniowaną datą + - Auto-migracja tabeli + +5. **Menu nawigacyjne** + - Pulpit / Notatnik / Kalendarz + - Dropdown z użytkownikiem + - Wylogowanie + - Aktywna pozycja menu podświetlona + +6. **Wygląd** + - Bootstrap 5.3 + Bootstrap Icons + - Gradient fioletowy jako tło + - Karty z cieniami i zaokrągleniami + - Responsywność (mobile-friendly) + - Animacje hover + +--- + +## Wymagania serwera + +- PHP 8.0+ +- Rozszerzenie PDO SQLite +- Apache z mod_rewrite +- Uprawnienia do zapisu w katalogu `database/` + +--- + +## Sesje PHP + +Używane klucze sesji: +- `user_id` - ID zalogowanego użytkownika +- `user_login` - Login użytkownika +- `logged_in` - Flaga pełnej autoryzacji (po 2FA) +- `pending_user_id` - ID podczas weryfikacji 2FA +- `pending_user_login` - Login podczas weryfikacji 2FA +- `simulated_code` - Kod 2FA (tylko do testów!) +- `error` - Komunikat błędu (flash message) +- `success` - Komunikat sukcesu (flash message) + +--- + +## Bezpieczeństwo + +- Hasła hashowane przez `password_hash()` / `password_verify()` +- Prepared statements (PDO) - ochrona przed SQL injection +- `htmlspecialchars()` - ochrona przed XSS +- Kody 2FA z czasem wygaśnięcia +- Usuwanie kodów po użyciu +- Sprawdzanie właściciela notatki/wydarzenia przy każdej operacji + +--- + +## Do zrobienia w przyszłości (pomysły) + +- [ ] Prawdziwa wysyłka emaili (PHPMailer / SMTP) +- [ ] Rejestracja nowych użytkowników +- [ ] Przypomnienie hasła +- [ ] Wyszukiwanie notatek +- [ ] Kategorie/tagi dla notatek +- [ ] Eksport notatek (PDF/TXT) +- [ ] Limit prób logowania (brute-force protection) +- [ ] Zapamiętaj mnie (persistent login) +- [ ] Logi aktywności użytkownika +- [ ] Tryb ciemny (dark mode) +- [ ] API REST dla notatek i wydarzeń +- [ ] Przypomnienia o wydarzeniach +- [ ] Powtarzające się wydarzenia (cykliczne) +- [ ] Widok tygodniowy/dzienny kalendarza +- [ ] Drag & drop dla wydarzeń +- [ ] Eksport kalendarza (iCal) + +--- + +## Historia zmian + +### v1.0 (początkowa wersja) +- System logowania z 2FA +- Baza SQLite z użytkownikiem testowym +- Widoki: logowanie, weryfikacja, dashboard +- Bootstrap 5.3 + gradient design + +### v1.1 (przyjazne linki) +- Routing przez .htaccess (mod_rewrite) +- Przeniesienie do katalogu głównego (bez /public/) +- SEO-friendly URLs (/logowanie, /panel, etc.) + +### v1.2 (notatnik) +- Menu nawigacyjne (Pulpit, Notatnik) +- Pełny CRUD notatek +- Kolorowe karty z podglądem +- Modal potwierdzenia usunięcia +- Statystyki na pulpicie +- Auto-migracja tabeli notes + +### v1.3 (kalendarz) +- Nowa pozycja menu: Kalendarz +- Widok kalendarza miesięcznego z nawigacją +- Widok dnia z listą wydarzeń +- Pełny CRUD wydarzeń (tytuł, treść, data) +- Podgląd wydarzeń bezpośrednio w kalendarzu +- Wyróżnienie dzisiejszego dnia i dni z wydarzeniami +- Statystyki wydarzeń na pulpicie (ten tydzień) +- Lista nadchodzących wydarzeń na pulpicie +- Szybkie akcje: nowe wydarzenie, otwórz kalendarz +- Auto-migracja tabeli events + +--- + +## Notatki dla Claude + +1. **Layout** - funkcja `renderLayout()` przyjmuje parametry: + - `$title` - tytuł strony + - `$content` - zawartość + - `$showMenu` - czy pokazać menu (domyślnie false) + - `$activeMenu` - która pozycja menu jest aktywna ('pulpit'/'notatnik'/'kalendarz') + - `$containerSize` - klasa Bootstrap dla kontenera (domyślnie 'col-md-5') + +2. **Modele z auto-migracją** - metoda `ensureTable()` w Note i Event automatycznie tworzy tabelę jeśli nie istnieje + +3. **Flash messages** - komunikaty błędów/sukcesu przez sesję, czyszczone po wyświetleniu + +4. **Routing** - w `index.php` używany `switch(true)` z `preg_match` dla dynamicznych URL + +5. **Kolory notatek** - dostępne: primary, success, danger, warning, info, secondary + +6. **Kalendarz - parametry URL:** + - `rok` - rok (YYYY) + - `miesiac` - miesiąc (1-12) + - Format daty w URL: `YYYY-MM-DD` + +7. **Event::countThisWeek()** - zlicza wydarzenia od poniedziałku do niedzieli bieżącego tygodnia + +8. **Event::getThisWeek()** - pobiera listę wydarzeń z bieżącego tygodnia (dla pulpitu) diff --git a/app/controllers/AuthController.php b/app/controllers/AuthController.php new file mode 100644 index 0000000..04b38db --- /dev/null +++ b/app/controllers/AuthController.php @@ -0,0 +1,117 @@ +requireAuth(); + + $userId = $_SESSION['user_id']; + $userLogin = $_SESSION['user_login']; + + // Pobierz rok i miesiąc z parametrów lub użyj bieżących + $year = isset($_GET['rok']) ? (int) $_GET['rok'] : (int) date('Y'); + $month = isset($_GET['miesiac']) ? (int) $_GET['miesiac'] : (int) date('m'); + + // Walidacja + if ($month < 1) { + $month = 12; + $year--; + } elseif ($month > 12) { + $month = 1; + $year++; + } + + $events = Event::getByMonth($userId, $year, $month); + + // Grupowanie wydarzeń po dacie + $eventsByDate = []; + foreach ($events as $event) { + $eventsByDate[$event['event_date']][] = $event; + } + + $success = $_SESSION['success'] ?? null; + $error = $_SESSION['error'] ?? null; + unset($_SESSION['success'], $_SESSION['error']); + + require __DIR__ . '/../views/calendar/index.php'; + } + + public function dayEvents(string $date): void + { + $this->requireAuth(); + + $userId = $_SESSION['user_id']; + $userLogin = $_SESSION['user_login']; + + // Walidacja daty + if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) { + $_SESSION['error'] = 'Nieprawidłowy format daty.'; + header('Location: /kalendarz'); + exit; + } + + $events = Event::getByDate($userId, $date); + + $success = $_SESSION['success'] ?? null; + $error = $_SESSION['error'] ?? null; + unset($_SESSION['success'], $_SESSION['error']); + + require __DIR__ . '/../views/calendar/day.php'; + } + + public function create(string $date = null): void + { + $this->requireAuth(); + + $userLogin = $_SESSION['user_login']; + $event = null; + $isEdit = false; + $selectedDate = $date ?? date('Y-m-d'); + + require __DIR__ . '/../views/calendar/form.php'; + } + + public function store(): void + { + $this->requireAuth(); + + $userId = $_SESSION['user_id']; + $title = trim($_POST['title'] ?? ''); + $content = trim($_POST['content'] ?? ''); + $eventDate = $_POST['event_date'] ?? ''; + + if (empty($title)) { + $_SESSION['error'] = 'Tytuł wydarzenia jest wymagany.'; + header('Location: /kalendarz/nowe'); + exit; + } + + if (empty($eventDate)) { + $_SESSION['error'] = 'Data wydarzenia jest wymagana.'; + header('Location: /kalendarz/nowe'); + exit; + } + + Event::create($userId, $title, $content, $eventDate); + $_SESSION['success'] = 'Wydarzenie zostało dodane.'; + header('Location: /kalendarz/dzien/' . $eventDate); + exit; + } + + public function edit(int $id): void + { + $this->requireAuth(); + + $userId = $_SESSION['user_id']; + $userLogin = $_SESSION['user_login']; + $event = Event::getById($id, $userId); + + if (!$event) { + $_SESSION['error'] = 'Wydarzenie nie zostało znalezione.'; + header('Location: /kalendarz'); + exit; + } + + $isEdit = true; + $selectedDate = $event['event_date']; + + require __DIR__ . '/../views/calendar/form.php'; + } + + public function update(int $id): void + { + $this->requireAuth(); + + $userId = $_SESSION['user_id']; + $title = trim($_POST['title'] ?? ''); + $content = trim($_POST['content'] ?? ''); + $eventDate = $_POST['event_date'] ?? ''; + + if (empty($title)) { + $_SESSION['error'] = 'Tytuł wydarzenia jest wymagany.'; + header("Location: /kalendarz/edytuj/$id"); + exit; + } + + $event = Event::getById($id, $userId); + if (!$event) { + $_SESSION['error'] = 'Wydarzenie nie zostało znalezione.'; + header('Location: /kalendarz'); + exit; + } + + Event::update($id, $userId, $title, $content, $eventDate); + $_SESSION['success'] = 'Wydarzenie zostało zaktualizowane.'; + header('Location: /kalendarz/dzien/' . $eventDate); + exit; + } + + public function delete(int $id): void + { + $this->requireAuth(); + + $userId = $_SESSION['user_id']; + $event = Event::getById($id, $userId); + + if (!$event) { + $_SESSION['error'] = 'Wydarzenie nie zostało znalezione.'; + header('Location: /kalendarz'); + exit; + } + + $eventDate = $event['event_date']; + Event::delete($id, $userId); + $_SESSION['success'] = 'Wydarzenie zostało usunięte.'; + header('Location: /kalendarz/dzien/' . $eventDate); + exit; + } +} diff --git a/app/controllers/NoteController.php b/app/controllers/NoteController.php new file mode 100644 index 0000000..58d7fe7 --- /dev/null +++ b/app/controllers/NoteController.php @@ -0,0 +1,126 @@ +requireAuth(); + + $userId = $_SESSION['user_id']; + $userLogin = $_SESSION['user_login']; + $notes = Note::getAllByUser($userId); + + $success = $_SESSION['success'] ?? null; + $error = $_SESSION['error'] ?? null; + unset($_SESSION['success'], $_SESSION['error']); + + require __DIR__ . '/../views/notes/index.php'; + } + + public function create(): void + { + $this->requireAuth(); + + $userLogin = $_SESSION['user_login']; + $note = null; + $isEdit = false; + + require __DIR__ . '/../views/notes/form.php'; + } + + public function store(): void + { + $this->requireAuth(); + + $userId = $_SESSION['user_id']; + $title = trim($_POST['title'] ?? ''); + $content = trim($_POST['content'] ?? ''); + $color = $_POST['color'] ?? 'primary'; + + if (empty($title)) { + $_SESSION['error'] = 'Tytuł notatki jest wymagany.'; + header('Location: /notatnik/nowa'); + exit; + } + + Note::create($userId, $title, $content, $color); + $_SESSION['success'] = 'Notatka została dodana.'; + header('Location: /notatnik'); + exit; + } + + public function edit(int $id): void + { + $this->requireAuth(); + + $userId = $_SESSION['user_id']; + $userLogin = $_SESSION['user_login']; + $note = Note::getById($id, $userId); + + if (!$note) { + $_SESSION['error'] = 'Notatka nie została znaleziona.'; + header('Location: /notatnik'); + exit; + } + + $isEdit = true; + require __DIR__ . '/../views/notes/form.php'; + } + + public function update(int $id): void + { + $this->requireAuth(); + + $userId = $_SESSION['user_id']; + $title = trim($_POST['title'] ?? ''); + $content = trim($_POST['content'] ?? ''); + $color = $_POST['color'] ?? 'primary'; + + if (empty($title)) { + $_SESSION['error'] = 'Tytuł notatki jest wymagany.'; + header("Location: /notatnik/edytuj/$id"); + exit; + } + + $note = Note::getById($id, $userId); + if (!$note) { + $_SESSION['error'] = 'Notatka nie została znaleziona.'; + header('Location: /notatnik'); + exit; + } + + Note::update($id, $userId, $title, $content, $color); + $_SESSION['success'] = 'Notatka została zaktualizowana.'; + header('Location: /notatnik'); + exit; + } + + public function delete(int $id): void + { + $this->requireAuth(); + + $userId = $_SESSION['user_id']; + $note = Note::getById($id, $userId); + + if (!$note) { + $_SESSION['error'] = 'Notatka nie została znaleziona.'; + header('Location: /notatnik'); + exit; + } + + Note::delete($id, $userId); + $_SESSION['success'] = 'Notatka została usunięta.'; + header('Location: /notatnik'); + exit; + } +} diff --git a/app/models/Database.php b/app/models/Database.php new file mode 100644 index 0000000..0650970 --- /dev/null +++ b/app/models/Database.php @@ -0,0 +1,22 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + self::$instance->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + } + return self::$instance; + } + + public static function getDbPath(): string + { + return self::$dbPath; + } +} diff --git a/app/models/Event.php b/app/models/Event.php new file mode 100644 index 0000000..ff09dab --- /dev/null +++ b/app/models/Event.php @@ -0,0 +1,182 @@ +exec(' + CREATE TABLE IF NOT EXISTS events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + title VARCHAR(255) NOT NULL, + content TEXT, + event_date DATE NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) + ) + '); + } + + public static function getAllByUser(int $userId): array + { + self::ensureTable(); + $db = Database::getInstance(); + $stmt = $db->prepare(' + SELECT * FROM events + WHERE user_id = :user_id + ORDER BY event_date ASC + '); + $stmt->execute(['user_id' => $userId]); + return $stmt->fetchAll(); + } + + public static function getByMonth(int $userId, int $year, int $month): array + { + self::ensureTable(); + $db = Database::getInstance(); + $startDate = sprintf('%04d-%02d-01', $year, $month); + $endDate = sprintf('%04d-%02d-%02d', $year, $month, cal_days_in_month(CAL_GREGORIAN, $month, $year)); + + $stmt = $db->prepare(' + SELECT * FROM events + WHERE user_id = :user_id + AND event_date BETWEEN :start_date AND :end_date + ORDER BY event_date ASC + '); + $stmt->execute([ + 'user_id' => $userId, + 'start_date' => $startDate, + 'end_date' => $endDate + ]); + return $stmt->fetchAll(); + } + + public static function getByDate(int $userId, string $date): array + { + self::ensureTable(); + $db = Database::getInstance(); + $stmt = $db->prepare(' + SELECT * FROM events + WHERE user_id = :user_id AND event_date = :event_date + ORDER BY created_at ASC + '); + $stmt->execute(['user_id' => $userId, 'event_date' => $date]); + return $stmt->fetchAll(); + } + + public static function getById(int $id, int $userId): ?array + { + self::ensureTable(); + $db = Database::getInstance(); + $stmt = $db->prepare(' + SELECT * FROM events + WHERE id = :id AND user_id = :user_id + '); + $stmt->execute(['id' => $id, 'user_id' => $userId]); + $event = $stmt->fetch(); + return $event ?: null; + } + + public static function create(int $userId, string $title, string $content, string $eventDate): int + { + self::ensureTable(); + $db = Database::getInstance(); + $stmt = $db->prepare(' + INSERT INTO events (user_id, title, content, event_date, created_at, updated_at) + VALUES (:user_id, :title, :content, :event_date, datetime("now"), datetime("now")) + '); + $stmt->execute([ + 'user_id' => $userId, + 'title' => $title, + 'content' => $content, + 'event_date' => $eventDate + ]); + return (int) $db->lastInsertId(); + } + + public static function update(int $id, int $userId, string $title, string $content, string $eventDate): bool + { + self::ensureTable(); + $db = Database::getInstance(); + $stmt = $db->prepare(' + UPDATE events + SET title = :title, content = :content, event_date = :event_date, updated_at = datetime("now") + WHERE id = :id AND user_id = :user_id + '); + return $stmt->execute([ + 'id' => $id, + 'user_id' => $userId, + 'title' => $title, + 'content' => $content, + 'event_date' => $eventDate + ]); + } + + public static function delete(int $id, int $userId): bool + { + $db = Database::getInstance(); + $stmt = $db->prepare(' + DELETE FROM events + WHERE id = :id AND user_id = :user_id + '); + return $stmt->execute(['id' => $id, 'user_id' => $userId]); + } + + public static function countByUser(int $userId): int + { + self::ensureTable(); + $db = Database::getInstance(); + $stmt = $db->prepare('SELECT COUNT(*) as count FROM events WHERE user_id = :user_id'); + $stmt->execute(['user_id' => $userId]); + return (int) $stmt->fetch()['count']; + } + + public static function countThisWeek(int $userId): int + { + self::ensureTable(); + $db = Database::getInstance(); + + // Poniedziałek tego tygodnia + $monday = date('Y-m-d', strtotime('monday this week')); + // Niedziela tego tygodnia + $sunday = date('Y-m-d', strtotime('sunday this week')); + + $stmt = $db->prepare(' + SELECT COUNT(*) as count FROM events + WHERE user_id = :user_id + AND event_date BETWEEN :monday AND :sunday + '); + $stmt->execute([ + 'user_id' => $userId, + 'monday' => $monday, + 'sunday' => $sunday + ]); + return (int) $stmt->fetch()['count']; + } + + public static function getThisWeek(int $userId): array + { + self::ensureTable(); + $db = Database::getInstance(); + + $monday = date('Y-m-d', strtotime('monday this week')); + $sunday = date('Y-m-d', strtotime('sunday this week')); + + $stmt = $db->prepare(' + SELECT * FROM events + WHERE user_id = :user_id + AND event_date BETWEEN :monday AND :sunday + ORDER BY event_date ASC + '); + $stmt->execute([ + 'user_id' => $userId, + 'monday' => $monday, + 'sunday' => $sunday + ]); + return $stmt->fetchAll(); + } +} diff --git a/app/models/Note.php b/app/models/Note.php new file mode 100644 index 0000000..3de151a --- /dev/null +++ b/app/models/Note.php @@ -0,0 +1,103 @@ +exec(' + CREATE TABLE IF NOT EXISTS notes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + title VARCHAR(255) NOT NULL, + content TEXT, + color VARCHAR(20) DEFAULT "primary", + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) + ) + '); + } + + public static function getAllByUser(int $userId): array + { + self::ensureTable(); + $db = Database::getInstance(); + $stmt = $db->prepare(' + SELECT * FROM notes + WHERE user_id = :user_id + ORDER BY updated_at DESC + '); + $stmt->execute(['user_id' => $userId]); + return $stmt->fetchAll(); + } + + public static function getById(int $id, int $userId): ?array + { + self::ensureTable(); + $db = Database::getInstance(); + $stmt = $db->prepare(' + SELECT * FROM notes + WHERE id = :id AND user_id = :user_id + '); + $stmt->execute(['id' => $id, 'user_id' => $userId]); + $note = $stmt->fetch(); + return $note ?: null; + } + + public static function create(int $userId, string $title, string $content, string $color = 'primary'): int + { + self::ensureTable(); + $db = Database::getInstance(); + $stmt = $db->prepare(' + INSERT INTO notes (user_id, title, content, color, created_at, updated_at) + VALUES (:user_id, :title, :content, :color, datetime("now"), datetime("now")) + '); + $stmt->execute([ + 'user_id' => $userId, + 'title' => $title, + 'content' => $content, + 'color' => $color + ]); + return (int) $db->lastInsertId(); + } + + public static function update(int $id, int $userId, string $title, string $content, string $color): bool + { + self::ensureTable(); + $db = Database::getInstance(); + $stmt = $db->prepare(' + UPDATE notes + SET title = :title, content = :content, color = :color, updated_at = datetime("now") + WHERE id = :id AND user_id = :user_id + '); + return $stmt->execute([ + 'id' => $id, + 'user_id' => $userId, + 'title' => $title, + 'content' => $content, + 'color' => $color + ]); + } + + public static function delete(int $id, int $userId): bool + { + $db = Database::getInstance(); + $stmt = $db->prepare(' + DELETE FROM notes + WHERE id = :id AND user_id = :user_id + '); + return $stmt->execute(['id' => $id, 'user_id' => $userId]); + } + + public static function countByUser(int $userId): int + { + self::ensureTable(); + $db = Database::getInstance(); + $stmt = $db->prepare('SELECT COUNT(*) as count FROM notes WHERE user_id = :user_id'); + $stmt->execute(['user_id' => $userId]); + return (int) $stmt->fetch()['count']; + } +} diff --git a/app/models/User.php b/app/models/User.php new file mode 100644 index 0000000..aac7c57 --- /dev/null +++ b/app/models/User.php @@ -0,0 +1,61 @@ +prepare('SELECT * FROM users WHERE login = :login'); + $stmt->execute(['login' => $login]); + $user = $stmt->fetch(); + return $user ?: null; + } + + public static function verifyPassword(string $login, string $password): ?array + { + $user = self::findByLogin($login); + if ($user && password_verify($password, $user['password'])) { + return $user; + } + return null; + } + + public static function saveVerificationCode(int $userId, string $code): bool + { + $db = Database::getInstance(); + $expiresAt = date('Y-m-d H:i:s', strtotime('+10 minutes')); + + $stmt = $db->prepare(' + INSERT OR REPLACE INTO verification_codes (user_id, code, expires_at, created_at) + VALUES (:user_id, :code, :expires_at, datetime("now")) + '); + + return $stmt->execute([ + 'user_id' => $userId, + 'code' => $code, + 'expires_at' => $expiresAt + ]); + } + + public static function verifyCode(int $userId, string $code): bool + { + $db = Database::getInstance(); + $stmt = $db->prepare(' + SELECT * FROM verification_codes + WHERE user_id = :user_id + AND code = :code + AND expires_at > datetime("now") + '); + $stmt->execute(['user_id' => $userId, 'code' => $code]); + return (bool) $stmt->fetch(); + } + + public static function deleteVerificationCode(int $userId): bool + { + $db = Database::getInstance(); + $stmt = $db->prepare('DELETE FROM verification_codes WHERE user_id = :user_id'); + return $stmt->execute(['user_id' => $userId]); + } +} diff --git a/app/views/calendar/day.php b/app/views/calendar/day.php new file mode 100644 index 0000000..98d73b3 --- /dev/null +++ b/app/views/calendar/day.php @@ -0,0 +1,164 @@ + 'Poniedziałek', 2 => 'Wtorek', 3 => 'Środa', 4 => 'Czwartek', + 5 => 'Piątek', 6 => 'Sobota', 7 => 'Niedziela' +]; + +$monthNames = [ + 1 => 'stycznia', 2 => 'lutego', 3 => 'marca', 4 => 'kwietnia', + 5 => 'maja', 6 => 'czerwca', 7 => 'lipca', 8 => 'sierpnia', + 9 => 'września', 10 => 'października', 11 => 'listopada', 12 => 'grudnia' +]; + +$timestamp = strtotime($date); +$dayOfWeek = date('N', $timestamp); +$day = date('j', $timestamp); +$month = date('n', $timestamp); +$year = date('Y', $timestamp); + +$formattedDate = $dayNames[$dayOfWeek] . ', ' . $day . ' ' . $monthNames[$month] . ' ' . $year; +$isToday = ($date === date('Y-m-d')); + +ob_start(); +?> + + + + + + + + + +
+
+ + Powrót do kalendarza + +

+ + Dzisiaj + + +

+
+ + Dodaj wydarzenie + +
+ + +
+
+ +

Brak wydarzeń

+

Nie masz żadnych wydarzeń zaplanowanych na ten dzień.

+ + Dodaj wydarzenie + +
+
+ +
+ +
+
+
+
+
+
+ + +
+ +

+ +

+ + + + Dodano: + +
+
+ + + + +
+
+
+
+
+ +
+ + + + + + + + + + + +
+
+ +

+
+
+ + + + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + + Anuluj + +
+
+
+
+ + 'Styczeń', 2 => 'Luty', 3 => 'Marzec', 4 => 'Kwiecień', + 5 => 'Maj', 6 => 'Czerwiec', 7 => 'Lipiec', 8 => 'Sierpień', + 9 => 'Wrzesień', 10 => 'Październik', 11 => 'Listopad', 12 => 'Grudzień' +]; + +$dayNames = ['Pon', 'Wt', 'Śr', 'Czw', 'Pt', 'Sob', 'Ndz']; + +// Obliczenia kalendarza +$firstDayOfMonth = mktime(0, 0, 0, $month, 1, $year); +$daysInMonth = cal_days_in_month(CAL_GREGORIAN, $month, $year); +$dayOfWeek = date('N', $firstDayOfMonth); // 1 = poniedziałek, 7 = niedziela + +$prevMonth = $month - 1; +$prevYear = $year; +if ($prevMonth < 1) { + $prevMonth = 12; + $prevYear--; +} + +$nextMonth = $month + 1; +$nextYear = $year; +if ($nextMonth > 12) { + $nextMonth = 1; + $nextYear++; +} + +$today = date('Y-m-d'); + +ob_start(); +?> + + + + + + + + + +
+
+
+ + + +

+ + +

+ + + +
+
+
+ + + + + + + + + + $daysInMonth) break; + ?> + + + + + + + +
onclick="window.location='/kalendarz/dzien/'"> + +
+ +
+ +
+ +
+ 15 ? '...' : '' ?> +
+ + 2): ?> +
+ więcej
+ +
+ + +
+
+ +
+ + + + + +
+
+
+
+
+
+ +
+
+

+ Witaj, ! +

+

+ Pomyślnie zalogowano z uwierzytelnianiem dwuskładnikowym. +

+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
Status 2FA
+

Aktywne

+
+
+
+
+
+
+
+ +
+
Notatki
+

+
+
+
+
+
+
+
+ +
+
Ten tydzień
+

= 2 && $eventsThisWeek <= 4 ? 'wydarzenia' : 'wydarzeń') ?>

+
+
+
+
+
+
+
+ +
+
Sesja
+

Aktywna

+
+
+
+
+ +
+ +
+
+
+
Wydarzenia tego tygodnia
+ Zobacz wszystkie +
+
+ +

+ + Brak wydarzeń w tym tygodniu +

+ +
    + +
  • +
    + +
    +
    +
    + + + +
    + + + +
  • + + 4): ?> +
  • + + więcej wydarzeń +
  • + +
+ +
+
+
+
+ +
+
+
+
+
Informacje o sesji
+
+
+
+
+ + Data logowania: +
+
+ + Metoda 2FA: Kod Email +
+
+ + Połączenie: Szyfrowane +
+
+
+
+
+
+ + + + + + + + <?= htmlspecialchars($title) ?> - System 2FA + + + + + + + + + +
+
+
+ +
+
+
+ + + + + + +
+
+ +

Logowanie

+
+
+ + + + +
+
+ + +
+
+ + +
+
+ +
+
+
+ +
+ + '#667eea', + 'success' => '#28a745', + 'danger' => '#dc3545', + 'warning' => '#ffc107', + 'info' => '#17a2b8', + 'secondary' => '#6c757d' +]; + +$currentColor = $note['color'] ?? 'primary'; + +ob_start(); +?> + +
+
+ +

+
+
+ + + + +
+
+ + +
+ +
+ + +
+ +
+ +
+ $hex): ?> +
+
+ +
+ +
+ + +
+
+
+ + + + + + + + + + + + + +
+

+ Moje notatki +

+ + Nowa notatka + +
+ + +
+
+ +

Brak notatek

+

Nie masz jeszcze żadnych notatek. Utwórz pierwszą!

+ + Utwórz notatkę + +
+
+ +
+ +
+
+
+
+
+
+ + + + +
+
+

+ +

+
+ + + + +
+
+
+
+ +
+ + + + + + + + + +
+
+ +

Weryfikacja dwuskładnikowa

+
+
+ + + + +
+ + Kod weryfikacyjny został wysłany na adres e-mail powiązany z kontem + . +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+
+ + + Anuluj i wróć + +
+
+
+ +
+ + + +loginForm(); + break; + + case $route === 'zaloguj': + $authController->login(); + break; + + case $route === 'weryfikacja': + $authController->verifyForm(); + break; + + case $route === 'zweryfikuj': + $authController->verify(); + break; + + case $route === 'panel': + $authController->dashboard(); + break; + + case $route === 'wyloguj-sie': + $authController->logout(); + break; + + // Notatnik + case $route === 'notatnik': + $noteController->index(); + break; + + case $route === 'notatnik/nowa': + $noteController->create(); + break; + + case $route === 'notatnik/dodaj': + $noteController->store(); + break; + + case preg_match('#^notatnik/edytuj/(\d+)$#', $route, $matches) === 1: + $noteController->edit((int) $matches[1]); + break; + + case preg_match('#^notatnik/zapisz/(\d+)$#', $route, $matches) === 1: + $noteController->update((int) $matches[1]); + break; + + case preg_match('#^notatnik/usun/(\d+)$#', $route, $matches) === 1: + $noteController->delete((int) $matches[1]); + break; + + // Kalendarz + case $route === 'kalendarz': + $eventController->index(); + break; + + case $route === 'kalendarz/nowe': + $eventController->create(); + break; + + case preg_match('#^kalendarz/nowe/(\d{4}-\d{2}-\d{2})$#', $route, $matches) === 1: + $eventController->create($matches[1]); + break; + + case $route === 'kalendarz/dodaj': + $eventController->store(); + break; + + case preg_match('#^kalendarz/dzien/(\d{4}-\d{2}-\d{2})$#', $route, $matches) === 1: + $eventController->dayEvents($matches[1]); + break; + + case preg_match('#^kalendarz/edytuj/(\d+)$#', $route, $matches) === 1: + $eventController->edit((int) $matches[1]); + break; + + case preg_match('#^kalendarz/zapisz/(\d+)$#', $route, $matches) === 1: + $eventController->update((int) $matches[1]); + break; + + case preg_match('#^kalendarz/usun/(\d+)$#', $route, $matches) === 1: + $eventController->delete((int) $matches[1]); + break; + + default: + // Strona 404 + http_response_code(404); + echo ' + + + + + 404 - Nie znaleziono + + + + +
+

404

+

Strona nie została znaleziona

+ Wróć do strony głównej +
+ + '; + break; +} diff --git a/init.php b/init.php new file mode 100644 index 0000000..1be9cad --- /dev/null +++ b/init.php @@ -0,0 +1,163 @@ +exec(' + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + login VARCHAR(50) UNIQUE NOT NULL, + password VARCHAR(255) NOT NULL, + email VARCHAR(100), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + ) + '); + + // Tworzenie tabeli kodów weryfikacyjnych + $db->exec(' + CREATE TABLE IF NOT EXISTS verification_codes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + code VARCHAR(6) NOT NULL, + expires_at DATETIME NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id), + UNIQUE(user_id) + ) + '); + + // Sprawdzenie czy użytkownik testowy istnieje + $stmt = $db->prepare('SELECT id FROM users WHERE login = :login'); + $stmt->execute(['login' => 'projectpro']); + $userExists = $stmt->fetch(); + + if (!$userExists) { + // Dodanie testowego użytkownika + $password = password_hash('testowehaslo', PASSWORD_DEFAULT); + $stmt = $db->prepare(' + INSERT INTO users (login, password, email) + VALUES (:login, :password, :email) + '); + $stmt->execute([ + 'login' => 'projectpro', + 'password' => $password, + 'email' => 'projectpro@example.com' + ]); + $userCreated = true; + } else { + $userCreated = false; + } + + // Wyświetlenie statusu + ?> + + + + + + Inicjalizacja bazy danych + + + + + +
+
+
+
+
+ +

Inicjalizacja zakończona

+
+
+
+ + Baza danych została zainicjalizowana pomyślnie! +
+ +
Status:
+
    +
  • + Tabela users + OK +
  • +
  • + Tabela verification_codes + OK +
  • +
  • + Użytkownik testowy + + + +
  • +
+ +
Dane testowe:
+
+

+ Login: + projectpro +

+

+ Hasło: + testowehaslo +

+
+ + +
+
+
+
+
+ + + + + + + + + Błąd inicjalizacji + + + +
+
+

Błąd inicjalizacji bazy danych

+
+

getMessage()) ?>

+
+
+ + +