pdo = $pdo; $this->config = $config; } /** @return array> */ 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> */ 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); } }