diff --git a/public/index.php b/public/index.php index 86194e2..f2b192d 100644 --- a/public/index.php +++ b/public/index.php @@ -1,26 +1,4 @@ render("admin.php", $vars); } // POST handler diff --git a/src/Controller/Auth/Auth.php b/src/Controller/Auth/Auth.php index b61b04b..0bbbfde 100644 --- a/src/Controller/Auth/Auth.php +++ b/src/Controller/Auth/Auth.php @@ -1,8 +1,8 @@ $config, @@ -10,7 +10,7 @@ class AuthController { 'error' => $error, ]; - echo render_template(TEMPLATES_DIR . '/login.php', $vars); + $this->render('login.php', $vars); } function handleLogin(){ @@ -19,24 +19,25 @@ class AuthController { $error = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { - if (!validateCsrfToken($_POST['csrf_token'])) { + if (!Session::validateCsrfToken($_POST['csrf_token'])) { die('Invalid CSRF token'); } - // TODO: move into session.php $username = $_POST['username'] ?? ''; $password = $_POST['password'] ?? ''; - - $db = get_db(); + + // TODO: move into user model + $db = Util::get_db(); $stmt = $db->prepare("SELECT id, username, password_hash FROM user WHERE username = ?"); $stmt->execute([$username]); $user = $stmt->fetch(); if ($user && password_verify($password, $user['password_hash'])) { session_regenerate_id(true); + // TODO: move into session.php $_SESSION['user_id'] = $user['id']; $_SESSION['username'] = $user['username']; - $_SESSION['csrf_token'] = generate_csrf_token(true); + Session::generateCsrfToken(true); header('Location: ' . $config->basePath); exit; } else { @@ -46,9 +47,7 @@ class AuthController { } function handleLogout(){ - $_SESSION = []; - session_destroy(); - + Session::end(); $config = Config::load(); header('Location: ' . $config->basePath); exit; diff --git a/src/Controller/Controller.php b/src/Controller/Controller.php new file mode 100644 index 0000000..8d4af6c --- /dev/null +++ b/src/Controller/Controller.php @@ -0,0 +1,15 @@ +vars); + $this->render("feed/rss.php", $this->vars); } public function atom(){ - echo render_template(TEMPLATES_DIR . "/feed/atom.php", $this->vars); + $this->render("feed/atom.php", $this->vars); } } diff --git a/src/Controller/Home/Home.php b/src/Controller/Home/Home.php index eaecfae..554d7ac 100644 --- a/src/Controller/Home/Home.php +++ b/src/Controller/Home/Home.php @@ -1,5 +1,7 @@ $tickList, ]; - echo render_template(TEMPLATES_DIR . "/home.php", $vars); + $this->render("home.php", $vars); } // POST handler @@ -30,7 +32,7 @@ class HomeController{ public function handleTick(){ if ($_SERVER['REQUEST_METHOD'] === 'POST' and isset($_POST['tick'])) { // ensure that the session is valid before proceeding - if (!validateCsrfToken($_POST['csrf_token'])) { + if (!Session::validateCsrfToken($_POST['csrf_token'])) { // TODO: maybe redirect to login? Maybe with tick preserved? die('Invalid CSRF token'); } diff --git a/src/Controller/Mood/Mood.php b/src/Controller/Mood/Mood.php index 5838ea2..6352b31 100644 --- a/src/Controller/Mood/Mood.php +++ b/src/Controller/Mood/Mood.php @@ -1,5 +1,5 @@ $moodPicker, ]; - echo render_template(TEMPLATES_DIR . "/mood.php", $vars); + $this->render("mood.php", $vars); } public function handleMood(){ if ($_SERVER['REQUEST_METHOD'] === 'POST' and isset($_POST['mood'])) { // ensure that the session is valid before proceeding - if (!validateCsrfToken($_POST['csrf_token'])) { + if (!Session::validateCsrfToken($_POST['csrf_token'])) { die('Invalid CSRF token'); } diff --git a/src/Framework/Session/Session.php b/src/Framework/Session/Session.php new file mode 100644 index 0000000..cc7b85e --- /dev/null +++ b/src/Framework/Session/Session.php @@ -0,0 +1,31 @@ +"\'()]+)~i', + fn($matches) => '' . $matches[1] . '', + $safe + ); + + return $safe; + } + + // For relative time display, compare the stored time to the current time + // and display it as "X second/minutes/hours/days etc. "ago + public static function relative_time(string $tickTime): string { + $datetime = new DateTime($tickTime); + $now = new DateTime('now', $datetime->getTimezone()); + $diff = $now->diff($datetime); + + if ($diff->y > 0) { + return $diff->y . ' year' . ($diff->y > 1 ? 's' : '') . ' ago'; + } + if ($diff->m > 0) { + return $diff->m . ' month' . ($diff->m > 1 ? 's' : '') . ' ago'; + } + if ($diff->d > 0) { + return $diff->d . ' day' . ($diff->d > 1 ? 's' : '') . ' ago'; + } + if ($diff->h > 0) { + return $diff->h . ' hour' . ($diff->h > 1 ? 's' : '') . ' ago'; + } + if ($diff->i > 0) { + return $diff->i . ' minute' . ($diff->i > 1 ? 's' : '') . ' ago'; + } + return $diff->s . ' second' . ($diff->s != 1 ? 's' : '') . ' ago'; + } + + public static function verify_data_dir(string $dir, bool $allow_create = false): void { + if (!is_dir($dir)) { + if ($allow_create) { + if (!mkdir($dir, 0770, true)) { + http_response_code(500); + echo "Failed to create required directory: $dir"; + exit; + } + } else { + http_response_code(500); + echo "Required directory does not exist: $dir"; + exit; + } + } + + if (!is_writable($dir)) { + http_response_code(500); + echo "Directory is not writable: $dir"; + exit; + } + } + + // Verify that setup is complete (i.e. the databse is populated). + // Redirect to setup.php if it isn't. + public static function confirm_setup(): void { + $db = Util::get_db(); + + // Ensure required tables exist + $db->exec("CREATE TABLE IF NOT EXISTS user ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL, + display_name TEXT NOT NULL, + password_hash TEXT NOT NULL, + about TEXT NULL, + website TEXT NULL, + mood TEXT NULL + )"); + + $db->exec("CREATE TABLE IF NOT EXISTS settings ( + id INTEGER PRIMARY KEY, + site_title TEXT NOT NULL, + site_description TEXT NULL, + base_path TEXT NOT NULL, + items_per_page INTEGER NOT NULL + )"); + + // See if there's any data in the tables + $user_count = (int) $db->query("SELECT COUNT(*) FROM user")->fetchColumn(); + $settings_count = (int) $db->query("SELECT COUNT(*) FROM settings")->fetchColumn(); + + // If either table has no records and we aren't on setup.php, redirect to setup.php + if ($user_count === 0 || $settings_count === 0){ + if (basename($_SERVER['PHP_SELF']) !== 'setup.php'){ + header('Location: setup.php'); + exit; + } + } else { + // If setup is complete and we are on setup.php, redirect to index.php. + if (basename($_SERVER['PHP_SELF']) === 'setup.php'){ + header('Location: index.php'); + exit; + } + }; + } + + // TODO: Move to model base class? + public static function get_db(): PDO { + Util::verify_data_dir(DATA_DIR, true); + + try { + $db = new PDO("sqlite:" . DB_FILE); + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + } catch (PDOException $e) { + die("Database connection failed: " . $e->getMessage()); + } + + return $db; + } +} \ No newline at end of file diff --git a/src/Model/Config/Config.php b/src/Model/Config/Config.php index 1b37166..9ad9e19 100644 --- a/src/Model/Config/Config.php +++ b/src/Model/Config/Config.php @@ -10,7 +10,7 @@ class Config { // load config from sqlite database public static function load(): self { - $db = get_db(); + $db = Util::get_db(); $stmt = $db->query("SELECT site_title, site_description, base_path, items_per_page FROM settings WHERE id=1"); $row = $stmt->fetch(PDO::FETCH_ASSOC); $c = new self(); @@ -26,7 +26,7 @@ class Config { } public function save(): self { - $db = get_db(); + $db = Util::get_db(); $stmt = $db->prepare("UPDATE settings SET site_title=?, site_description=?, base_path=?, items_per_page=? WHERE id=1"); $stmt->execute([$this->siteTitle, $this->siteDescription, $this->basePath, $this->itemsPerPage]); diff --git a/src/Model/User/User.php b/src/Model/User/User.php index 44f8975..59b31e9 100644 --- a/src/Model/User/User.php +++ b/src/Model/User/User.php @@ -9,7 +9,7 @@ class User { // load user settings from sqlite database public static function load(): self { - $db = get_db(); + $db = Util::get_db(); // There's only ever one user. I'm just leaning into that. $stmt = $db->query("SELECT username, display_name, about, website, mood FROM user WHERE id=1"); @@ -28,7 +28,7 @@ class User { } public function save(): self { - $db = get_db(); + $db = Util::get_db(); $stmt = $db->prepare("UPDATE user SET username=?, display_name=?, about=?, website=?, mood=? WHERE id=1"); $stmt->execute([$this->username, $this->displayName, $this->about, $this->website, $this->mood]); @@ -39,7 +39,7 @@ class User { // Making this a separate function to avoid // loading the password into memory public function set_password(string $password): void { - $db = get_db(); + $db = Util::get_db(); $hash = password_hash($password, PASSWORD_DEFAULT); $stmt = $db->prepare("UPDATE user SET password_hash=? WHERE id=1"); diff --git a/src/View/Home/Home.php b/src/View/Home/Home.php index 4ef7694..8bdcfff 100644 --- a/src/View/Home/Home.php +++ b/src/View/Home/Home.php @@ -11,8 +11,8 @@ class HomeView {
= $user->about ?>
-Website: = escape_and_linkify($user->website) ?>
+Website: = Util::escape_and_linkify($user->website) ?>