From eeb73eccd4e6b1b011e546192d7f8edc6ce495b3 Mon Sep 17 00:00:00 2001 From: Greg Sarjeant Date: Thu, 14 Aug 2025 01:10:35 +0000 Subject: [PATCH] Fix feed links, clean up single tick pages (#69) Fix feed links. Clean up single-tick page. Reviewed-on: https://gitea.subcultureofone.org/greg/tkr/pulls/69 Co-authored-by: Greg Sarjeant Co-committed-by: Greg Sarjeant --- public/css/default.css | 5 ++++ public/index.php | 2 +- .../HomeController/HomeController.php | 4 +-- .../TickController/TickController.php | 27 +++++++++++-------- src/Feed/AtomGenerator.php | 2 +- src/Feed/RssGenerator.php | 5 ++-- src/Model/TickModel/TickModel.php | 17 ++++++------ templates/partials/tick-404.php | 4 +++ templates/partials/tick.php | 14 ++++++++-- .../TickController/TickControllerTest.php | 12 ++++----- 10 files changed, 58 insertions(+), 34 deletions(-) create mode 100644 templates/partials/tick-404.php diff --git a/public/css/default.css b/public/css/default.css index 738995b..b04aaf5 100644 --- a/public/css/default.css +++ b/public/css/default.css @@ -370,6 +370,11 @@ time { font-size: 1.0em; display: block; } +.tick-meta { + color: var(--color-log-muted); + font-size: 0.9em; + margin-bottom: 0.4em; +} .tick-pagination a { margin: 0 5px; diff --git a/public/index.php b/public/index.php index f8dd5e8..c20e3c2 100644 --- a/public/index.php +++ b/public/index.php @@ -118,7 +118,7 @@ if ($method === 'POST' && $path != 'tkr-setup') { if ($path != 'login'){ if (!Session::isValid($_POST['csrf_token'])) { // Invalid session - redirect to /login - Log::info('Attempt to POST with invalid session. Redirecting to login.'); + Log::warning('Attempt to POST with invalid session. Redirecting to login.'); header('Location: ' . Util::buildRelativeUrl($app['settings']->basePath, 'login')); exit; } diff --git a/src/Controller/HomeController/HomeController.php b/src/Controller/HomeController/HomeController.php index 0736df4..d6efb0e 100644 --- a/src/Controller/HomeController/HomeController.php +++ b/src/Controller/HomeController/HomeController.php @@ -6,8 +6,8 @@ class HomeController extends Controller { // renders the homepage view. public function index(){ $page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1; - $data = $this->getHomeData($page); - $this->render("home.php", $data); + $vars = $this->getHomeData($page); + $this->render("home.php", $vars); } public function getHomeData(int $page): array { diff --git a/src/Controller/TickController/TickController.php b/src/Controller/TickController/TickController.php index f33aa24..6474b30 100644 --- a/src/Controller/TickController/TickController.php +++ b/src/Controller/TickController/TickController.php @@ -2,22 +2,27 @@ declare(strict_types=1); class TickController extends Controller{ - public function index(int $id){ + public function index(string $id){ + // This is because my router is too simplistic to cleanly handle type casting, + // so I just accept a sting here and cast it to an int immediately. + $id = (int) $id; global $app; + $vars = ['settings' => $app['settings']]; Log::debug("Fetching tick with ID: {$id}"); try { $tickModel = new TickModel($app['db'], $app['settings']); - $vars = $tickModel->get($id); + $tick = $tickModel->get($id); - if (empty($vars) || !isset($vars['tick'])) { + if (empty($tick) || !isset($tick['tick'])) { Log::warning("Tick not found for ID: {$id}"); http_response_code(404); - echo '

404 - Tick Not Found

'; + $this->render('tick-404.php', $vars); return; } + $vars = array_merge($tick, $vars); Log::info("Successfully loaded tick {$id}: " . substr($vars['tick'], 0, 50) . (strlen($vars['tick']) > 50 ? '...' : '')); $this->render('tick.php', $vars); @@ -30,30 +35,30 @@ class TickController extends Controller{ public function handleDelete(string $id){ global $app; - + $id = (int) $id; Log::debug("Attempting to delete tick with ID: {$id}"); - + try { $tickModel = new TickModel($app['db'], $app['settings']); - + // TickModel->delete() handles validation and sets flash messages: // - "Tick not found" if tick doesn't exist - // - "Tick is too old to delete" if outside deletion window + // - "Tick is too old to delete" if outside deletion window // - "Deleted: '{content}'" on success $success = $tickModel->delete($id); - + if ($success) { Log::info("Successfully deleted tick {$id}"); } else { Log::warning("Failed to delete tick {$id}"); } - + } catch (Exception $e) { Log::error("Exception while deleting tick {$id}: " . $e->getMessage()); Session::setFlashMessage('error', 'An error occurred while deleting the tick'); } - + // Redirect back to homepage header('Location: ' . Util::buildRelativeUrl($app['settings']->basePath, '')); exit(); diff --git a/src/Feed/AtomGenerator.php b/src/Feed/AtomGenerator.php index f3f0fa5..71723da 100644 --- a/src/Feed/AtomGenerator.php +++ b/src/Feed/AtomGenerator.php @@ -41,7 +41,7 @@ class AtomGenerator extends FeedGenerator { $tickUrl = Util::escape_xml($siteUrl . $tickPath); $tickTime = date(DATE_ATOM, strtotime($tick['timestamp'])); $tickTitle = Util::escape_xml($tick['tick']); - $tickContent = Util::linkify($tickTitle); + $tickContent = Util::escape_xml(Util::linkify(Util::escape_html($tick['tick']))); ?> <?= $tickTitle ?> diff --git a/src/Feed/RssGenerator.php b/src/Feed/RssGenerator.php index c8597b6..4fb7e8d 100644 --- a/src/Feed/RssGenerator.php +++ b/src/Feed/RssGenerator.php @@ -35,10 +35,11 @@ class RssGenerator extends FeedGenerator { $tickUrl = Util::escape_xml($this->buildTickUrl($tick['id'])); $tickDate = date(DATE_RSS, strtotime($tick['timestamp'])); $tickTitle = Util::escape_xml($tick['tick']); - $tickDescription = Util::linkify($tickTitle); + $tickDescription = Util::escape_xml(Util::linkify(Util::escape_html($tick['tick']))); + Log::debug("RSS item: {$tickDescription}"); ?> - <?php echo $tickTitle ?> + <?php echo $tickTitle; ?> diff --git a/src/Model/TickModel/TickModel.php b/src/Model/TickModel/TickModel.php index 74bab9f..51286f9 100644 --- a/src/Model/TickModel/TickModel.php +++ b/src/Model/TickModel/TickModel.php @@ -7,14 +7,14 @@ class TickModel { public function getPage(int $limit, int $offset = 0): array { $stmt = $this->db->prepare("SELECT id, timestamp, tick FROM tick ORDER BY timestamp DESC LIMIT ? OFFSET ?"); $stmt->execute([$limit, $offset]); - + $ticks = $stmt->fetchAll(PDO::FETCH_ASSOC); - + return array_map(function($tick) { $tickTime = new DateTimeImmutable($tick['timestamp'], new DateTimeZone('UTC')); $now = new DateTimeImmutable('now', new DateTimeZone('UTC')); $hoursSinceCreation = ($now->getTimestamp() - $tickTime->getTimestamp()) / 3600; - + $tick['can_delete'] = $hoursSinceCreation <= $this->settings->tickDeleteHours; return $tick; }, $ticks); @@ -41,7 +41,6 @@ class TickModel { return [ 'tickTime' => $row['timestamp'], 'tick' => $row['tick'], - 'settings' => $this->settings, ]; } @@ -50,26 +49,26 @@ class TickModel { $stmt = $this->db->prepare("SELECT tick, timestamp FROM tick WHERE id=?"); $stmt->execute([$id]); $row = $stmt->fetch(PDO::FETCH_ASSOC); - + if ($row === false || empty($row)) { Session::setFlashMessage('error', 'Tick not found'); return false; } - + // Check deletion window $tickTime = new DateTimeImmutable($row['timestamp'], new DateTimeZone('UTC')); $now = new DateTimeImmutable('now', new DateTimeZone('UTC')); $hoursSinceCreation = ($now->getTimestamp() - $tickTime->getTimestamp()) / 3600; - + if ($hoursSinceCreation > $this->settings->tickDeleteHours) { Session::setFlashMessage('error', 'Tick is too old to delete'); return false; } - + // Delete and set success message $stmt = $this->db->prepare("DELETE FROM tick WHERE id=?"); $stmt->execute([$id]); - + Session::setFlashMessage('success', "Deleted: '{$row['tick']}'"); return true; } diff --git a/templates/partials/tick-404.php b/templates/partials/tick-404.php new file mode 100644 index 0000000..8769c48 --- /dev/null +++ b/templates/partials/tick-404.php @@ -0,0 +1,4 @@ +
+

Tick Not Found

+

The tick you're looking for has been deleted or never existed.

+
\ No newline at end of file diff --git a/templates/partials/tick.php b/templates/partials/tick.php index 649151a..dee4037 100644 --- a/templates/partials/tick.php +++ b/templates/partials/tick.php @@ -1,4 +1,14 @@ -

Tick from

-

+ +
+
+
+

Tick

+

Posted on UTC

+
+
+ +
+
+
diff --git a/tests/Controller/TickController/TickControllerTest.php b/tests/Controller/TickController/TickControllerTest.php index 0401d93..b9d69cd 100644 --- a/tests/Controller/TickController/TickControllerTest.php +++ b/tests/Controller/TickController/TickControllerTest.php @@ -62,7 +62,7 @@ class TickControllerTest extends TestCase ob_start(); $controller = new TickController(); - $controller->index(123); + $controller->index("123"); $output = ob_get_clean(); @@ -96,12 +96,12 @@ class TickControllerTest extends TestCase ob_start(); $controller = new TickController(); - $controller->index(999); + $controller->index("999"); $output = ob_get_clean(); // Should return 404 error - $this->assertStringContainsString('404 - Tick Not Found', $output); + $this->assertStringContainsString('Tick Not Found', $output); } public function testIndexWithEmptyTickData(): void @@ -125,12 +125,12 @@ class TickControllerTest extends TestCase ob_start(); $controller = new TickController(); - $controller->index(456); + $controller->index("456"); $output = ob_get_clean(); // Should return 404 error for empty data - $this->assertStringContainsString('404 - Tick Not Found', $output); + $this->assertStringContainsString('Tick Not Found', $output); } public function testIndexWithDatabaseException(): void @@ -145,7 +145,7 @@ class TickControllerTest extends TestCase ob_start(); $controller = new TickController(); - $controller->index(123); + $controller->index("123"); $output = ob_get_clean();