-
Notifications
You must be signed in to change notification settings - Fork 0
Build shared-hosting news site with ad flow and themes #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| RewriteEngine On | ||
| RewriteBase / | ||
| RewriteCond %{REQUEST_URI} !^/(admin|api|bots|assets|includes|app|sql)/ | ||
| RewriteCond %{REQUEST_FILENAME} !-f | ||
| RewriteCond %{REQUEST_FILENAME} !-d | ||
| RewriteRule ^([^/]+)/?$ post.php?slug=$1 [QSA,L] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # Tin tức PHP cho shared hosting (dataonline.vn) | ||
|
|
||
| ## Cấu trúc thư mục | ||
| - Upload toàn bộ thư mục `public_html` lên hosting (đặt đúng tên public_html). | ||
| - `app/config/config.php`: sửa thông tin DB, BASE_URL, SESSION_SALT, đường dẫn cache, token Telegram. | ||
| - `sql/database.sql`: import vào MySQL (có dữ liệu mẫu + tài khoản admin/admin123 thay đổi ngay). | ||
|
|
||
| ## Cài đặt | ||
| 1. Tạo database MySQL, import `sql/database.sql`. | ||
| 2. Mở `app/config/config.php` và điền DB_HOST/DB_NAME/DB_USER/DB_PASS + BASE_URL. | ||
| 3. Đảm bảo thư mục `app/cache` có quyền ghi. | ||
| 4. Bật mod_rewrite trên Apache (dataonline.vn bật sẵn). `.htaccess` đã cấu hình slug `/slug` → `post.php?slug=...` và bỏ qua `/admin|/api|/bots|/assets|/includes|/app|/sql`. | ||
|
|
||
| ## Đăng nhập admin | ||
| - URL: `BASE_URL/admin/login.php` | ||
| - Tài khoản mẫu: admin / admin123 (hãy đổi trong DB hoặc cập nhật mật khẩu bằng `password_hash`). | ||
|
|
||
| ## Kiểm thử bắt buộc | ||
| - **Slug**: `/tin-nong-hom-nay` mở đúng bài. | ||
| - **Quảng cáo Shopee** (ads_enabled=1, ad_link có): | ||
| - Click bài từ trang chủ hoặc truy cập trực tiếp `/slug` → overlay hiện. | ||
| - Click overlay hoặc nút X: tab hiện tại chuyển ngay đến ad_link, đồng thời mở tab mới `/slug?ad=0` không quảng cáo. | ||
| - **Tắt quảng cáo**: ads_enabled=0 hoặc ad_link rỗng → không overlay. | ||
| - **Chống click ảo**: gửi nhiều lần `/api/track.php` trong 30s không tăng vô hạn (rate limit + token + hash IP/UA). | ||
| - **Theme**: đổi theme 1/2/3 trong admin/settings, frontend đổi CSS. | ||
| - **Telegram**: điền TELEGRAM_BOT_TOKEN + TELEGRAM_ADMIN_CHAT_ID rồi publish bài mới (status=public) hoặc có ad_click → Telegram nhận thông báo (gộp click, gửi tối thiểu mỗi 5 phút). | ||
| - **Responsive**: kiểm tra mobile/laptop, hamburger 3D mở menu. | ||
|
|
||
| ## Lưu ý | ||
| - Không dùng .env, không cần composer. Thuần PHP/PDO. | ||
| - Cache file: settings (5 phút), home (60s), bài viết (60s). | ||
| - Assets đã thêm lazy-load (iframe/JS), nên đặt header cache-control dài hạn trên hosting nếu muốn. | ||
| - Nếu cần webhook Telegram, trỏ `bots/telegram/webhook.php` và bật HTTPS. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <?php | ||
| require_once __DIR__ . '/includes/auth_check.php'; | ||
| require_once __DIR__ . '/../app/lib/db.php'; | ||
| include __DIR__ . '/includes/header.php'; | ||
| $stats = db()->query('SELECT event_type, COUNT(*) as c FROM events GROUP BY event_type')->fetchAll(); | ||
| $byType = []; foreach ($stats as $s){$byType[$s['event_type']] = $s['c'];} | ||
| ?> | ||
| <div class="card"> | ||
| <h3>Tổng quan</h3> | ||
| <p>Page views: <?= $byType['page_view'] ?? 0 ?></p> | ||
| <p>Post views: <?= $byType['post_view'] ?? 0 ?></p> | ||
| <p>Ad clicks: <?= $byType['ad_click'] ?? 0 ?></p> | ||
| </div> | ||
| <?php include __DIR__ . '/includes/footer.php'; ?> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| <?php | ||
| session_start(); | ||
| require_once __DIR__ . '/../../app/config/config.php'; | ||
| if (empty($_SESSION['admin_id'])) { | ||
| header('Location: ' . BASE_URL . '/admin/login.php'); | ||
| exit; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| </main> | ||
| </body> | ||
| </html> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| <?php require_once __DIR__ . '/../../app/config/config.php'; ?> | ||
| <!DOCTYPE html> | ||
| <html lang="vi"> | ||
| <head><meta charset="UTF-8"><title>Admin</title><link rel="stylesheet" href="<?= BASE_URL ?>/assets/css/admin.css"></head> | ||
| <body> | ||
| <header class="admin-header"> | ||
| <div>Admin Panel</div> | ||
| <nav> | ||
| <a href="<?= BASE_URL ?>/admin/dashboard.php">Dashboard</a> | ||
| <a href="<?= BASE_URL ?>/admin/posts.php">Bài viết</a> | ||
| <a href="<?= BASE_URL ?>/admin/settings.php">Cài đặt</a> | ||
| <a href="<?= BASE_URL ?>/admin/stats.php">Thống kê</a> | ||
| <a href="<?= BASE_URL ?>/admin/logout.php">Logout</a> | ||
| </nav> | ||
| </header> | ||
| <main class="admin-main"> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| <?php | ||
| session_start(); | ||
| require_once __DIR__ . '/../app/lib/auth.php'; | ||
| $message = ''; | ||
| if ($_SERVER['REQUEST_METHOD'] === 'POST') { | ||
| require_once __DIR__ . '/../app/lib/csrf.php'; | ||
| csrf_verify(); | ||
| if (admin_login($_POST['username'], $_POST['password'])) { | ||
| header('Location: ' . BASE_URL . '/admin/dashboard.php'); | ||
| exit; | ||
| } | ||
| $message = 'Sai tài khoản hoặc mật khẩu'; | ||
| } | ||
| ?> | ||
| <!DOCTYPE html> | ||
| <html lang="vi"> | ||
| <head><meta charset="UTF-8"><title>Đăng nhập</title><link rel="stylesheet" href="<?= BASE_URL ?>/assets/css/admin.css"></head> | ||
| <body class="login-page"> | ||
| <form method="post" class="login-form"> | ||
| <h2>Admin Login</h2> | ||
| <?php if ($message): ?><div class="alert"><?= htmlspecialchars($message) ?></div><?php endif; ?> | ||
| <?php if (function_exists('csrf_field')) echo csrf_field(); ?> | ||
| <input type="text" name="username" placeholder="Tên đăng nhập" required> | ||
| <input type="password" name="password" placeholder="Mật khẩu" required> | ||
| <button type="submit">Đăng nhập</button> | ||
| </form> | ||
| </body> | ||
| </html> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| <?php | ||
| require_once __DIR__ . '/../app/lib/auth.php'; | ||
| admin_logout(); | ||
| header('Location: ' . BASE_URL . '/admin/login.php'); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| <?php | ||
| require_once __DIR__ . '/includes/auth_check.php'; | ||
| require_once __DIR__ . '/../app/lib/db.php'; | ||
| require_once __DIR__ . '/../app/lib/slugify.php'; | ||
| require_once __DIR__ . '/../app/lib/csrf.php'; | ||
| require_once __DIR__ . '/../app/lib/telegram.php'; | ||
| $message = ''; | ||
| if ($_SERVER['REQUEST_METHOD'] === 'POST') { | ||
| csrf_verify(); | ||
| $slug = $_POST['slug'] ?: slugify($_POST['title']); | ||
| $stmt = db()->prepare('INSERT INTO articles (slug,title,excerpt,content,telegram_media,meta_title,meta_description,meta_keywords,status) VALUES (:slug,:title,:excerpt,:content,:media,:mt,:md,:mk,:status)'); | ||
| $stmt->execute([ | ||
| ':slug'=>$slug, | ||
| ':title'=>$_POST['title'], | ||
| ':excerpt'=>$_POST['excerpt'], | ||
| ':content'=>$_POST['content'], | ||
| ':media'=>$_POST['telegram_media'], | ||
| ':mt'=>$_POST['meta_title'], | ||
| ':md'=>$_POST['meta_description'], | ||
| ':mk'=>$_POST['meta_keywords'], | ||
| ':status'=>$_POST['status'] | ||
| ]); | ||
| if ($_POST['status']==='public') { | ||
| sendTelegramMessage('Bài mới: '.$_POST['title'].' - '.BASE_URL.'/'.$slug); | ||
| } | ||
| header('Location: '.BASE_URL.'/admin/posts.php'); | ||
| exit; | ||
| } | ||
| include __DIR__ . '/includes/header.php'; | ||
| ?> | ||
| <h3>Thêm bài</h3> | ||
| <form method="post"> | ||
| <?= csrf_field() ?> | ||
| <label>Tiêu đề<input type="text" name="title" required></label> | ||
| <label>Slug<input type="text" name="slug"></label> | ||
| <label>Tóm tắt<textarea name="excerpt"></textarea></label> | ||
| <label>Nội dung<textarea name="content" rows="10"></textarea></label> | ||
| <label>Telegram media (mỗi dòng một link)<textarea name="telegram_media"></textarea></label> | ||
| <label>Meta title<input type="text" name="meta_title"></label> | ||
| <label>Meta description<input type="text" name="meta_description"></label> | ||
| <label>Meta keywords<input type="text" name="meta_keywords"></label> | ||
| <label>Trạng thái<select name="status"><option value="draft">Draft</option><option value="public">Public</option></select></label> | ||
| <button class="btn" type="submit">Lưu</button> | ||
| </form> | ||
| <?php include __DIR__ . '/includes/footer.php'; ?> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <?php | ||
| require_once __DIR__ . '/includes/auth_check.php'; | ||
| require_once __DIR__ . '/../app/lib/db.php'; | ||
| require_once __DIR__ . '/../app/lib/slugify.php'; | ||
| $id = (int)($_GET['id'] ?? 0); | ||
| $stmt = db()->prepare('SELECT * FROM articles WHERE id=:id'); | ||
| $stmt->execute([':id'=>$id]); | ||
| $a = $stmt->fetch(); | ||
| if($a){ | ||
| $newSlug = slugify($a['slug'].'-copy-'.rand(100,999)); | ||
| $ins = db()->prepare('INSERT INTO articles (slug,title,excerpt,content,telegram_media,meta_title,meta_description,meta_keywords,status) VALUES (:slug,:title,:excerpt,:content,:media,:mt,:md,:mk,:status)'); | ||
| $ins->execute([':slug'=>$newSlug,':title'=>$a['title'].' (copy)',':excerpt'=>$a['excerpt'],':content'=>$a['content'],':media'=>$a['telegram_media'],':mt'=>$a['meta_title'],':md'=>$a['meta_description'],':mk'=>$a['meta_keywords'],':status'=>'draft']); | ||
| } | ||
| header('Location: '.BASE_URL.'/admin/posts.php'); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| <?php | ||
| require_once __DIR__ . '/includes/auth_check.php'; | ||
| require_once __DIR__ . '/../app/lib/db.php'; | ||
| $id = (int)($_GET['id'] ?? 0); | ||
| if ($id) { | ||
| $stmt = db()->prepare('DELETE FROM articles WHERE id=:id'); | ||
| $stmt->execute([':id'=>$id]); | ||
| } | ||
| header('Location: '.BASE_URL.'/admin/posts.php'); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| <?php | ||
| require_once __DIR__ . '/includes/auth_check.php'; | ||
| require_once __DIR__ . '/../app/lib/db.php'; | ||
| require_once __DIR__ . '/../app/lib/slugify.php'; | ||
| require_once __DIR__ . '/../app/lib/csrf.php'; | ||
| $id = (int)($_GET['id'] ?? 0); | ||
| $stmt = db()->prepare('SELECT * FROM articles WHERE id=:id'); | ||
| $stmt->execute([':id'=>$id]); | ||
| $article = $stmt->fetch(); | ||
| if(!$article){exit('Not found');} | ||
| if($_SERVER['REQUEST_METHOD']==='POST'){ | ||
| csrf_verify(); | ||
| $slug = $_POST['slug'] ?: slugify($_POST['title']); | ||
| $u = db()->prepare('UPDATE articles SET slug=:slug,title=:title,excerpt=:excerpt,content=:content,telegram_media=:media,meta_title=:mt,meta_description=:md,meta_keywords=:mk,status=:status WHERE id=:id'); | ||
| $u->execute([ | ||
| ':slug'=>$slug,':title'=>$_POST['title'],':excerpt'=>$_POST['excerpt'],':content'=>$_POST['content'],':media'=>$_POST['telegram_media'],':mt'=>$_POST['meta_title'],':md'=>$_POST['meta_description'],':mk'=>$_POST['meta_keywords'],':status'=>$_POST['status'],':id'=>$id | ||
| ]); | ||
| header('Location: '.BASE_URL.'/admin/posts.php'); | ||
| exit; | ||
| } | ||
| include __DIR__ . '/includes/header.php'; | ||
| ?> | ||
| <h3>Sửa bài</h3> | ||
| <form method="post"> | ||
| <?= csrf_field() ?> | ||
| <label>Tiêu đề<input type="text" name="title" value="<?= htmlspecialchars($article['title']) ?>" required></label> | ||
| <label>Slug<input type="text" name="slug" value="<?= htmlspecialchars($article['slug']) ?>"></label> | ||
| <label>Tóm tắt<textarea name="excerpt"><?= htmlspecialchars($article['excerpt']) ?></textarea></label> | ||
| <label>Nội dung<textarea name="content" rows="10"><?= htmlspecialchars($article['content']) ?></textarea></label> | ||
| <label>Telegram media<textarea name="telegram_media"><?= htmlspecialchars($article['telegram_media']) ?></textarea></label> | ||
| <label>Meta title<input type="text" name="meta_title" value="<?= htmlspecialchars($article['meta_title']) ?>"></label> | ||
| <label>Meta description<input type="text" name="meta_description" value="<?= htmlspecialchars($article['meta_description']) ?>"></label> | ||
| <label>Meta keywords<input type="text" name="meta_keywords" value="<?= htmlspecialchars($article['meta_keywords']) ?>"></label> | ||
| <label>Trạng thái<select name="status"><option value="draft" <?= $article['status']==='draft'?'selected':'' ?>>Draft</option><option value="public" <?= $article['status']==='public'?'selected':'' ?>>Public</option></select></label> | ||
| <button class="btn" type="submit">Lưu</button> | ||
| </form> | ||
| <?php include __DIR__ . '/includes/footer.php'; ?> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| <?php | ||
| require_once __DIR__ . '/includes/auth_check.php'; | ||
| require_once __DIR__ . '/../app/lib/db.php'; | ||
| include __DIR__ . '/includes/header.php'; | ||
| $articles = db()->query('SELECT * FROM articles ORDER BY created_at DESC')->fetchAll(); | ||
| ?> | ||
| <div class="actions"><a class="btn" href="<?= BASE_URL ?>/admin/post_add.php">Thêm bài</a></div> | ||
| <table> | ||
| <tr><th>Tiêu đề</th><th>Slug</th><th>Trạng thái</th><th>Hành động</th></tr> | ||
| <?php foreach ($articles as $a): ?> | ||
| <tr> | ||
| <td><?= htmlspecialchars($a['title']) ?></td> | ||
| <td><?= htmlspecialchars($a['slug']) ?></td> | ||
| <td><?= $a['status'] ?></td> | ||
| <td> | ||
| <a href="<?= BASE_URL ?>/admin/post_edit.php?id=<?= $a['id'] ?>">Sửa</a> | | ||
| <a href="<?= BASE_URL ?>/admin/post_copy.php?id=<?= $a['id'] ?>">Copy</a> | | ||
| <a href="<?= BASE_URL ?>/admin/post_delete.php?id=<?= $a['id'] ?>" onclick="return confirm('Xóa?')">Xóa</a> | ||
| </td> | ||
| </tr> | ||
| <?php endforeach; ?> | ||
| </table> | ||
| <?php include __DIR__ . '/includes/footer.php'; ?> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| <?php | ||
| require_once __DIR__ . '/includes/auth_check.php'; | ||
| require_once __DIR__ . '/../app/lib/db.php'; | ||
| require_once __DIR__ . '/../app/lib/csrf.php'; | ||
| $settings = db()->query('SELECT * FROM site_settings LIMIT 1')->fetch(); | ||
| if($_SERVER['REQUEST_METHOD']==='POST'){ | ||
| csrf_verify(); | ||
| $logoPath = $settings['logo_path']; | ||
| $bannerPath = $settings['banner_path']; | ||
| if (!empty($_FILES['logo']['name'])) { | ||
| $ext = pathinfo($_FILES['logo']['name'], PATHINFO_EXTENSION); | ||
| $name = 'assets/img/logo_'.time().'.'.$ext; | ||
| move_uploaded_file($_FILES['logo']['tmp_name'], __DIR__.'/../'.$name); | ||
| $logoPath = $name; | ||
|
Comment on lines
+10
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The settings form saves uploaded logo/banner files directly to Useful? React with 👍 / 👎. |
||
| } | ||
| if (!empty($_FILES['banner']['name'])) { | ||
| $ext = pathinfo($_FILES['banner']['name'], PATHINFO_EXTENSION); | ||
| $name = 'assets/img/banner_'.time().'.'.$ext; | ||
| move_uploaded_file($_FILES['banner']['tmp_name'], __DIR__.'/../'.$name); | ||
| $bannerPath = $name; | ||
| } | ||
| $upd = db()->prepare('UPDATE site_settings SET site_name=:sn, site_description=:sd, logo_path=:logo, banner_path=:banner, theme=:theme, ads_enabled=:ads, ad_link=:link, ad_title=:title, ad_body=:body, updated_at=NOW() WHERE id=1'); | ||
| $upd->execute([ | ||
| ':sn'=>$_POST['site_name'], ':sd'=>$_POST['site_description'], ':logo'=>$logoPath, ':banner'=>$bannerPath, | ||
| ':theme'=>$_POST['theme'], ':ads'=>!empty($_POST['ads_enabled'])?1:0, ':link'=>$_POST['ad_link'], ':title'=>$_POST['ad_title'], ':body'=>$_POST['ad_body'] | ||
| ]); | ||
| header('Location: '.BASE_URL.'/admin/settings.php'); | ||
| exit; | ||
| } | ||
| include __DIR__ . '/includes/header.php'; | ||
| ?> | ||
| <h3>Cài đặt</h3> | ||
| <form method="post" enctype="multipart/form-data"> | ||
| <?= csrf_field() ?> | ||
| <label>Tên site<input type="text" name="site_name" value="<?= htmlspecialchars($settings['site_name']) ?>"></label> | ||
| <label>Mô tả<input type="text" name="site_description" value="<?= htmlspecialchars($settings['site_description']) ?>"></label> | ||
| <label>Logo<input type="file" name="logo"></label> | ||
| <label>Banner<input type="file" name="banner"></label> | ||
| <label>Theme<select name="theme"> | ||
| <option value="1" <?= $settings['theme']==='1'?'selected':'' ?>>Theme 1</option> | ||
| <option value="2" <?= $settings['theme']==='2'?'selected':'' ?>>Theme 2</option> | ||
| <option value="3" <?= $settings['theme']==='3'?'selected':'' ?>>Theme 3</option> | ||
| </select></label> | ||
| <label><input type="checkbox" name="ads_enabled" value="1" <?= $settings['ads_enabled']?'checked':'' ?>> Bật quảng cáo</label> | ||
| <label>Link Shopee<input type="url" name="ad_link" value="<?= htmlspecialchars($settings['ad_link']) ?>"></label> | ||
| <label>Tiêu đề quảng cáo<input type="text" name="ad_title" value="<?= htmlspecialchars($settings['ad_title']) ?>"></label> | ||
| <label>Nội dung quảng cáo<textarea name="ad_body"><?= htmlspecialchars($settings['ad_body']) ?></textarea></label> | ||
| <button class="btn" type="submit">Lưu</button> | ||
| </form> | ||
| <?php include __DIR__ . '/includes/footer.php'; ?> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| <?php | ||
| require_once __DIR__ . '/includes/auth_check.php'; | ||
| require_once __DIR__ . '/../app/lib/db.php'; | ||
| require_once __DIR__ . '/../app/lib/csrf.php'; | ||
|
|
||
| if($_SERVER['REQUEST_METHOD']==='POST'){ | ||
| csrf_verify(); | ||
| if(isset($_POST['reset_all'])){ | ||
| db()->exec('TRUNCATE TABLE events'); | ||
| } | ||
| header('Location: '.BASE_URL.'/admin/stats.php'); | ||
| exit; | ||
| } | ||
| $daily = db()->query('SELECT DATE(created_at) d, event_type, COUNT(*) c FROM events GROUP BY d, event_type ORDER BY d DESC LIMIT 30')->fetchAll(); | ||
| include __DIR__ . '/includes/header.php'; | ||
| ?> | ||
| <h3>Thống kê</h3> | ||
| <table> | ||
| <tr><th>Ngày</th><th>Loại</th><th>Số</th></tr> | ||
| <?php foreach($daily as $row): ?> | ||
| <tr><td><?= $row['d'] ?></td><td><?= $row['event_type'] ?></td><td><?= $row['c'] ?></td></tr> | ||
| <?php endforeach; ?> | ||
| </table> | ||
| <form method="post" onsubmit="return confirm('Reset toàn bộ?')"> | ||
| <?= csrf_field() ?> | ||
| <button class="btn" name="reset_all" value="1">Reset thống kê</button> | ||
| </form> | ||
| <?php include __DIR__ . '/includes/footer.php'; ?> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <?php | ||
| require_once __DIR__ . '/../app/lib/db.php'; | ||
| header('Content-Type: application/json'); | ||
| $totals = db()->query('SELECT event_type, COUNT(*) c FROM events GROUP BY event_type')->fetchAll(); | ||
| echo json_encode($totals); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| <?php | ||
| session_start(); | ||
| require_once __DIR__ . '/../app/lib/db.php'; | ||
| require_once __DIR__ . '/../app/lib/rate_limit.php'; | ||
| header('Content-Type: application/json'); | ||
| $event = $_POST['event'] ?? $_GET['event'] ?? ''; | ||
| $slug = $_POST['slug'] ?? $_GET['slug'] ?? null; | ||
| $token = $_POST['token'] ?? $_GET['token'] ?? ''; | ||
| if (!$event || !$token || ($event==='post_view' && !$slug)) { http_response_code(400); echo json_encode(['ok'=>false]); exit; } | ||
| if (!hash_equals($_SESSION['track_token'] ?? '', $token)) { http_response_code(403); echo json_encode(['ok'=>false]); exit; } | ||
| if (!in_array($event, ['page_view','post_view','ad_click'])) { http_response_code(400); echo json_encode(['ok'=>false]); exit; } | ||
| $window = $event==='ad_click' ? 120 : 30; | ||
| if (!rate_limit($event, $slug, $window)) { echo json_encode(['ok'=>false,'rate_limited'=>true]); exit; } | ||
| log_event($event, $slug, $token); | ||
| if ($event==='ad_click') { require_once __DIR__ . '/../app/lib/telegram.php'; queue_click_notify(1); } | ||
| echo json_encode(['ok'=>true]); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <?php | ||
| // Cấu hình hệ thống (không dùng .env) | ||
| define('DB_HOST', 'localhost'); | ||
| define('DB_NAME', 'news_site'); | ||
| define('DB_USER', 'db_user'); | ||
| define('DB_PASS', 'db_pass'); | ||
|
|
||
| define('BASE_URL', 'https://yourdomain.com'); | ||
| define('SESSION_SALT', 'change_me_salt'); | ||
| define('CACHE_PATH', __DIR__ . '/../cache'); | ||
| define('CACHE_TTL', 300); // giây | ||
|
|
||
| define('TELEGRAM_BOT_TOKEN', ''); | ||
| define('TELEGRAM_ADMIN_CHAT_ID', ''); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logout handler calls
admin_logout()without ever callingsession_start(), whileadmin_logout()only invokessession_destroy(). PHP ignoressession_destroy()when no session is active, so this endpoint redirects without clearing the session cookie or data and the admin remains logged in. Start the session before destroying it (or callsession_start()insideadmin_logout()) so logout actually ends the session.Useful? React with 👍 / 👎.