Files
tcms/app/CommentService.php
T
Ty Clifford bd0bfdc09f - Import
2026-07-04 20:14:52 -04:00

190 lines
6.7 KiB
PHP

<?php
declare(strict_types=1);
namespace NeonBlog;
use PDO;
final class CommentService
{
private PDO $pdo;
private Config $config;
public function __construct(PDO $pdo, Config $config)
{
$this->pdo = $pdo;
$this->config = $config;
}
/** @return array<int, array<string, mixed>> */
public function approvedFor(string $slug): array
{
$statement = $this->pdo->prepare(
'SELECT * FROM comments WHERE slug = :slug AND status = "approved" ORDER BY created_at ASC'
);
$statement->execute(['slug' => $slug]);
return $statement->fetchAll();
}
/** @return array{ok: bool, stored: bool, message: string} */
public function add(string $slug, array $data): array
{
$honeypot = (string) $this->config->get('comments.honeypot_field', 'website_url');
if (trim((string) ($data[$honeypot] ?? '')) !== '') {
return [
'ok' => true,
'stored' => false,
'message' => 'Thanks. Your comment is awaiting review.',
];
}
$author = trim((string) ($data['author'] ?? ''));
$email = trim((string) ($data['email'] ?? ''));
$website = trim((string) ($data['site_url'] ?? ''));
$body = trim((string) ($data['body'] ?? ''));
if ($author === '' || $email === '' || $body === '') {
return ['ok' => false, 'stored' => false, 'message' => 'Name, email, and comment are required.'];
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return ['ok' => false, 'stored' => false, 'message' => 'Please enter a valid email address.'];
}
if ($website !== '' && !filter_var($website, FILTER_VALIDATE_URL)) {
return ['ok' => false, 'stored' => false, 'message' => 'Please enter a valid website URL or leave it blank.'];
}
if (mb_strlen($body) > 5000) {
return ['ok' => false, 'stored' => false, 'message' => 'Comments must stay under 5,000 characters.'];
}
$now = date('c');
$status = $this->config->get('comments.moderate', true) ? 'pending' : 'approved';
$statement = $this->pdo->prepare(
'INSERT INTO comments
(slug, author, email, website, body, status, ip_hash, user_agent, created_at, updated_at)
VALUES
(:slug, :author, :email, :website, :body, :status, :ip_hash, :user_agent, :created_at, :updated_at)'
);
$statement->execute([
'slug' => $slug,
'author' => $author,
'email' => $email,
'website' => $website,
'body' => $body,
'status' => $status,
'ip_hash' => $this->ipHash(),
'user_agent' => substr((string) ($_SERVER['HTTP_USER_AGENT'] ?? 'cli'), 0, 300),
'created_at' => $now,
'updated_at' => $now,
]);
return [
'ok' => true,
'stored' => true,
'message' => $status === 'approved'
? 'Thanks. Your comment is live.'
: 'Thanks. Your comment is awaiting review.',
];
}
/** @param array<string, mixed> $data */
public function import(string $slug, array $data): int
{
$createdAt = $this->normalizeDate((string) ($data['created_at'] ?? $data['date'] ?? ''));
$updatedAt = $this->normalizeDate((string) ($data['updated_at'] ?? $data['modified'] ?? $createdAt));
$status = (string) ($data['status'] ?? 'approved');
if (!in_array($status, ['pending', 'approved', 'spam'], true)) {
$status = match ($status) {
'published', 'published-comment', 'visible', '1', 'true' => 'approved',
'trash', 'deleted', 'spam-comment' => 'spam',
default => 'pending',
};
}
$statement = $this->pdo->prepare(
'INSERT INTO comments
(slug, author, email, website, body, status, ip_hash, user_agent, created_at, updated_at)
VALUES
(:slug, :author, :email, :website, :body, :status, :ip_hash, :user_agent, :created_at, :updated_at)'
);
$statement->execute([
'slug' => $slug,
'author' => trim((string) ($data['author'] ?? $data['name'] ?? 'Imported')),
'email' => trim((string) ($data['email'] ?? '')),
'website' => trim((string) ($data['website'] ?? $data['site_url'] ?? $data['url'] ?? '')),
'body' => trim((string) ($data['body'] ?? $data['content'] ?? $data['comment'] ?? '')),
'status' => $status,
'ip_hash' => trim((string) ($data['ip_hash'] ?? '')),
'user_agent' => substr((string) ($data['user_agent'] ?? $data['agent'] ?? 'import'), 0, 300),
'created_at' => $createdAt,
'updated_at' => $updatedAt,
]);
return (int) $this->pdo->lastInsertId();
}
/** @return array<int, array<string, mixed>> */
public function list(?string $status = null, ?string $slug = null): array
{
$where = [];
$params = [];
if ($status !== null) {
$where[] = 'status = :status';
$params['status'] = $status;
}
if ($slug !== null) {
$where[] = 'slug = :slug';
$params['slug'] = $slug;
}
$sql = 'SELECT * FROM comments';
if ($where !== []) {
$sql .= ' WHERE ' . implode(' AND ', $where);
}
$sql .= ' ORDER BY created_at DESC';
$statement = $this->pdo->prepare($sql);
$statement->execute($params);
return $statement->fetchAll();
}
public function setStatus(int $id, string $status): bool
{
if (!in_array($status, ['pending', 'approved', 'spam'], true)) {
return false;
}
$statement = $this->pdo->prepare('UPDATE comments SET status = :status, updated_at = :updated_at WHERE id = :id');
$statement->execute([
'id' => $id,
'status' => $status,
'updated_at' => date('c'),
]);
return $statement->rowCount() > 0;
}
public function delete(int $id): bool
{
$statement = $this->pdo->prepare('DELETE FROM comments WHERE id = :id');
$statement->execute(['id' => $id]);
return $statement->rowCount() > 0;
}
private function ipHash(): string
{
$ip = (string) ($_SERVER['REMOTE_ADDR'] ?? 'cli');
$salt = (string) $this->config->get('stats.hash_salt', 'change-me');
return hash_hmac('sha256', $ip, $salt);
}
private function normalizeDate(string $value): string
{
$timestamp = strtotime($value);
return date('c', $timestamp ?: time());
}
}