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

View File

@ -77,6 +77,10 @@ fieldset.emoji-group {
gap: 0.3em;
}
h1.site-description {
font-size: 1.3em;
}
.delete-emoji-fieldset .fieldset-items {
display: block;
grid-template-columns: none;
@ -293,7 +297,7 @@ label.description {
font-size: 1.0em;
}
.pagination a {
.tick-pagination a {
margin: 0 5px;
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);
}
// Shows the custom emoji management page
public function showCustomEmoji(){
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'])) {
public function handlePost(){
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// ensure that the session is valid before proceeding
if (!Session::validateCsrfToken($_POST['csrf_token'])) {
die('Invalid CSRF token');
@ -97,9 +25,17 @@
// Get the data we need
global $config;
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 = $user->save();
@ -110,7 +46,7 @@
}
private static function getEmojisWithLabels(): array {
$customEmoji = MoodModel::loadAll();
$customEmoji = EmojiModel::loadAll();
if (!empty($customEmoji)){
$custom = [];

View File

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

View File

@ -1,12 +1,12 @@
<?php
// welp this model is overkill
class MoodModel{
class EmojiModel{
// This isn't memory-efficient,
// but I think it'll be fine on this app's scales.
public static function loadAll(): array {
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);
}
@ -15,7 +15,7 @@ class MoodModel{
public static function add(string $emoji, ?string $description): void{
global $db;
$stmt = $db->prepare("INSERT INTO mood (emoji, description) VALUES (?, ?)");
$stmt = $db->prepare("INSERT INTO emoji (emoji, description) VALUES (?, ?)");
$stmt->execute([$emoji, $description]);
}
@ -23,7 +23,7 @@ class MoodModel{
global $db;
$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);
}
}

View File

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

View File

@ -33,7 +33,10 @@ class MoodView {
<form method="post" class="emoji-form">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<?= $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>
<?php

View File

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

View File

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

View File

@ -17,11 +17,11 @@
<link rel="alternate"
type="application/rss+xml"
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"
type="application/atom+xml"
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>
<body>
<?php include TEMPLATES_DIR . '/partials/navbar.php'?>

View File

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