148 lines
4.8 KiB
PHP
148 lines
4.8 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.',
|
|
];
|
|
}
|
|
|
|
/** @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);
|
|
}
|
|
}
|