Clean up emoji model and sidebar. Fix some validation errors. Allow mood to be cleared.

This commit is contained in:
Greg Sarjeant 2025-06-14 13:05:18 -04:00
parent 3b8d54460c
commit 427558bd8c
12 changed files with 124 additions and 102 deletions

View File

@ -163,7 +163,7 @@ function create_tables(): void {
)"); )");
// mood table // mood table
$db->exec("CREATE TABLE IF NOT EXISTS mood ( $db->exec("CREATE TABLE IF NOT EXISTS emoji(
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
emoji TEXT UNIQUE NOT NULL, emoji TEXT UNIQUE NOT NULL,
description TEXT NOT NULL description TEXT NOT NULL
@ -185,7 +185,7 @@ function validate_tables(): void {
$appTables[] = "settings"; $appTables[] = "settings";
$appTables[] = "user"; $appTables[] = "user";
$appTables[] = "css"; $appTables[] = "css";
$appTables[] = "mood"; $appTables[] = "emoji";
$db = get_db(); $db = get_db();

View File

@ -77,6 +77,10 @@ fieldset.emoji-group {
gap: 0.3em; gap: 0.3em;
} }
h1.site-description {
font-size: 1.3em;
}
.delete-emoji-fieldset .fieldset-items { .delete-emoji-fieldset .fieldset-items {
display: block; display: block;
grid-template-columns: none; grid-template-columns: none;
@ -293,7 +297,7 @@ label.description {
font-size: 1.0em; font-size: 1.0em;
} }
.pagination a { .tick-pagination a {
margin: 0 5px; margin: 0 5px;
text-decoration: none; text-decoration: none;
} }

View File

@ -0,0 +1,73 @@
<?php
class EmojiController extends Controller {
// Shows the custom emoji management page
public function index(){
global $config;
$emojiList = EmojiModel::loadAll();
$vars = [
'config' => $config,
'emojiList' => $emojiList,
];
$this->render("emoji.php", $vars);
}
public function handlePost(): void {
global $config;
switch ($_POST['action']) {
case 'add':
$emoji = trim($_POST['emoji']);
$description = trim($_POST['emoji-description']);
$this->handleAdd($emoji, $description);
break;
case 'delete':
if (!empty($_POST['delete_emoji_ids'])){
$this->handleDelete();
}
break;
}
header('Location: ' . $config->basePath . 'admin/emoji');
exit;
}
public function handleAdd(string $emoji, ?string $description=null): void {
// Validate 1 visible character in the emoji
if (extension_loaded('mbstring')) {
// TODO - log a warning if mbstring isn't loaded
$charCount = mb_strlen($emoji, 'UTF-8');
if ($charCount !== 1) {
// TODO - handle error
return;
}
}
// Validate the emoji is actually an emoji
$emojiPattern = '/^[\x{1F000}-\x{1F9FF}\x{2600}-\x{26FF}\x{2700}-\x{27BF}\x{1F600}-\x{1F64F}\x{1F300}-\x{1F5FF}\x{1F680}-\x{1F6FF}\x{1F1E0}-\x{1F1FF}\x{1F900}-\x{1F9FF}\x{1FA70}-\x{1FAFF}]$/u';
if (!preg_match($emojiPattern, $emoji)) {
// TODO - handle error
return;
}
// emojis should have more bytes than characters
$byteCount = strlen($emoji);
if ($byteCount <= 1) {
// TODO - handle error
return;
}
// It looks like an emoji. Let's add it.
EmojiModel::add($emoji, $description);
}
public function handleDelete(): void {
$ids = $_POST['delete_emoji_ids'];
if (!empty($ids)) {
EmojiModel::delete($ids);
}
}
}

View File

@ -15,80 +15,8 @@
$this->render("mood.php", $vars); $this->render("mood.php", $vars);
} }
// Shows the custom emoji management page public function handlePost(){
public function showCustomEmoji(){ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
global $config;
$emojiList = MoodModel::loadAll();
$vars = [
'config' => $config,
'emojiList' => $emojiList,
];
$this->render("emoji.php", $vars);
}
public function handlePost(): void {
global $config;
switch ($_POST['action']) {
case 'add':
$emoji = trim($_POST['emoji']);
$description = trim($_POST['emoji-description']);
$this->handleAdd($emoji, $description);
break;
case 'delete':
if (!empty($_POST['delete_emoji_ids'])){
$this->handleDelete();
}
break;
}
header('Location: ' . $config->basePath . 'admin/emoji');
exit;
}
public function handleAdd(string $emoji, ?string $description=null): void {
// Validate 1 visible character in the emoji
if (extension_loaded('mbstring')) {
// TODO - log a warning if mbstring isn't loaded
$charCount = mb_strlen($emoji, 'UTF-8');
if ($charCount !== 1) {
// TODO - handle error
return;
}
}
// Validate the emoji is actually an emoji
$emojiPattern = '/^[\x{1F000}-\x{1F9FF}\x{2600}-\x{26FF}\x{2700}-\x{27BF}\x{1F600}-\x{1F64F}\x{1F300}-\x{1F5FF}\x{1F680}-\x{1F6FF}\x{1F1E0}-\x{1F1FF}\x{1F900}-\x{1F9FF}\x{1FA70}-\x{1FAFF}]$/u';
if (!preg_match($emojiPattern, $emoji)) {
// TODO - handle error
return;
}
// emojis should have more bytes than characters
$byteCount = strlen($emoji);
if ($byteCount <= 1) {
// TODO - handle error
return;
}
// It looks like an emoji. Let's add it.
MoodModel::add($emoji, $description);
}
public function handleDelete(): void {
$ids = $_POST['delete_emoji_ids'];
if (!empty($ids)) {
$moodModel = new MoodModel();
$moodModel->delete($ids);
}
}
public function handleSetMood(){
if ($_SERVER['REQUEST_METHOD'] === 'POST' and isset($_POST['mood'])) {
// ensure that the session is valid before proceeding // ensure that the session is valid before proceeding
if (!Session::validateCsrfToken($_POST['csrf_token'])) { if (!Session::validateCsrfToken($_POST['csrf_token'])) {
die('Invalid CSRF token'); die('Invalid CSRF token');
@ -97,9 +25,17 @@
// Get the data we need // Get the data we need
global $config; global $config;
global $user; global $user;
$mood = $_POST['mood'];
// set the mood switch ($_POST['action']){
case 'set':
$mood = $_POST['mood'];
break;
case 'clear':
$mood = '';
break;
}
// set or clear the mood
$user->mood = $mood; $user->mood = $mood;
$user = $user->save(); $user = $user->save();
@ -110,7 +46,7 @@
} }
private static function getEmojisWithLabels(): array { private static function getEmojisWithLabels(): array {
$customEmoji = MoodModel::loadAll(); $customEmoji = EmojiModel::loadAll();
if (!empty($customEmoji)){ if (!empty($customEmoji)){
$custom = []; $custom = [];

View File

@ -10,15 +10,15 @@ class Router {
['admin', 'AdminController@handleSave', ['POST']], ['admin', 'AdminController@handleSave', ['POST']],
['admin/css', 'CssController'], ['admin/css', 'CssController'],
['admin/css', 'CssController@handlePost', ['POST']], ['admin/css', 'CssController@handlePost', ['POST']],
['admin/emoji', 'MoodController@showCustomEmoji'], ['admin/emoji', 'EmojiController'],
['admin/emoji', 'MoodController@handlePost', ['POST']], ['admin/emoji', 'EmojiController@handlePost', ['POST']],
['feed/rss', 'FeedController@rss'], ['feed/rss', 'FeedController@rss'],
['feed/atom', 'FeedController@atom'], ['feed/atom', 'FeedController@atom'],
['login', 'AuthController@showLogin'], ['login', 'AuthController@showLogin'],
['login', 'AuthController@handleLogin', ['POST']], ['login', 'AuthController@handleLogin', ['POST']],
['logout', 'AuthController@handleLogout', ['GET', 'POST']], ['logout', 'AuthController@handleLogout', ['GET', 'POST']],
['mood', 'MoodController'], ['mood', 'MoodController'],
['mood', 'MoodController@handleSetMood', ['POST']], ['mood', 'MoodController@handlePost', ['POST']],
['setup', 'AdminController@showSetup'], ['setup', 'AdminController@showSetup'],
['setup', 'AdminController@handleSetup', ['POST']], ['setup', 'AdminController@handleSetup', ['POST']],
['tick/{y}/{m}/{d}/{h}/{i}/{s}', 'TickController'], ['tick/{y}/{m}/{d}/{h}/{i}/{s}', 'TickController'],

View File

@ -1,12 +1,12 @@
<?php <?php
// welp this model is overkill // welp this model is overkill
class MoodModel{ class EmojiModel{
// This isn't memory-efficient, // This isn't memory-efficient,
// but I think it'll be fine on this app's scales. // but I think it'll be fine on this app's scales.
public static function loadAll(): array { public static function loadAll(): array {
global $db; global $db;
$stmt = $db->query("SELECT id, emoji, description FROM mood"); $stmt = $db->query("SELECT id, emoji, description FROM emoji");
return $stmt->fetchAll(PDO::FETCH_ASSOC); return $stmt->fetchAll(PDO::FETCH_ASSOC);
} }
@ -15,7 +15,7 @@ class MoodModel{
public static function add(string $emoji, ?string $description): void{ public static function add(string $emoji, ?string $description): void{
global $db; global $db;
$stmt = $db->prepare("INSERT INTO mood (emoji, description) VALUES (?, ?)"); $stmt = $db->prepare("INSERT INTO emoji (emoji, description) VALUES (?, ?)");
$stmt->execute([$emoji, $description]); $stmt->execute([$emoji, $description]);
} }
@ -23,7 +23,7 @@ class MoodModel{
global $db; global $db;
$placeholders = rtrim(str_repeat('?,', count($idsToDelete)), ','); $placeholders = rtrim(str_repeat('?,', count($idsToDelete)), ',');
$stmt = $db->prepare("DELETE FROM mood WHERE id IN ($placeholders)"); $stmt = $db->prepare("DELETE FROM emoji WHERE id IN ($placeholders)");
$stmt->execute($idsToDelete); $stmt->execute($idsToDelete);
} }
} }

View File

@ -4,8 +4,8 @@ class HomeView {
ob_start(); ob_start();
?> ?>
<section id="ticks" class="home-ticks"> <main id="ticks" class="home-main">
<div class="home-ticks-list"> <div class="tick-feed">
<?php foreach ($ticks as $tick): ?> <?php foreach ($ticks as $tick): ?>
<article class="tick"> <article class="tick">
<div class="tick-time"><?= htmlspecialchars(Util::relative_time($tick['timestamp'])) ?></div> <div class="tick-time"><?= htmlspecialchars(Util::relative_time($tick['timestamp'])) ?></div>
@ -13,7 +13,7 @@ class HomeView {
</article> </article>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<div class="home-ticks-pagination"> <div class="tick-pagination">
<?php if ($page > 1): ?> <?php if ($page > 1): ?>
<a href="?page=<?= $page - 1 ?>">&laquo; Newer</a> <a href="?page=<?= $page - 1 ?>">&laquo; Newer</a>
<?php endif; ?> <?php endif; ?>
@ -21,7 +21,7 @@ class HomeView {
<a href="?page=<?= $page + 1 ?>">Older &raquo;</a> <a href="?page=<?= $page + 1 ?>">Older &raquo;</a>
<?php endif; ?> <?php endif; ?>
</div> </div>
</section> </main>
<?php return ob_get_clean(); <?php return ob_get_clean();
} }

View File

@ -33,7 +33,10 @@ class MoodView {
<form method="post" class="emoji-form"> <form method="post" class="emoji-form">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>"> <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<?= $this->render_emoji_groups($emojiGroups, $currentMood) ?> <?= $this->render_emoji_groups($emojiGroups, $currentMood) ?>
<button type="submit">Set the mood</button> <div class="button-group">
<button type="submit" name="action" value="set">Set the mood</button>
<button type="submit" name="action" value="clear" class="clear-button">Clear mood</button>
</div>
</form> </form>
<?php <?php

View File

@ -14,8 +14,8 @@ echo '<?xml version="1.0" encoding="utf-8"?>' . "\n";
<link rel="self" <link rel="self"
type="application/atom+xml" type="application/atom+xml"
title="<?php echo htmlspecialchars($config->siteTitle) ?> Atom Feed" title="<?php echo htmlspecialchars($config->siteTitle) ?> Atom Feed"
href="<?php echo htmlspecialchars($siteUrl . $basePath) ?>feed/atom" /> href="<?php echo htmlspecialchars($siteUrl . $basePath) ?>feed/atom">
<link rel="alternate" href="<?= $siteUrl ?>"/> <link rel="alternate" href="<?= $siteUrl ?>">
<updated><?= $updated ?></updated> <updated><?= $updated ?></updated>
<id><?= $siteUrl ?></id> <id><?= $siteUrl ?></id>
<author> <author>

View File

@ -9,15 +9,15 @@ echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
?> ?>
<rss version="2.0"> <rss version="2.0">
<channel> <channel>
<title>My tkr</title> <title><?php echo htmlspecialchars($config->siteTitle) ?> RSS Feed</title>
<link rel="self" <link rel="self"
type="application/rss+xml" type="application/rss+xml"
title="<?php echo htmlspecialchars($config->siteTitle) ?> RSS Feed" title="<?php echo htmlspecialchars($config->siteTitle) ?> RSS Feed"
href="<?php echo htmlspecialchars($config->baseUrl . $config->basePath)?>feed/rss/" /> href="<?php echo htmlspecialchars($config->baseUrl . $config->basePath)?>feed/rss/">
<link rel="alternate" <link rel="alternate"
type="text/html" type="text/html"
href="<?php echo htmlspecialchars($config->baseUrl . $config->basePath) ?>" /> href="<?php echo htmlspecialchars($config->baseUrl . $config->basePath) ?>">
<description>My tkr</description> <description><?php echo htmlspecialchars($config->siteDescription) ?></description>
<language>en-us</language> <language>en-us</language>
<lastBuildDate><?php echo date(DATE_RSS); ?></lastBuildDate> <lastBuildDate><?php echo date(DATE_RSS); ?></lastBuildDate>
<?php foreach ($ticks as $tick): <?php foreach ($ticks as $tick):

View File

@ -17,11 +17,11 @@
<link rel="alternate" <link rel="alternate"
type="application/rss+xml" type="application/rss+xml"
title="<?php echo htmlspecialchars($config->siteTitle) ?> RSS Feed" title="<?php echo htmlspecialchars($config->siteTitle) ?> RSS Feed"
href="<?php echo htmlspecialchars($config->baseUrl . $config->basePath)?>feed/rss/" /> href="<?php echo htmlspecialchars($config->baseUrl . $config->basePath)?>feed/rss/">
<link rel="alternate" <link rel="alternate"
type="application/atom+xml" type="application/atom+xml"
title="<?php echo htmlspecialchars($config->siteTitle) ?> Atom Feed" title="<?php echo htmlspecialchars($config->siteTitle) ?> Atom Feed"
href="<?php echo htmlspecialchars($config->baseUrl . $config->basePath)?>feed/atom/" /> href="<?php echo htmlspecialchars($config->baseUrl . $config->basePath)?>feed/atom/">
</head> </head>
<body> <body>
<?php include TEMPLATES_DIR . '/partials/navbar.php'?> <?php include TEMPLATES_DIR . '/partials/navbar.php'?>

View File

@ -5,10 +5,15 @@
<div class="home-container"> <div class="home-container">
<section id="sidebar" class="home-sidebar"> <section id="sidebar" class="home-sidebar">
<div class="home-header"> <div class="home-header">
<h2 class="site-description"><?= $config->siteDescription ?></h2> <h1 class="site-description"><?= $config->siteDescription ?></h1>
</div> </div>
<p><?= $user->about ?></p> <?php if (!empty($user->about)): ?>
<p>About: <?= $user->about ?></p>
<?php endif ?>
<?php if (!empty($user->website)): ?>
<p>Website: <?= Util::escape_and_linkify($user->website) ?></p> <p>Website: <?= Util::escape_and_linkify($user->website) ?></p>
<?php endif ?>
<?php if (!empty($user->mood) || Session::isLoggedIn()): ?>
<div class="profile-row"> <div class="profile-row">
<div class="mood-bar"> <div class="mood-bar">
<span>Current mood: <?= $user->mood ?></span> <span>Current mood: <?= $user->mood ?></span>
@ -17,6 +22,7 @@
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>
<?php endif; ?>
<?php if (Session::isLoggedIn()): ?> <?php if (Session::isLoggedIn()): ?>
<hr/> <hr/>
<div class="profile-row"> <div class="profile-row">