redirect('/admin/login'); } } public function loginForm() { if (isset($_SESSION['user_id']) && ($_SESSION['role'] ?? '') === 'admin') { $this->redirect('/admin/dashboard'); } $this->view('admin/login'); } public function login() { $username = $_POST['username'] ?? ''; $password = $_POST['password'] ?? ''; $db = db_pdo(); $stmt = $db->prepare("SELECT * FROM users WHERE username = ? AND role = 'admin'"); $stmt->execute([$username]); $user = $stmt->fetch(); if ($user && password_verify($password, $user['password'])) { $_SESSION['user_id'] = $user['id']; $_SESSION['username'] = $user['username']; $_SESSION['role'] = $user['role']; $this->redirect('/admin/dashboard'); } else { $error = "Invalid username or password, or you are not an admin"; $this->view('admin/login', ['error' => $error]); } } public function logout() { session_destroy(); $this->redirect('/admin/login'); } public function dashboard() { $this->checkAuth(); $apkService = new ApkService(); $db = db_pdo(); $stats = [ 'total_apks' => count($apkService->getAllApks()), 'total_downloads' => $this->getTotalDownloads(), 'total_users' => $db->query("SELECT COUNT(*) FROM users")->fetchColumn(), 'pending_withdrawals' => $db->query("SELECT COUNT(*) FROM withdrawals WHERE status = 'pending'")->fetchColumn(), 'recent_apks' => array_slice($apkService->getAllApks(), 0, 5), 'referral_stats' => $this->getReferralStats() ]; $this->view('admin/dashboard', $stats); } private function getReferralStats() { $db = db_pdo(); $stmt = $db->query("SELECT DATE(created_at) as date, COUNT(*) as count FROM referral_downloads WHERE created_at > DATE_SUB(NOW(), INTERVAL 7 DAY) GROUP BY DATE(created_at) ORDER BY date ASC"); return $stmt->fetchAll(); } private function getTotalDownloads() { $db = db_pdo(); return $db->query("SELECT SUM(total_downloads) FROM apks")->fetchColumn() ?: 0; } // Member Management public function users() { $this->checkAuth(); $db = db_pdo(); $users = $db->query("SELECT * FROM users ORDER BY created_at DESC")->fetchAll(); $this->view('admin/users/index', ['users' => $users]); } public function toggleBan($params) { $this->checkAuth(); $db = db_pdo(); $stmt = $db->prepare("UPDATE users SET is_banned = NOT is_banned WHERE id = ? AND role != 'admin'"); $stmt->execute([$params['id']]); $this->redirect('/admin/users'); } // APK Management public function apks() { $this->checkAuth(); $search = $_GET['search'] ?? null; $apkService = new ApkService(); $apks = $apkService->getAllApks(null, $search); $this->view('admin/apks/index', ['apks' => $apks]); } public function addApkForm() { $this->checkAuth(); $db = db_pdo(); $categories = $db->query("SELECT * FROM categories")->fetchAll(); $this->view('admin/apks/form', ['action' => 'add', 'categories' => $categories]); } public function addApk() { $this->checkAuth(); $title = $_POST['title']; $slug = $this->slugify($title); $description = $_POST['description']; $version = $_POST['version']; $image_url = $_POST['image_url']; $download_url = $_POST['download_url']; $category_id = !empty($_POST['category_id']) ? $_POST['category_id'] : null; $status = $_POST['status'] ?? 'published'; $is_vip = isset($_POST['is_vip']) ? 1 : 0; $icon_path = $this->handleUpload('icon_file', 'icons'); $db = db_pdo(); $stmt = $db->prepare("INSERT INTO apks (title, slug, description, version, image_url, icon_path, download_url, category_id, status, is_vip, display_order) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)"); $stmt->execute([$title, $slug, $description, $version, $image_url, $icon_path, $download_url, $category_id, $status, $is_vip]); $this->redirect('/admin/apks'); } public function massUploadForm() { $this->checkAuth(); $db = db_pdo(); $categories = $db->query("SELECT * FROM categories")->fetchAll(); $this->view('admin/apks/mass_upload', ['categories' => $categories]); } public function massUpload() { $this->checkAuth(); $titles = $_POST['titles'] ?? []; $versions = $_POST['versions'] ?? []; $download_urls = $_POST['download_urls'] ?? []; $category_id = !empty($_POST['category_id']) ? $_POST['category_id'] : null; $status = $_POST['status'] ?? 'published'; $db = db_pdo(); $stmt = $db->prepare("INSERT INTO apks (title, slug, description, version, icon_path, download_url, category_id, status, is_vip, display_order) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, 0)"); foreach ($titles as $index => $title) { if (empty($title)) continue; $slug = $this->slugify($title); $version = $versions[$index] ?? ''; $download_url = $download_urls[$index] ?? ''; $description = $title; // Default description to title for mass upload $icon_path = $this->handleMassUploadFile('icon_files', $index, 'icons'); $stmt->execute([$title, $slug, $description, $version, $icon_path, $download_url, $category_id, $status]); } $this->redirect('/admin/apks'); } private function handleMassUploadFile($field, $index, $dir = 'icons') { if (!isset($_FILES[$field]['name'][$index]) || $_FILES[$field]['error'][$index] !== UPLOAD_ERR_OK) { return null; } $uploadDir = 'assets/uploads/' . $dir . '/'; if (!is_dir($uploadDir)) { mkdir($uploadDir, 0775, true); } $ext = pathinfo($_FILES[$field]['name'][$index], PATHINFO_EXTENSION); $fileName = uniqid() . '.' . $ext; $targetPath = $uploadDir . $fileName; if (compress_image($_FILES[$field]['tmp_name'][$index], $targetPath, 75)) { return $targetPath; } return null; } public function editApkForm($params) { $this->checkAuth(); $apkService = new ApkService(); $apk = $apkService->getApkById($params['id']); $db = db_pdo(); $categories = $db->query("SELECT * FROM categories")->fetchAll(); $this->view('admin/apks/form', ['action' => 'edit', 'apk' => $apk, 'categories' => $categories]); } public function editApk($params) { $this->checkAuth(); $title = $_POST['title']; $description = $_POST['description']; $version = $_POST['version']; $image_url = $_POST['image_url']; $download_url = $_POST['download_url']; $category_id = !empty($_POST['category_id']) ? $_POST['category_id'] : null; $status = $_POST['status']; $is_vip = isset($_POST['is_vip']) ? 1 : 0; $db = db_pdo(); $apk = $db->query("SELECT * FROM apks WHERE id = " . $params['id'])->fetch(); $icon_path = $this->handleUpload('icon_file', 'icons') ?: $apk['icon_path']; $stmt = $db->prepare("UPDATE apks SET title = ?, description = ?, version = ?, image_url = ?, icon_path = ?, download_url = ?, category_id = ?, status = ?, is_vip = ? WHERE id = ?"); $stmt->execute([$title, $description, $version, $image_url, $icon_path, $download_url, $category_id, $status, $is_vip, $params['id']]); $this->redirect('/admin/apks'); } public function updateOrder() { $this->checkAuth(); $order = $_POST['order'] ?? []; $db = db_pdo(); foreach ($order as $index => $id) { $stmt = $db->prepare("UPDATE apks SET display_order = ? WHERE id = ?"); $stmt->execute([$index, $id]); } header('Content-Type: application/json'); echo json_encode(['success' => true]); } private function handleUpload($field, $dir = 'icons') { if (!isset($_FILES[$field]) || $_FILES[$field]['error'] !== UPLOAD_ERR_OK) { return null; } $uploadDir = 'assets/uploads/' . $dir . '/'; if (!is_dir($uploadDir)) { mkdir($uploadDir, 0775, true); } $ext = pathinfo($_FILES[$field]['name'], PATHINFO_EXTENSION); $fileName = uniqid() . '.' . $ext; $targetPath = $uploadDir . $fileName; if (compress_image($_FILES[$field]['tmp_name'], $targetPath, 75)) { return $targetPath; } return null; } // Settings Management public function settingsForm() { $this->checkAuth(); $settings = [ 'site_name' => get_setting('site_name'), 'contact_email' => get_setting('contact_email'), 'site_icon' => get_setting('site_icon'), 'site_favicon' => get_setting('site_favicon'), 'meta_description' => get_setting('meta_description'), 'meta_keywords' => get_setting('meta_keywords'), 'head_js' => get_setting('head_js'), 'body_js' => get_setting('body_js'), 'facebook_url' => get_setting('facebook_url'), 'twitter_url' => get_setting('twitter_url'), 'instagram_url' => get_setting('instagram_url'), 'github_url' => get_setting('github_url'), 'telegram_url' => get_setting('telegram_url'), 'whatsapp_url' => get_setting('whatsapp_url'), 'maintenance_mode' => get_setting('maintenance_mode'), ]; $this->view('admin/settings', ['settings' => $settings]); } public function saveSettings() { $this->checkAuth(); $db = db_pdo(); $fields = [ 'site_name', 'contact_email', 'meta_description', 'meta_keywords', 'head_js', 'body_js', 'facebook_url', 'twitter_url', 'instagram_url', 'github_url', 'telegram_url', 'whatsapp_url' ]; foreach ($fields as $field) { if (isset($_POST[$field])) { $stmt = $db->prepare("UPDATE settings SET setting_value = ? WHERE setting_key = ?"); $stmt->execute([$_POST[$field], $field]); } } $site_icon = $this->handleUpload('site_icon_file', 'settings'); if ($site_icon) { $stmt = $db->prepare("UPDATE settings SET setting_value = ? WHERE setting_key = 'site_icon'"); $stmt->execute([$site_icon]); } $site_favicon = $this->handleUpload('site_favicon_file', 'settings'); if ($site_favicon) { $stmt = $db->prepare("UPDATE settings SET setting_value = ? WHERE setting_key = 'site_favicon'"); $stmt->execute([$site_favicon]); } $this->redirect('/admin/settings'); } // Blog Management public function posts() { $this->checkAuth(); $db = db_pdo(); $posts = $db->query("SELECT * FROM posts ORDER BY created_at DESC")->fetchAll(); $this->view('admin/posts/index', ['posts' => $posts]); } public function addPostForm() { $this->checkAuth(); $this->view('admin/posts/form', ['action' => 'add']); } public function addPost() { $this->checkAuth(); $title = $_POST['title']; $slug = $this->slugify($title); $content = $_POST['content']; $status = $_POST['status'] ?? 'published'; $image_path = $this->handleUpload('image_file', 'blog'); $db = db_pdo(); $stmt = $db->prepare("INSERT INTO posts (title, slug, content, image_path, status) VALUES (?, ?, ?, ?, ?)"); $stmt->execute([$title, $slug, $content, $image_path, $status]); $this->redirect('/admin/posts'); } public function editPostForm($params) { $this->checkAuth(); $db = db_pdo(); $post = $db->query("SELECT * FROM posts WHERE id = " . $params['id'])->fetch(); $this->view('admin/posts/form', ['action' => 'edit', 'post' => $post]); } public function editPost($params) { $this->checkAuth(); $title = $_POST['title']; $content = $_POST['content']; $status = $_POST['status']; $db = db_pdo(); $post = $db->query("SELECT * FROM posts WHERE id = " . $params['id'])->fetch(); $image_path = $this->handleUpload('image_file', 'blog') ?: $post['image_path']; $stmt = $db->prepare("UPDATE posts SET title = ?, content = ?, image_path = ?, status = ? WHERE id = ?"); $stmt->execute([$title, $content, $image_path, $status, $params['id']]); $this->redirect('/admin/posts'); } public function deletePost($params) { $this->checkAuth(); $db = db_pdo(); $stmt = $db->prepare("DELETE FROM posts WHERE id = ?"); $stmt->execute([$params['id']]); $this->redirect('/admin/posts'); } // Category Management public function categories() { $this->checkAuth(); $db = db_pdo(); $categories = $db->query("SELECT * FROM categories")->fetchAll(); $this->view('admin/categories/index', ['categories' => $categories]); } public function addCategory() { $this->checkAuth(); $name = $_POST['name']; $slug = $this->slugify($name); $db = db_pdo(); $stmt = $db->prepare("INSERT INTO categories (name, slug) VALUES (?, ?)"); $stmt->execute([$name, $slug]); $this->redirect('/admin/categories'); } public function deleteCategory($params) { $this->checkAuth(); $db = db_pdo(); $stmt = $db->prepare("DELETE FROM categories WHERE id = ?"); $stmt->execute([$params['id']]); $this->redirect('/admin/categories'); } // Withdrawal Management public function withdrawals() { $this->checkAuth(); $db = db_pdo(); $withdrawals = $db->query("SELECT w.*, u.username FROM withdrawals w JOIN users u ON w.user_id = u.id ORDER BY w.created_at DESC")->fetchAll(); $this->view('admin/withdrawals/index', ['withdrawals' => $withdrawals]); } public function approveWithdrawal($params) { $this->checkAuth(); $db = db_pdo(); $stmt = $db->prepare("UPDATE withdrawals SET status = 'approved' WHERE id = ?"); $stmt->execute([$params['id']]); $this->redirect('/admin/withdrawals'); } public function rejectWithdrawal($params) { $this->checkAuth(); $db = db_pdo(); $wd = $db->query("SELECT * FROM withdrawals WHERE id = " . $params['id'])->fetch(); if ($wd && $wd['status'] === 'pending') { $stmt = $db->prepare("UPDATE users SET balance = balance + ? WHERE id = ?"); $stmt->execute([$wd['amount'], $wd['user_id']]); $stmt = $db->prepare("UPDATE withdrawals SET status = 'rejected' WHERE id = ?"); $stmt->execute([$params['id']]); } $this->redirect('/admin/withdrawals'); } public function deleteApk($params) { $this->checkAuth(); $db = db_pdo(); $stmt = $db->prepare("DELETE FROM apks WHERE id = ?"); $stmt->execute([$params['id']]); $this->redirect('/admin/apks'); } // Newsletter Management public function newsletter() { $this->checkAuth(); $db = db_pdo(); $subscribers = $db->query("SELECT * FROM newsletter_subscribers ORDER BY created_at DESC")->fetchAll(); $this->view('admin/newsletter/index', ['subscribers' => $subscribers]); } public function deleteSubscriber($params) { $this->checkAuth(); $db = db_pdo(); $stmt = $db->prepare("DELETE FROM newsletter_subscribers WHERE id = ?"); $stmt->execute([$params['id']]); $this->redirect('/admin/newsletter'); } public function exportSubscribers() { $this->checkAuth(); $db = db_pdo(); $subscribers = $db->query("SELECT email, created_at FROM newsletter_subscribers ORDER BY created_at DESC")->fetchAll(\PDO::FETCH_ASSOC); header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename=subscribers_' . date('Y-m-d') . '.csv'); $output = fopen('php://output', 'w'); fputcsv($output, ['Email', 'Subscribed At']); foreach ($subscribers as $row) { fputcsv($output, $row); } fclose($output); exit; } public function sendNewsletterForm() { $this->checkAuth(); $db = db_pdo(); $total_subscribers = $db->query("SELECT COUNT(*) FROM newsletter_subscribers")->fetchColumn(); $this->view('admin/newsletter/send', ['total_subscribers' => $total_subscribers]); } public function sendNewsletter() { $this->checkAuth(); $subject = $_POST['subject'] ?? ''; $message = $_POST['message'] ?? ''; if (empty($subject) || empty($message)) { $this->view('admin/newsletter/send', ['error' => 'Subject and message are required.']); return; } $db = db_pdo(); $subscribers = $db->query("SELECT email FROM newsletter_subscribers")->fetchAll(\PDO::FETCH_COLUMN); if (empty($subscribers)) { $this->view('admin/newsletter/send', ['error' => 'No subscribers found.']); return; } require_once __DIR__ . '/../../mail/MailService.php'; // We use BCC to prevent subscribers from seeing each other's emails $results = \MailService::sendMail(null, $subject, $message, null, ['bcc' => $subscribers]); if ($results['success']) { $this->view('admin/newsletter/send', ['success' => 'Email sent to ' . count($subscribers) . ' subscribers.']); } else { $this->view('admin/newsletter/send', ['error' => 'Failed to send email: ' . $results['error']]); } } private function slugify($text) { $text = preg_replace('~[^\pL\d]+~u', '-', $text); $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text); $text = preg_replace('~[^-\w]+~', '', $text); $text = trim($text, '-'); $text = preg_replace('~-+~', '-', $text); $text = strtolower($text); return empty($text) ? 'n-a' : $text; } }