Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 0 additions & 74 deletions dec.py

This file was deleted.

11 changes: 11 additions & 0 deletions public_html/.htaccess
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Options -Indexes
RewriteEngine On

RewriteCond %{REQUEST_URI} !^/admin
RewriteCond %{REQUEST_URI} !^/assets
RewriteCond %{REQUEST_URI} !^/api
RewriteCond %{REQUEST_URI} !^/sql
RewriteCond %{REQUEST_URI} !^/logs
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([A-Za-z0-9\-]+)/?$ post.php?slug=$1 [L,QSA]
13 changes: 13 additions & 0 deletions public_html/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Website tin tức đơn giản

Upload toàn bộ thư mục `public_html` lên hosting (DataOnline.vn), import `sql/database.sql`, cập nhật thông tin DB trong `config.php`. Sau khi import có sẵn tài khoản admin/admin567.

## Đường dẫn
- Trang chủ: `/`
- Trang bài: `/slug`
- Đăng nhập: `/admin/login.php`
- Trang quản trị một trang: `/admin/panel.php`

## Lưu ý
- Bật/tắt quảng cáo, link Shopee, link liên hệ nằm trong tab Cài đặt của panel.
- Khi quảng cáo bật và người dùng mở trực tiếp `/slug`, overlay sẽ xuất hiện ngay; đóng overlay sẽ mở tab mới `/slug?ad=0` và tab hiện tại chuyển sang link Shopee.
40 changes: 40 additions & 0 deletions public_html/admin/login.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
require_once __DIR__ . '/../lib/db.php';
require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/error_handler.php';

ensure_session();
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
if (admin_login($username, $password)) {
header('Location: /admin/panel.php');
exit;
}
$error = 'Sai tài khoản hoặc mật khẩu';
}
?><!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Đăng nhập</title>
<link rel="stylesheet" href="/assets/css/admin.css">
</head>
<body>
<div class="container">
<div class="card">
<h2>Đăng nhập admin</h2>
<?php if ($error): ?><p class="alert"><?php echo htmlspecialchars($error, ENT_QUOTES, 'UTF-8'); ?></p><?php endif; ?>
<form method="post" autocomplete="off">
<label>Tên đăng nhập</label>
<input name="username" required>
<label>Mật khẩu</label>
<input type="password" name="password" required>
<button type="submit">Đăng nhập</button>
</form>
</div>
</div>
</body>
</html>
7 changes: 7 additions & 0 deletions public_html/admin/logout.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/error_handler.php';

admin_logout();
header('Location: /admin/login.php');
exit;
181 changes: 181 additions & 0 deletions public_html/admin/panel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
<?php
require_once __DIR__ . '/../lib/db.php';
require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/csrf.php';
require_once __DIR__ . '/../lib/error_handler.php';
require_once __DIR__ . '/../lib/slugify.php';
require_once __DIR__ . '/../lib/track.php';

admin_require();
$pdo = db();
$message = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!csrf_check($_POST['csrf'] ?? '')) {
die('CSRF không hợp lệ');
}

$action = $_POST['action'] ?? '';
if ($action === 'create_post') {
$title = trim($_POST['title'] ?? '');
$slug = trim($_POST['slug'] ?? '');
$slug = $slug !== '' ? $slug : slugify($title);
$content = trim($_POST['content'] ?? '');
$images = array_filter(array_map('trim', explode("\n", $_POST['images'] ?? '')));
$videos = array_filter(array_map('trim', explode("\n", $_POST['videos'] ?? '')));
$stmt = $pdo->prepare('INSERT INTO posts (title, slug, content, images_json, videos_json, created_at) VALUES (?, ?, ?, ?, ?, NOW())');
$stmt->execute([$title, $slug, $content, json_encode(array_values($images)), json_encode(array_values($videos))]);
$message = 'Đã thêm bài';
} elseif ($action === 'update_post') {
$postId = (int)($_POST['post_id'] ?? 0);
$title = trim($_POST['title'] ?? '');
$slug = trim($_POST['slug'] ?? '');
$slug = $slug !== '' ? $slug : slugify($title);
$content = trim($_POST['content'] ?? '');
$images = array_filter(array_map('trim', explode("\n", $_POST['images'] ?? '')));
$videos = array_filter(array_map('trim', explode("\n", $_POST['videos'] ?? '')));
$stmt = $pdo->prepare('UPDATE posts SET title=?, slug=?, content=?, images_json=?, videos_json=? WHERE id=?');
$stmt->execute([$title, $slug, $content, json_encode(array_values($images)), json_encode(array_values($videos)), $postId]);
$message = 'Đã cập nhật';
} elseif ($action === 'delete_post') {
$postId = (int)($_POST['post_id'] ?? 0);
$stmt = $pdo->prepare('DELETE FROM posts WHERE id = ?');
$stmt->execute([$postId]);
$message = 'Đã xóa';
} elseif ($action === 'save_settings') {
$adsEnabled = isset($_POST['ads_enabled']) ? 1 : 0;
$adLink = trim($_POST['ad_link'] ?? '');
$adTitle = trim($_POST['ad_title'] ?? '');
$adBody = trim($_POST['ad_body'] ?? '');
$contactLink = trim($_POST['contact_link'] ?? '');
$stmt = $pdo->prepare('UPDATE settings SET ads_enabled=?, ad_link=?, ad_title=?, ad_body=?, contact_link=? WHERE id = 1');
$stmt->execute([$adsEnabled, $adLink, $adTitle, $adBody, $contactLink]);
$message = 'Đã lưu cài đặt';
}
}

