?php class NewsController { private $pdo; public function __construct($pdo) { $this->pdo = $pdo; } public function show($slug = null) { if (!$slug) { $this->list(); return; } // افزایش تعداد بازدید $this->pdo->prepare("UPDATE news SET view_count = view_count + 1, updated_at = NOW() WHERE slug = ?") ->execute([$slug]); // دریافت خبر $stmt = $this->pdo->prepare(" SELECT n.*, GROUP_CONCAT(DISTINCT c.name SEPARATOR ', ') as categories, GROUP_CONCAT(DISTINCT c.slug SEPARATOR ',') as category_slugs FROM news n LEFT JOIN news_categories nc ON n.id = nc.news_id LEFT JOIN categories c ON nc.category_id = c.id WHERE n.slug = ? AND n.status = 'published' GROUP BY n.id "); $stmt->execute([$slug]); $news = $stmt->fetch(); if (!$news) { $this->notFound(); return; } // محاسبه زمان خواندن $reading_time = $this->calculateReadingTime($news['content']); // دریافت اخبار مرتبط $relatedNews = $this->getRelatedNews($news['id'], $news['category_slugs']); // دریافت آخرین اخبار $latestNews = $this->getLatestNews(5, $news['id']); // دریافت پربازدیدترینها $popularNews = $this->getPopularNews(5, $news['id']); // ساختار JSON-LD $jsonLd = $this->generateJsonLd($news); $this->render('news/single', [ 'news' => $news, 'relatedNews' => $relatedNews, 'latestNews' => $latestNews, 'popularNews' => $popularNews, 'reading_time' => $reading_time, 'jsonLd' => $jsonLd ]); } private function calculateReadingTime($content) { $wordCount = str_word_count(strip_tags($content)); $minutes = ceil($wordCount / 200); // 200 کلمه در دقیقه return max(1, $minutes); // حداقل 1 دقیقه } public function list() { $page = max(1, intval($_GET['page'] ?? 1)); $limit = 12; $offset = ($page - 1) * $limit; $news = $this->pdo->query(" SELECT n.* FROM news n WHERE n.status = 'published' ORDER BY n.published_at DESC LIMIT $limit OFFSET $offset ")->fetchAll(); $total = $this->pdo->query("SELECT COUNT(*) as count FROM news WHERE status = 'published'")->fetch()['count']; $this->render('news/list', [ 'news' => $news, 'total' => $total, 'page' => $page, 'limit' => $limit ]); } public function category($slug) { if (!$slug) { $this->notFound(); return; } // دریافت اطلاعات دستهبندی $stmt = $this->pdo->prepare("SELECT * FROM categories WHERE slug = ? AND is_active = 1"); $stmt->execute([$slug]); $category = $stmt->fetch(); if (!$category) { $this->notFound(); return; } $page = $_GET['page'] ?? 1; $limit = 12; $offset = ($page - 1) * $limit; // دریافت اخبار این دسته $stmt = $this->pdo->prepare(" SELECT n.* FROM news n INNER JOIN news_categories nc ON n.id = nc.news_id WHERE nc.category_id = ? AND n.status = 'published' ORDER BY n.published_at DESC LIMIT ? OFFSET ? "); $stmt->execute([$category['id'], $limit, $offset]); $news = $stmt->fetchAll(); // تعداد کل // تعداد کل $stmt = $this->pdo->prepare(" SELECT COUNT(*) as count FROM news n INNER JOIN news_categories nc ON n.id = nc.news_id WHERE nc.category_id = ? AND n.status = 'published' "); $stmt->execute([$category['id']]); $total = $stmt->fetch()['count']; $this->render('news/category', [ 'category' => $category, 'news' => $news, 'total' => $total, 'page' => $page, 'limit' => $limit ]); } private function getRelatedNews($newsId, $categorySlugs) { if (!$categorySlugs) return []; $categorySlugsArray = explode(',', $categorySlugs); $placeholders = str_repeat('?,', count($categorySlugsArray) - 1) . '?'; $stmt = $this->pdo->prepare(" SELECT DISTINCT n.* FROM news n INNER JOIN news_categories nc ON n.id = nc.news_id INNER JOIN categories c ON nc.category_id = c.id WHERE c.slug IN ($placeholders) AND n.id != ? AND n.status = 'published' ORDER BY n.published_at DESC LIMIT 4 "); $params = array_merge($categorySlugsArray, [$newsId]); $stmt->execute($params); return $stmt->fetchAll(); } private function getLatestNews($limit = 5, $excludeId = null) { $sql = "SELECT n.* FROM news n WHERE n.status = 'published'"; $params = []; if ($excludeId) { $sql .= " AND n.id != ?"; $params[] = $excludeId; } $sql .= " ORDER BY n.published_at DESC LIMIT ?"; $params[] = $limit; $stmt = $this->pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(); } private function getPopularNews($limit = 5, $excludeId = null) { $sql = "SELECT n.* FROM news n WHERE n.status = 'published'"; $params = []; if ($excludeId) { $sql .= " AND n.id != ?"; $params[] = $excludeId; } $sql .= " ORDER BY n.view_count DESC LIMIT ?"; $params[] = $limit; $stmt = $this->pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(); } private function generateJsonLd($news) { $siteUrl = 'https://bilgix.click'; return [ '@context' => 'https://schema.org', '@type' => 'NewsArticle', 'headline' => $news['title'], 'description' => $news['lead'], 'image' => $news['featured_image'] ? [ $siteUrl . '/uploads/news/' . $news['featured_image'] ] : [ $siteUrl . '/logo.jpeg' ], 'datePublished' => $news['published_at'], 'dateModified' => $news['updated_at'], 'author' => [ '@type' => 'Organization', 'name' => 'BilgiX News Agency' ], 'publisher' => [ '@type' => 'Organization', 'name' => 'BilgiX', 'logo' => [ '@type' => 'ImageObject', 'url' => $siteUrl . '/logo.jpeg' ] ], 'mainEntityOfPage' => [ '@type' => 'WebPage', '@id' => $siteUrl . '/?route=' . $news['slug'] ] ]; } private function notFound() { http_response_code(404); $this->render('errors/404'); } private function render($view, $data = []) { // اطلاعات عمومی $generalData = [ 'breakingNews' => $this->getBreakingNews(), 'categories' => $this->getCategories(), 'settings' => $this->getSettings() ]; extract($generalData); extract($data); include __DIR__ . '/../views/layouts/frontend/header.php'; if (file_exists(__DIR__ . "/../views/frontend/{$view}.php")) { include __DIR__ . "/../views/frontend/{$view}.php"; } elseif (file_exists(__DIR__ . "/../views/{$view}.php")) { include __DIR__ . "/../views/{$view}.php"; } else { echo "