Add in-house comments (Lib\Comments)
A reusable comment thread any sidecar can attach to any page, tied to real logged-in accounts (never anonymous), auto-approved on submission with hide/delete moderation at /admin/comments — only a verified account can post, so there's no anonymous-spam vector to pre-vet against. Framework-level (novaconium/lib, novaconium/migrations, novaconium/pages), not App/migrations, matching Admin auth and Media manager's shape. A page needs its own sidecar to use it — this repo has no client-side JS, so comments ride the same server-rendered POST pattern as every other dynamic feature, which is also what excludes a page from the static cache. App/pages/blog/comments-demo/ demonstrates the pattern without touching existing posts that are referenced elsewhere as the sidecar-less example. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
// Demonstrates attaching Lib\Comments to a page — see /admin/docs/comments.
|
||||
// This is the one post under App/pages/blog/ with a sidecar, specifically
|
||||
// so it can carry a live comment thread; giving it one is what excludes
|
||||
// it from the static HTML cache (Renderer::render() only ever caches
|
||||
// sidecar-less pages) — every other post stays sidecar-less/cached and
|
||||
// has no thread, since blog/_layout/layout.twig only includes one when
|
||||
// 'comments' is present in the returned context.
|
||||
|
||||
use App\AdminAuth;
|
||||
use App\Response;
|
||||
use Lib\Comments;
|
||||
use Lib\Csrf;
|
||||
use Lib\FormValidator;
|
||||
use Lib\Input;
|
||||
use Lib\SpamGuard;
|
||||
|
||||
$pagePath = Comments::currentPagePath();
|
||||
$user = AdminAuth::currentUser();
|
||||
$spamGuard = new SpamGuard();
|
||||
$commentError = null;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (!Csrf::verify(Input::post('csrf_token'))) {
|
||||
return Response::redirect($pagePath . '?error=security');
|
||||
}
|
||||
|
||||
if ($user === null) {
|
||||
return Response::redirect($pagePath . '?error=login');
|
||||
}
|
||||
|
||||
$body = Input::post('body', '');
|
||||
$validator = (new FormValidator())
|
||||
->required($body, 'body', 'Enter a comment.')
|
||||
->maxLength($body, 'body', 2000, 'Comments are 2000 characters max.');
|
||||
|
||||
if ($validator->passes()) {
|
||||
// Same "bot gets an identical response" reasoning as the contact
|
||||
// form — see App/pages/contact/index.php. Only the insert is
|
||||
// skipped for spam.
|
||||
if (!$spamGuard->isSpam(Input::post())) {
|
||||
Comments::create($pagePath, $user['id'], $body);
|
||||
}
|
||||
|
||||
return Response::redirect($pagePath);
|
||||
}
|
||||
|
||||
$commentError = $validator->errors()['body'] ?? null;
|
||||
}
|
||||
|
||||
return [
|
||||
'comments' => Comments::forPage($pagePath),
|
||||
'currentUser' => $user,
|
||||
'commentError' => $commentError,
|
||||
'renderedAt' => $spamGuard->renderedAt(),
|
||||
'csrfField' => Csrf::fieldName(),
|
||||
'csrfToken' => Csrf::token(),
|
||||
];
|
||||
Reference in New Issue
Block a user