$settings = load_settings($pdo);
$posts = $pdo->query('SELECT * FROM posts ORDER BY created_at DESC')->fetchAll();
$totalPosts = count($posts);
$totalViews = stat_total($pdo, 'view');
$totalClicks = stat_total($pdo, 'ad_click');
$csrf = csrf_token();
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https://' : 'http://';
$baseUrl = $scheme . ($_SERVER['HTTP_HOST'] ?? 'domain');
?><!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quản trị</title>
<link rel="stylesheet" href="/assets/css/admin.css">
<script>
function switchTab(id){
document.querySelectorAll('.tab').forEach(el=>el.classList.add('hidden'));
document.getElementById(id).classList.remove('hidden');
}
function copyLink(link){
navigator.clipboard.writeText(link).then(()=>{
alert('Đã copy link');
});
}
</script>
</head>
<body>
<div class="container">
<div class="card">
<div class="top-row">
<h2>Xin chào, <?php echo htmlspecialchars(admin_name(), ENT_QUOTES, 'UTF-8'); ?></h2>
<a class="logout" href="/admin/logout.php">Đăng xuất</a>
</div>
<div class="nav">
<button onclick="switchTab('tab-post')">Bài viết</button>
<button onclick="switchTab('tab-settings')">Cài đặt</button>
<button onclick="switchTab('tab-stats')">Thống kê</button>
</div>
<?php if ($message): ?><p class="badge"><?php echo htmlspecialchars($message, ENT_QUOTES, 'UTF-8'); ?></p><?php endif; ?>

<div id="tab-post" class="tab">
<h3>Thêm bài</h3>
<form method="post">
<input type="hidden" name="csrf" value="<?php echo htmlspecialchars($csrf, ENT_QUOTES, 'UTF-8'); ?>">
<input type="hidden" name="action" value="create_post">
<label>Tiêu đề</label>
<input name="title" required>
<label>Slug (để trống tự tạo)</label>
<input name="slug">
<label>Nội dung</label>
<textarea name="content" rows="4"></textarea>
<label>Link ảnh (mỗi dòng một link Telegram)</label>
<textarea name="images" rows="4"></textarea>
<label>Link video (mỗi dòng một link Telegram)</label>
<textarea name="videos" rows="4"></textarea>
<button type="submit">Thêm bài</button>
</form>

<h3>Danh sách bài</h3>
<?php foreach ($posts as $item): $itemId = (int)$item['id']; $slugVal = $item['slug']; $full = $baseUrl . '/' . $slugVal; ?>
<div class="card inner">
<div class="row">
<strong><?php echo htmlspecialchars($item['title'], ENT_QUOTES, 'UTF-8'); ?></strong>
<span class="badge">ID <?php echo $itemId; ?></span>
</div>
<p class="link-copy">Link: <?php echo htmlspecialchars($full, ENT_QUOTES, 'UTF-8'); ?></p>
<button type="button" onclick="copyLink('<?php echo htmlspecialchars($full, ENT_QUOTES, 'UTF-8'); ?>')">Copy link</button>
<details>
<summary>Sửa / Xóa</summary>
<form method="post">
<input type="hidden" name="csrf" value="<?php echo htmlspecialchars($csrf, ENT_QUOTES, 'UTF-8'); ?>">
<input type="hidden" name="action" value="update_post">
<input type="hidden" name="post_id" value="<?php echo $itemId; ?>">
<label>Tiêu đề</label>
<input name="title" value="<?php echo htmlspecialchars($item['title'], ENT_QUOTES, 'UTF-8'); ?>" required>
<label>Slug</label>
<input name="slug" value="<?php echo htmlspecialchars($item['slug'], ENT_QUOTES, 'UTF-8'); ?>" required>
<label>Nội dung</label>
<textarea name="content" rows="4"><?php echo htmlspecialchars($item['content'], ENT_QUOTES, 'UTF-8'); ?></textarea>
<label>Link ảnh</label>
<textarea name="images" rows="3"><?php echo htmlspecialchars(implode("\n", json_decode($item['images_json'], true) ?: []), ENT_QUOTES, 'UTF-8'); ?></textarea>
<label>Link video</label>
<textarea name="videos" rows="3"><?php echo htmlspecialchars(implode("\n", json_decode($item['videos_json'], true) ?: []), ENT_QUOTES, 'UTF-8'); ?></textarea>
<button type="submit">Lưu</button>
</form>
<form method="post" onsubmit="return confirm('Xóa bài này?');">
<input type="hidden" name="csrf" value="<?php echo htmlspecialchars($csrf, ENT_QUOTES, 'UTF-8'); ?>">
<input type="hidden" name="action" value="delete_post">
<input type="hidden" name="post_id" value="<?php echo $itemId; ?>">
<button type="submit" class="danger">Xóa</button>
</form>
</details>
</div>
<?php endforeach; ?>
</div>

