v2.0.0.1
Publisium
- <?php
-
- declare(strict_types=1);
-
- require_once __DIR__ . '/includes/config.php';
- require_once __DIR__ . '/includes/database.php';
- require_once __DIR__ . '/includes/auth.php';
- require_once __DIR__ . '/includes/csrf.php';
- require_once __DIR__ . '/includes/activity-log.php';
- require_once __DIR__ . '/includes/site-front.php';
- require_once __DIR__ . '/includes/site-comments.php';
-
- Auth::boot();
- Auth::requireRoleAtLeast(Auth::ROLE_WRITER);
-
- $currentUser = Auth::user();
- $currentRole = Auth::role();
- $canModerate = Auth::canModerate();
- $db = Database::site();
-
- $flashMessage = null;
- $flashType = 'success';
-
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- if (!Csrf::verify($_POST['csrf_token'] ?? null)) {
- $flashMessage = 'Your session expired. Please try again.';
- $flashType = 'error';
- } else {
- require __DIR__ . '/dashboard-actions/manage-comments.php';
- }
- }
-
- $statusFilters = $canModerate ? ['pending' => 'Waiting', 'approved' => 'Published', 'all' => 'All'] : ['approved' => 'Published'];
- $defaultFilter = $canModerate ? 'pending' : 'approved';
- $statusFilter = array_key_exists((string) ($_GET['status'] ?? ''), $statusFilters) ? (string) $_GET['status'] : $defaultFilter;
- $perPage = 50;
- $currentPage = max(1, (int) ($_GET['page'] ?? 1));
-
- $ownArticlesSql = $canModerate ? '' : ' AND a.author_id = ' . (int) $currentUser['id'];
-
- $counts = ['pending' => 0, 'approved' => 0, 'all' => 0];
- foreach ($db->query('SELECT c.status, COUNT(*) AS total FROM article_comments c LEFT JOIN articles a ON a.id = c.article_id WHERE 1 = 1' . $ownArticlesSql . ' GROUP BY c.status') as $countRow) {
- if (isset($counts[$countRow['status']])) {
- $counts[$countRow['status']] = (int) $countRow['total'];
- }
- $counts['all'] += (int) $countRow['total'];
- }
-
- $totalPages = max(1, (int) ceil($counts[$statusFilter] / $perPage));
- $currentPage = min($currentPage, $totalPages);
- $where = 'WHERE 1 = 1' . $ownArticlesSql . ($statusFilter === 'all' ? '' : ' AND c.status = :status');
- $statement = $db->prepare(
- 'SELECT c.*, a.title AS article_title, a.slug AS article_slug, a.status AS article_status
- FROM article_comments c
- LEFT JOIN articles a ON a.id = c.article_id
- ' . $where . '
- ORDER BY c.created_at DESC, c.id DESC
- LIMIT ' . $perPage . ' OFFSET ' . (($currentPage - 1) * $perPage)
- );
- $statement->execute($statusFilter === 'all' ? [] : ['status' => $statusFilter]);
- $comments = $statement->fetchAll();
-
- $dashActivePage = 'comments';
- $dashPageTitle = 'Comments';
-
- require __DIR__ . '/includes/dash-header.php';
-
- ?>
-
- <?php if ($flashMessage !== null): ?>
- <div class="dash-flash dash-flash-<?= htmlspecialchars($flashType) ?>"><?= Icons::icon($flashType === 'error' ? 'x' : 'check', 'icon icon-sm') ?><?= htmlspecialchars($flashMessage) ?></div>
- <?php endif; ?>
-
- <div class="dash-header-row">
- <h1 class="dash-title"><?= Icons::icon('message', 'icon icon-lg') ?>Comments</h1>
- <?php if ($canModerate && $statusFilter === 'pending' && $counts['pending'] > 0): ?>
- <form method="post" onsubmit="return confirm('Delete all <?= (int) $counts['pending'] ?> waiting comments? This can’t be undone.');">
- <?= Csrf::field() ?>
- <input type="hidden" name="action" value="delete_pending_comments">
- <button type="submit" class="dash-btn dash-btn-danger"><?= Icons::icon('trash', 'icon icon-sm') ?>Delete all waiting</button>
- </form>
- <?php endif; ?>
- </div>
-
- <?php if (!SiteComments::isActive()): ?>
- <p class="comments-note">Publisium comments are not the selected comment service right now, so nothing new comes in and published comments aren’t shown on the site. You can switch in Settings → Comments.</p>
- <?php endif; ?>
-
- <nav class="comments-filter">
- <?php foreach ($statusFilters as $filterKey => $filterLabel): ?>
- <a href="comments.php?status=<?= $filterKey ?>" class="dash-btn<?= $statusFilter === $filterKey ? ' dash-btn-primary' : '' ?>"><?= htmlspecialchars($filterLabel) ?> (<?= $counts[$filterKey] ?>)</a>
- <?php endforeach; ?>
- </nav>
-
- <table class="dash-table comments-table">
- <thead>
- <tr>
- <th><?= Icons::icon('message', 'icon icon-sm') ?>Comment</th>
- <th><?= Icons::icon('user', 'icon icon-sm') ?>Author</th>
- <th><?= Icons::icon('articles', 'icon icon-sm') ?>Article</th>
- <th><?= Icons::icon('calendar', 'icon icon-sm') ?>Date</th>
- <th><?= Icons::icon('eye', 'icon icon-sm') ?>Status</th>
- <th></th>
- </tr>
- </thead>
- <tbody>
- <?php foreach ($comments as $comment): ?>
- <?php $commentId = (int) $comment['id']; ?>
- <tr>
- <td class="comments-table-body">
- <?php if ($comment['parent_id'] !== null): ?><span class="status-pill status-draft">Reply</span><?php endif; ?>
- <?= SiteComments::formatBody((string) $comment['body']) ?>
- <?php if ($comment['status'] === 'approved' && $comment['article_status'] === 'published'): ?>
- <details class="comments-reply">
- <summary class="dash-btn-small"><?= Icons::icon('message', 'icon icon-sm') ?>Reply</summary>
- <form method="post">
- <?= Csrf::field() ?>
- <input type="hidden" name="action" value="reply_comment">
- <input type="hidden" name="comment_id" value="<?= $commentId ?>">
- <textarea name="reply_body" rows="3" required maxlength="<?= SiteComments::MAX_BODY ?>" placeholder="Your reply, shown with your name and a team badge"></textarea>
- <button type="submit" class="dash-btn dash-btn-primary"><?= Icons::icon('check', 'icon icon-sm') ?>Publish reply</button>
- </form>
- </details>
- <?php endif; ?>
- </td>
- <td>
- <?= htmlspecialchars((string) $comment['author_name']) ?>
- <span class="status-pill status-draft"><?= !empty($comment['team_account_id']) ? 'Team' : ($comment['user_account_id'] !== null ? 'Reader' : 'Guest') ?></span>
- <?php if ($canModerate && !empty($comment['author_email'])): ?>
- <div class="comments-email"><a href="mailto:<?= htmlspecialchars((string) $comment['author_email'], ENT_QUOTES) ?>"><?= htmlspecialchars((string) $comment['author_email']) ?></a></div>
- <?php endif; ?>
- </td>
- <td>
- <?php if ($comment['article_title'] === null): ?>
- <em>Deleted article</em>
- <?php elseif ($comment['article_status'] === 'published'): ?>
- <a href="<?= htmlspecialchars(SiteFront::articleUrl((string) $comment['article_slug'], '') . '#comment-' . $commentId, ENT_QUOTES) ?>" target="_blank" rel="noopener"><?= htmlspecialchars((string) $comment['article_title']) ?></a>
- <?php else: ?>
- <?= htmlspecialchars((string) $comment['article_title']) ?>
- <?php endif; ?>
- </td>
- <td><?= htmlspecialchars(date('M j, Y H:i', strtotime((string) $comment['created_at']) ?: time())) ?></td>
- <td>
- <?php if ($comment['status'] === 'approved'): ?>
- <span class="status-pill status-published">Published</span>
- <?php else: ?>
- <span class="status-pill status-pending_review">Waiting</span>
- <?php endif; ?>
- </td>
- <td class="dash-table-actions">
- <?php if ($canModerate): ?>
- <form method="post" style="display:inline;">
- <?= Csrf::field() ?>
- <input type="hidden" name="comment_id" value="<?= $commentId ?>">
- <?php if ($comment['status'] === 'approved'): ?>
- <button type="submit" name="action" value="hide_comment" class="dash-btn-small"><?= Icons::icon('eye', 'icon icon-sm') ?>Hide</button>
- <?php else: ?>
- <button type="submit" name="action" value="approve_comment" class="dash-btn-small"><?= Icons::icon('check', 'icon icon-sm') ?>Approve</button>
- <?php endif; ?>
- </form>
- <form method="post" style="display:inline;" onsubmit="return confirm('Delete this comment? Replies to it are deleted too.');">
- <?= Csrf::field() ?>
- <input type="hidden" name="action" value="delete_comment">
- <input type="hidden" name="comment_id" value="<?= $commentId ?>">
- <button type="submit" class="dash-btn-small dash-btn-danger"><?= Icons::icon('trash', 'icon icon-sm') ?>Delete</button>
- </form>
- <?php endif; ?>
- </td>
- </tr>
- <?php endforeach; ?>
- <?php if (empty($comments)): ?>
- <tr>
- <td colspan="6"><?= $statusFilter === 'pending' ? 'Nothing is waiting for approval.' : ($canModerate ? 'No comments here yet.' : 'No comments on your articles yet.') ?></td>
- </tr>
- <?php endif; ?>
- </tbody>
- </table>
-
- <?php if ($totalPages > 1): ?>
- <nav class="comments-filter">
- <?php for ($pageNumber = 1; $pageNumber <= $totalPages; $pageNumber++): ?>
- <a href="comments.php?status=<?= $statusFilter ?>&page=<?= $pageNumber ?>" class="dash-btn<?= $pageNumber === $currentPage ? ' dash-btn-primary' : '' ?>"><?= $pageNumber ?></a>
- <?php endfor; ?>
- </nav>
- <?php endif; ?>
-
- <style>
- .comments-filter { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 16px; }
- .comments-note { color: var(--muted); font-size: 13px; }
- .comments-table-body { max-width: 420px; overflow-wrap: anywhere; white-space: normal; }
- .comments-table-body .status-pill { margin-right: 6px; }
- .comments-email { margin-top: 4px; font-size: 12px; overflow-wrap: anywhere; }
- .comments-reply { margin-top: 10px; }
- .comments-reply summary { display: inline-flex; cursor: pointer; list-style: none; }
- .comments-reply summary::-webkit-details-marker { display: none; }
- .comments-reply form { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
- .comments-reply textarea { width: 100%; }
- .comments-reply button { align-self: flex-start; }
- </style>
-
- <?php require __DIR__ . '/includes/dash-footer.php'; ?>
-