From 20129d9fcfb196672789dcb8da9e75970e80a9e9 Mon Sep 17 00:00:00 2001 From: Greg Sarjeant <1686767+gsarjeant@users.noreply.github.com> Date: Mon, 2 Jun 2025 20:33:32 -0400 Subject: [PATCH] Convert login and mood pages to MVC pattern. --- public/index.php | 45 ++++-- public/save_tick.php | 29 ---- src/Controller/Home/Home.php | 71 ++++++---- src/Controller/Login/Login.php | 48 +++++++ src/Controller/Mood/Mood.php | 247 +++++++++++++++++++++++++++++++++ src/View/Mood/Mood.php | 43 ++++++ src/lib/mood.php | 53 ------- templates/home.php | 10 +- templates/login.php | 44 +----- templates/mood.php | 19 +++ templates/set_mood.php | 44 ------ templates/tick.php | 5 - 12 files changed, 450 insertions(+), 208 deletions(-) delete mode 100644 public/save_tick.php create mode 100644 src/Controller/Login/Login.php create mode 100644 src/Controller/Mood/Mood.php create mode 100644 src/View/Mood/Mood.php delete mode 100644 src/lib/mood.php create mode 100644 templates/mood.php delete mode 100644 templates/set_mood.php diff --git a/public/index.php b/public/index.php index 308f76d..a7a9daf 100644 --- a/public/index.php +++ b/public/index.php @@ -1,5 +1,11 @@ basePath) === 0) { $path = trim($path, '/'); -function route($pattern, $callback, $methods = ['GET']) { +function route(string $pattern, string $controller, array $methods = ['GET']) { global $path, $method; if (!in_array($method, $methods)) { return false; } - // Convert route pattern to regex $pattern = preg_replace('/\{([^}]+)\}/', '([^/]+)', $pattern); $pattern = '#^' . $pattern . '$#'; if (preg_match($pattern, $path, $matches)) { - array_shift($matches); // Remove full match - call_user_func_array($callback, $matches); + array_shift($matches); + + if (strpos($controller, '@') !== false) { + [$className, $methodName] = explode('@', $controller); + } else { + // Default to 'index' method if no method specified + $className = $controller; + $methodName = 'index'; + } + $instance = new $className(); + call_user_func_array([$instance, $methodName], $matches); return true; } @@ -78,7 +92,22 @@ function route($pattern, $callback, $methods = ['GET']) { header('Content-Type: text/html; charset=utf-8'); // routes -route('', function(){ - $hc = new HomeController(); - echo $hc->render(); -}); +$routes = [ + ['', 'HomeController'], + ['', 'HomeController@tick', ['POST']], + ['login', 'LoginController'], + ['login', 'LoginController@login', ['POST']], + ['mood', 'MoodController'], + ['mood', 'MoodController@set_mood', ['POST']], + +]; + +foreach ($routes as $routeConfig) { + $pattern = $routeConfig[0]; + $controller = $routeConfig[1]; + $methods = $routeConfig[2] ?? ['GET']; + + if (route($pattern, $controller, $methods)) { + break; + } +}; \ No newline at end of file diff --git a/public/save_tick.php b/public/save_tick.php deleted file mode 100644 index d0b21d4..0000000 --- a/public/save_tick.php +++ /dev/null @@ -1,29 +0,0 @@ -basePath); -exit; \ No newline at end of file diff --git a/src/Controller/Home/Home.php b/src/Controller/Home/Home.php index 6bcfe6d..69924a3 100644 --- a/src/Controller/Home/Home.php +++ b/src/Controller/Home/Home.php @@ -1,5 +1,52 @@ itemsPerPage; + $offset = ($page - 1) * $limit; + $ticks = iterator_to_array(stream_ticks($limit, $offset)); + + $view = new HomeView(); + $tickList = $view->renderTicksSection($config->siteDescription, $ticks, $page, $limit); + + $vars = [ + 'isLoggedIn' => $isLoggedIn, + 'config' => $config, + 'user' => $user, + 'tickList' => $tickList, + ]; + + echo render_template(TEMPLATES_DIR . "/home.php", $vars); + } + + // POST handler + // Saves the tick and reloads the homepage + public function tick(){ + if ($_SERVER['REQUEST_METHOD'] === 'POST' and isset($_POST['tick'])) { + // ensure that the session is valid before proceeding + if (!validateCsrfToken($_POST['csrf_token'])) { + // TODO: maybe redirect to login? Maybe with tick preserved? + die('Invalid CSRF token'); + } + + // save the tick + save_tick($_POST['tick']); + } + + // get the config + $config = Config::load(); + + // redirect to the index (will show the latest tick if one was sent) + header('Location: ' . $config->basePath); + exit; + } + private function stream_ticks(int $limit, int $offset = 0): Generator { $tick_files = glob(TICKS_DIR . '/*/*/*.txt'); usort($tick_files, fn($a, $b) => strcmp($b, $a)); // sort filenames in reverse chronological order @@ -50,28 +97,4 @@ class HomeController{ } } } - - public function render(){ - $page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1; - $isLoggedIn = isset($_SESSION['user_id']); - $config = Config::load(); - $user = User::load(); - - $limit = $config->itemsPerPage; - $offset = ($page - 1) * $limit; - $ticks = iterator_to_array(stream_ticks($limit, $offset)); - - $view = new HomeView(); - $tickList = $view->renderTicksSection($config->siteDescription, $ticks, $page, $limit); - - $vars = [ - 'isLoggedIn' => $isLoggedIn, - 'config' => $config, - 'user' => $user, - 'tickList' => $tickList, - ]; - - echo render_template(TEMPLATES_DIR . "/home.php", $vars); - - } } \ No newline at end of file diff --git a/src/Controller/Login/Login.php b/src/Controller/Login/Login.php new file mode 100644 index 0000000..dda1ee6 --- /dev/null +++ b/src/Controller/Login/Login.php @@ -0,0 +1,48 @@ + $config, + 'csrf_token' => $csrf_token, + 'error' => $error, + ]; + + echo render_template(TEMPLATES_DIR . '/login.php', $vars); + } + + function login(){ + $config = Config::load(); + + $error = ''; + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + if (!validateCsrfToken($_POST['csrf_token'])) { + die('Invalid CSRF token'); + } + + // TODO: move into session.php + $username = $_POST['username'] ?? ''; + $password = $_POST['password'] ?? ''; + + $db = 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); + $_SESSION['user_id'] = $user['id']; + $_SESSION['username'] = $user['username']; + header('Location: ' . $config->basePath); + exit; + } else { + $error = 'Invalid username or password'; + } + } + + $csrf_token = generateCsrfToken(); + } +} \ No newline at end of file diff --git a/src/Controller/Mood/Mood.php b/src/Controller/Mood/Mood.php new file mode 100644 index 0000000..04d0715 --- /dev/null +++ b/src/Controller/Mood/Mood.php @@ -0,0 +1,247 @@ +render_mood_picker(self::get_emojis_with_labels(), $user->mood); + + $vars = [ + 'config' => $config, + 'moodPicker' => $moodPicker, + ]; + + echo render_template(TEMPLATES_DIR . "/mood.php", $vars); + } + + public function set_mood(){ + if ($_SERVER['REQUEST_METHOD'] === 'POST' and isset($_POST['mood'])) { + // ensure that the session is valid before proceeding + if (!validateCsrfToken($_POST['csrf_token'])) { + die('Invalid CSRF token'); + } + + // Get the data we need + $config = Config::load(); + $user = User::load(); + $mood = $_POST['mood']; + + // set the mood + $user->mood = $mood; + $user = $user->save(); + + // go back to the index and show the updated mood + header('Location: ' . $config->basePath); + //exit; + } + } + + private static function get_emojis_with_labels(): array { + return [ + 'faces' => [ + ['๐Ÿ˜€', 'grinning face'], + ['๐Ÿ˜„', 'grinning face with smiling eyes'], + ['๐Ÿ˜', 'beaming face with smiling eyes'], + ['๐Ÿ˜†', 'grinning squinting face'], + ['๐Ÿ˜…', 'grinning face with sweat'], + ['๐Ÿ˜‚', 'face with tears of joy'], + ['๐Ÿคฃ', 'rolling on the floor laughing'], + ['๐Ÿ˜Š', 'smiling face with smiling eyes'], + ['๐Ÿ˜‡', 'smiling face with halo'], + ['๐Ÿ™‚', 'slightly smiling face'], + ['๐Ÿ™ƒ', 'upside-down face'], + ['๐Ÿ˜‰', 'winking face'], + ['๐Ÿ˜Œ', 'relieved face'], + ['๐Ÿ˜', 'smiling face with heart-eyes'], + ['๐Ÿฅฐ', 'smiling face with hearts'], + ['๐Ÿ˜˜', 'face blowing a kiss'], + ['๐Ÿ˜—', 'kissing face'], + ['๐Ÿ˜š', 'kissing face with closed eyes'], + ['๐Ÿ˜‹', 'face savoring food'], + ['๐Ÿ˜›', 'face with tongue'], + ['๐Ÿ˜œ', 'winking face with tongue'], + ['๐Ÿ˜', 'squinting face with tongue'], + ['๐Ÿคช', 'zany face'], + ['๐Ÿฆธ', 'superhero'], + ['๐Ÿฆน', 'supervillain'], + ['๐Ÿง™', 'mage'], + ['๐Ÿง›', 'vampire'], + ['๐ŸงŸ', 'zombie'], + ['๐Ÿงž', 'genie'], + ], + 'gestures' => [ + ['๐Ÿ‘‹', 'waving hand'], + ['๐Ÿ––', 'vulcan salute'], + ['๐Ÿ‘Œ', 'OK hand'], + ['๐ŸคŒ', 'pinched fingers'], + ['โœŒ๏ธ', 'victory hand'], + ['๐Ÿคž', 'crossed fingers'], + ['๐ŸคŸ', 'love-you gesture'], + ['๐Ÿค˜', 'sign of the horns'], + ['๐Ÿค™', 'call me hand'], + ['๐Ÿ‘', 'thumbs up'], + ['๐Ÿ‘Ž', 'thumbs down'], + ['โœŠ', 'raised fist'], + ['๐Ÿ‘Š', 'oncoming fist'], + ], + 'nature' => [ + ['โ˜€๏ธ', 'sun'], + ['โ›…', 'sun behind cloud'], + ['๐ŸŒง๏ธ', 'cloud with rain'], + ['๐ŸŒจ๏ธ', 'cloud with snow'], + ['โ„๏ธ', 'snowflake'], + ['๐ŸŒฉ๏ธ', 'cloud with lightning'], + ['๐ŸŒช๏ธ', 'tornado'], + ['๐ŸŒˆ', 'rainbow'], + ['๐Ÿ”ฅ', 'fire'], + ['๐Ÿ’ง', 'droplet'], + ['๐ŸŒŠ', 'water wave'], + ['๐ŸŒซ๏ธ', 'fog'], + ['๐ŸŒฌ๏ธ', 'wind face'], + ['๐Ÿ‚', 'fallen leaf'], + ['๐ŸŒต', 'cactus'], + ['๐ŸŒด', 'palm tree'], + ['๐ŸŒธ', 'cherry blossom'], + ], + 'animals' => [ + ['๐Ÿถ', 'dog face'], + ['๐Ÿฑ', 'cat face'], + ['๐Ÿญ', 'mouse face'], + ['๐Ÿน', 'hamster face'], + ['๐Ÿฐ', 'rabbit face'], + ['๐ŸฆŠ', 'fox face'], + ['๐Ÿป', 'bear face'], + ['๐Ÿผ', 'panda face'], + ['๐Ÿจ', 'koala'], + ['๐Ÿฏ', 'tiger face'], + ['๐Ÿฆ', 'lion face'], + ['๐Ÿฎ', 'cow face'], + ['๐Ÿท', 'pig face'], + ['๐Ÿธ', 'frog face'], + ['๐Ÿต', 'monkey face'], + ['๐Ÿ”', 'chicken'], + ['๐Ÿง', 'penguin'], + ['๐Ÿฆ', 'bird'], + ['๐Ÿฃ', 'hatching chick'], + ['๐Ÿบ', 'wolf face'], + ['๐Ÿฆ„', 'unicorn face'], + ], + 'hearts' => [ + ['โค๏ธ', 'red heart'], + ['๐Ÿงก', 'orange heart'], + ['๐Ÿ’›', 'yellow heart'], + ['๐Ÿ’š', 'green heart'], + ['๐Ÿ’™', 'blue heart'], + ['๐Ÿ’œ', 'purple heart'], + ['๐Ÿ–ค', 'black heart'], + ['๐Ÿค', 'white heart'], + ['๐ŸคŽ', 'brown heart'], + ['๐Ÿ’–', 'sparkling heart'], + ['๐Ÿ’—', 'growing heart'], + ['๐Ÿ’“', 'beating heart'], + ['๐Ÿ’ž', 'revolving hearts'], + ['๐Ÿ’•', 'two hearts'], + ['๐Ÿ’˜', 'heart with arrow'], + ['๐Ÿ’', 'heart with ribbon'], + ['๐Ÿ’”', 'broken heart'], + ['โฃ๏ธ', 'heart exclamation'], + ], + 'activities' => [ + ['๐Ÿšด', 'person biking'], + ['๐Ÿšต', 'person mountain biking'], + ['๐Ÿƒ', 'person running'], + ['๐Ÿ‹๏ธ', 'person lifting weights'], + ['๐ŸŠ', 'person swimming'], + ['๐Ÿ„', 'person surfing'], + ['๐Ÿšฃ', 'person rowing boat'], + ['๐Ÿคธ', 'person cartwheeling'], + ['๐Ÿง˜', 'person in lotus position'], + ['๐Ÿง—', 'person climbing'], + ['๐Ÿ•๏ธ', 'camping'], + ['๐ŸŽฃ', 'fishing pole'], + ['๐ŸŽฟ', 'skis'], + ['๐Ÿ‚', 'snowboarder'], + ['๐Ÿ›น', 'skateboard'], + ['๐Ÿงบ', 'basket'], + ['๐ŸŽฏ', 'bullseye'], + ], + 'hobbies' => [ + ['๐Ÿ“š', 'books'], + ['๐Ÿ“–', 'open book'], + ['๐ŸŽง', 'headphone'], + ['๐ŸŽต', 'musical note'], + ['๐ŸŽค', 'microphone'], + ['๐ŸŽท', 'saxophone'], + ['๐ŸŽธ', 'guitar'], + ['๐ŸŽน', 'musical keyboard'], + ['๐ŸŽบ', 'trumpet'], + ['๐ŸŽป', 'violin'], + ['๐Ÿช•', 'banjo'], + ['โœ๏ธ', 'writing hand'], + ['๐Ÿ“', 'memo'], + ['๐Ÿ“ท', 'camera'], + ['๐ŸŽจ', 'artist palette'], + ['๐Ÿงต', 'thread'], + ['๐Ÿงถ', 'yarn'], + ['๐Ÿชก', 'sewing needle'], + ['๐Ÿ“น', 'video camera'], + ['๐ŸŽฌ', 'clapper board'], + ], + 'food' => [ + ['๐ŸŽ', 'red apple'], + ['๐ŸŒ', 'banana'], + ['๐Ÿ‡', 'grapes'], + ['๐Ÿ“', 'strawberry'], + ['๐Ÿ‰', 'watermelon'], + ['๐Ÿ', 'pineapple'], + ['๐Ÿฅญ', 'mango'], + ['๐Ÿ‘', 'peach'], + ['๐Ÿ’', 'cherries'], + ['๐Ÿ…', 'tomato'], + ['๐Ÿฅฆ', 'broccoli'], + ['๐Ÿฅ•', 'carrot'], + ['๐ŸŒฝ', 'ear of corn'], + ['๐Ÿฅ”', 'potato'], + ['๐Ÿž', 'bread'], + ['๐Ÿฅ', 'croissant'], + ['๐Ÿฅ–', 'baguette bread'], + ['๐Ÿง€', 'cheese wedge'], + ['๐Ÿ•', 'pizza'], + ['๐Ÿ”', 'hamburger'], + ['๐ŸŸ', 'french fries'], + ['๐ŸŒญ', 'hot dog'], + ['๐Ÿฃ', 'sushi'], + ], + 'vibes' => [ + ['๐Ÿ’ค', 'zzz'], + ['๐Ÿคฏ', 'exploding head'], + ['๐Ÿ˜ฑ', 'face screaming in fear'], + ['๐Ÿฅต', 'hot face'], + ['๐Ÿฅถ', 'cold face'], + ['๐Ÿคฌ', 'face with symbols on mouth'], + ['๐Ÿคจ', 'face with raised eyebrow'], + ], + 'tech' => [ + ['๐Ÿ’ป', 'laptop'], + ['๐Ÿ“ž', 'telephone receiver'], + ['๐Ÿ”‹', 'battery'], + ['๐Ÿ’ฟ', 'optical disk'], + ['๐Ÿ•น๏ธ', 'joystick'], + ['๐Ÿ”', 'magnifying glass tilted left'], + ['๐Ÿ“ˆ', 'chart increasing'], + ], + 'travel' => [ + ['โœˆ๏ธ', 'airplane'], + ['๐Ÿš—', 'automobile'], + ['๐Ÿš•', 'taxi'], + ['๐Ÿšฒ', 'bicycle'], + ['๐Ÿ›ด', 'kick scooter'], + ['โ›ต', 'sailboat'], + ], + + //'custom' => get_user_emojis($db), + ]; + } +} +?> \ No newline at end of file diff --git a/src/View/Mood/Mood.php b/src/View/Mood/Mood.php new file mode 100644 index 0000000..38c1473 --- /dev/null +++ b/src/View/Mood/Mood.php @@ -0,0 +1,43 @@ +mood; + + ob_start(); + ?> + + $emojis): ?> +
+ + + + +
+ +
+ + render_emoji_tabs($emojiGroups, $currentMood) ?> + +
+ \ No newline at end of file diff --git a/src/lib/mood.php b/src/lib/mood.php deleted file mode 100644 index 3c3904d..0000000 --- a/src/lib/mood.php +++ /dev/null @@ -1,53 +0,0 @@ -mood = $mood; - $user = $user->save(); - header("Location: $config->basePath"); - exit; -} - -function render_emoji_tabs(): string { - $user = User::load(); - $emoji_groups = get_emojis_with_labels(); - $selected_emoji = $user->mood; - - ob_start(); - ?> - - $emojis): ?> -
- - - - -
- -
- - - -
- basePath ?>rss">rss atom - login + login - admin - logout + admin + logout
@@ -33,14 +33,14 @@
Current mood: mood ?> - Change + Change

-
+ diff --git a/templates/login.php b/templates/login.php index 22d6ffe..e338ca5 100644 --- a/templates/login.php +++ b/templates/login.php @@ -1,42 +1,6 @@ -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); - $_SESSION['user_id'] = $user['id']; - $_SESSION['username'] = $user['username']; - header('Location: ' . $config->basePath); - exit; - } else { - $error = 'Invalid username or password'; - } -} - -$csrf_token = generateCsrfToken(); -?> + + + @@ -51,7 +15,7 @@ $csrf_token = generateCsrfToken();

- +

diff --git a/templates/mood.php b/templates/mood.php new file mode 100644 index 0000000..4a037fa --- /dev/null +++ b/templates/mood.php @@ -0,0 +1,19 @@ + + + + + + + <?= $config->siteTitle ?> + + + + + +

How are you feeling?

+ + + + Back to home + + \ No newline at end of file diff --git a/templates/set_mood.php b/templates/set_mood.php deleted file mode 100644 index 44da373..0000000 --- a/templates/set_mood.php +++ /dev/null @@ -1,44 +0,0 @@ -basePath); - exit; -} -?> - - - - - <?= $config->siteTitle ?> - - - - - -

How are you feeling?

- - - - Back to home - - \ No newline at end of file diff --git a/templates/tick.php b/templates/tick.php index e99d2b0..85b66a8 100644 --- a/templates/tick.php +++ b/templates/tick.php @@ -1,9 +1,4 @@