query("SELECT * FROM categories ORDER BY id")->fetchAll(); } function get_all_tags() { return db()->query("SELECT t.*, COUNT(at.article_id) cnt FROM tags t LEFT JOIN article_tags at ON t.id=at.tag_id GROUP BY t.id ORDER BY cnt DESC")->fetchAll(); } function get_article_tags($articleId) { $st = db()->prepare("SELECT t.* FROM tags t JOIN article_tags at ON t.id=at.tag_id WHERE at.article_id=?"); $st->execute([$articleId]); return $st->fetchAll(); } function get_archives() { $sql = "SELECT DATE_FORMAT(created_at, ?) ym, COUNT(*) cnt FROM articles WHERE status=? GROUP BY ym ORDER BY ym DESC"; $st = db()->prepare($sql); $st->execute(["%Y年%m月", "published"]); return $st->fetchAll(); } function record_visit($page) { $ip = $_SERVER["REMOTE_ADDR"] ?? ""; $ua = substr($_SERVER["HTTP_USER_AGENT"] ?? "", 0, 250); $st = db()->prepare("INSERT IGNORE INTO visitor_stats (visit_date, ip, page, ua) VALUES (CURDATE(), ?, ?, ?)"); $st->execute([$ip, $page, $ua]); } function paginate($total, $page, $perPage) { $pages = max(1, (int)ceil($total / $perPage)); $page = max(1, min($pages, (int)$page)); return [ "page" => $page, "pages" => $pages, "offset" => ($page - 1) * $perPage, "perPage" => $perPage, ]; } function markdown($text) { static $parsedown = null; if ($parsedown === null) { require_once __DIR__ . "/Parsedown.php"; $parsedown = new Parsedown(); $parsedown->setBreaksEnabled(true); } return $parsedown->text((string)$text); }