<div id="tab-settings" class="tab hidden">
<h3>Cài đặt</h3>
<form method="post">
<input type="hidden" name="csrf" value="<?php echo htmlspecialchars($csrf, ENT_QUOTES, 'UTF-8'); ?>">
<input type="hidden" name="action" value="save_settings">
<label><input type="checkbox" name="ads_enabled" <?php echo !empty($settings['ads_enabled']) ? 'checked' : ''; ?>> Bật quảng cáo</label>
<label>Link Shopee</label>
<input name="ad_link" value="<?php echo htmlspecialchars($settings['ad_link'], ENT_QUOTES, 'UTF-8'); ?>" placeholder="https://s.shopee.vn/xxxx">
<label>Tiêu đề quảng cáo</label>
<input name="ad_title" value="<?php echo htmlspecialchars($settings['ad_title'], ENT_QUOTES, 'UTF-8'); ?>">
<label>Nội dung quảng cáo</label>
<textarea name="ad_body" rows="3"><?php echo htmlspecialchars($settings['ad_body'], ENT_QUOTES, 'UTF-8'); ?></textarea>
<label>Link liên hệ admin</label>
<input name="contact_link" value="<?php echo htmlspecialchars($settings['contact_link'], ENT_QUOTES, 'UTF-8'); ?>" placeholder="Facebook hoặc Telegram">
<button type="submit">Lưu</button>
</form>
</div>

<div id="tab-stats" class="tab hidden">
<h3>Thống kê</h3>
<p>Tổng số bài báo: <strong><?php echo $totalPosts; ?></strong></p>
<p>Tổng lượt xem bài: <strong><?php echo $totalViews; ?></strong></p>
<p>Tổng lượt click quảng cáo: <strong><?php echo $totalClicks; ?></strong></p>
</div>
</div>
</div>
</body>
</html>
22 changes: 22 additions & 0 deletions public_html/api/track.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php
require_once __DIR__ . '/../lib/db.php';
require_once __DIR__ . '/../lib/error_handler.php';
require_once __DIR__ . '/../lib/track.php';

$pdo = db();
$type = $_POST['type'] ?? 'view';
$slug = $_POST['slug'] ?? '';
$postId = null;

if ($slug !== '') {
$stmt = $pdo->prepare('SELECT id FROM posts WHERE slug = ? LIMIT 1');
$stmt->execute([$slug]);
$row = $stmt->fetch();
if ($row) {
$postId = (int)$row['id'];
}
}

record_stat($pdo, $type === 'ad_click' ? 'ad_click' : 'view', $postId, 60);

echo json_encode(['ok' => true]);
24 changes: 24 additions & 0 deletions public_html/assets/css/admin.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
body {
margin: 0;
font-family: 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
background: #0b1222;
color: #e2e8f0;
}
.container { max-width: 880px; margin: 0 auto; padding: 18px 14px 60px; }
.card { background: #111827; border-radius: 12px; padding: 16px; box-shadow: 0 12px 32px rgba(0,0,0,0.35); }
.card + .card { margin-top: 12px; }
input, textarea, button { width: 100%; padding: 12px; margin: 6px 0 12px; border-radius: 8px; border: 1px solid #1f2937; background: #0d152b; color: #e2e8f0; }
label { font-weight: 600; display: block; margin-top: 4px; }
button { cursor: pointer; background: #2563eb; border: none; font-weight: 600; }
button.danger { background: #ef4444; }
.nav { display: flex; gap: 8px; margin: 12px 0; }
.nav button { flex: 1; }
.badge { display: inline-block; padding: 6px 10px; background: #1d4ed8; border-radius: 8px; font-size: 12px; }
.alert { color: #fca5a5; }
.hidden { display: none; }
.link-copy { font-size: 13px; color: #93c5fd; word-break: break-all; }
.row { display: flex; justify-content: space-between; align-items: center; }
.top-row { display: flex; justify-content: space-between; align-items: center; gap: 12px; }
.logout { color: #93c5fd; text-decoration: none; }
.card.inner { margin-top: 10px; }
@media (max-width: 600px) { .nav { flex-direction: column; } .top-row { flex-direction: column; align-items: flex-start; } }
Loading