-
Notifications
You must be signed in to change notification settings - Fork 0
Add full AVSTube PHP/MySQL video sharing site with admin, popup ads, and SQL schema #46
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,28 +1,62 @@ | ||
| # Decrypt | ||
| ### [+] Created By HTR-TECH (@***tahmid.rayat***) | ||
| ### [+] Disclaimer : | ||
| ***Decrypter is a tool to decrypt Encrypted Bash Scripts into a Readable Format.This Tool is created for Educational Purpose only.I am not responsible for any misuse of this tool.*** | ||
|
|
||
| <img src="https://raw.githubusercontent.com/htr-tech/release-download/master/images/decrypter.png" alt="" border="0" /> | ||
|
|
||
| ### [+] Installation | ||
| ```apt update``` | ||
|
|
||
| ```apt install git python2 -y``` | ||
|
|
||
| ```git clone https://github.com/hax0rtahm1d/decrypt``` | ||
|
|
||
| ```cd decrypt``` | ||
|
|
||
| ```python2 dec.py``` | ||
|
|
||
| ### Or, Use Single Command | ||
|
|
||
| ``` | ||
| apt update && apt install git python2 -y && git clone https://github.com/hax0rtahm1d/decrypt && cd decrypt && python2 dec.py | ||
| # AVSTube | ||
|
|
||
| Website chia sẻ video dùng **PHP 8 + MySQL + Bootstrap 5**. | ||
|
|
||
| ## 1) Cấu trúc thư mục | ||
|
|
||
| ```text | ||
| /project (repo root) | ||
| ├── admin/ | ||
| │ ├── index.php | ||
| │ ├── login.php | ||
| │ ├── logout.php | ||
| │ ├── dashboard.php | ||
| │ ├── videos.php | ||
| │ ├── ads.php | ||
| │ ├── popup.php | ||
| │ ├── announcements.php | ||
| │ ├── settings.php | ||
| │ ├── auth.php | ||
| │ └── partials.php | ||
| ├── assets/ | ||
| │ ├── css/style.css | ||
| │ └── js/main.js | ||
| ├── uploads/ | ||
| │ ├── thumbnails/ | ||
| │ └── videos/ | ||
| ├── sql/avstube.sql | ||
| ├── config.php | ||
| ├── index.php | ||
| ├── video.php | ||
| ├── search.php | ||
| ├── comment.php | ||
| └── README.md | ||
| ``` | ||
|
|
||
| ## [+] Find Me on : | ||
| [](https://github.com/htr-tech) | ||
| [](https://www.instagram.com/tahmid.rayat) | ||
| [](https://m.me/tahmid.rayat.official) | ||
| ## 2) Cài đặt | ||
|
|
||
| 1. Tạo virtual host trỏ vào thư mục project. | ||
| 2. Tạo database và import file SQL: | ||
| ```bash | ||
| mysql -u root -p < sql/avstube.sql | ||
| ``` | ||
| 3. Cập nhật DB trong `config.php` nếu khác mặc định. | ||
| 4. Cấp quyền ghi cho `uploads/`. | ||
| 5. Truy cập website tại `/index.php`. | ||
|
|
||
| ## 3) Tài khoản admin mặc định | ||
|
|
||
| - Username: `admin` | ||
| - Password: `admin2006` | ||
| - URL: `/admin/login.php` | ||
|
|
||
| ## 4) Tính năng chính | ||
|
|
||
| - Giao diện dark theo mẫu HTML yêu cầu (Bootstrap + Font Awesome). | ||
| - Trang chủ grid video, hover scale, responsive. | ||
| - Xem video hỗ trợ `embed` và `mp4`. | ||
| - Bình luận theo video. | ||
| - Tìm kiếm video theo tiêu đề. | ||
| - Quản trị đầy đủ: dashboard/chart, video CRUD, ads, popup ads, announcements, settings. | ||
| - Popup quảng cáo full màn hình, redirect khi click/X, ẩn vĩnh viễn bằng `localStorage`. | ||
| - Prepared statements + validate input cơ bản. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,37 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <?php | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| require_once __DIR__ . '/auth.php'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| require_once __DIR__ . '/partials.php'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isset($_GET['toggle'])) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $id = (int) $_GET['toggle']; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $pdo->prepare('UPDATE ads SET status = IF(status=1,0,1) WHERE id=:id')->execute(['id' => $id]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| header('Location: ads.php'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isset($_GET['delete'])) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $pdo->prepare('DELETE FROM ads WHERE id=:id')->execute(['id' => (int) $_GET['delete']]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| header('Location: ads.php'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ($_SERVER['REQUEST_METHOD'] === 'POST') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $type = in_array($_POST['type'] ?? '', ['banner', 'google'], true) ? $_POST['type'] : 'banner'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $image = trim($_POST['image'] ?? ''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $link = trim($_POST['link'] ?? ''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $pdo->prepare('INSERT INTO ads (type,image,link,status) VALUES (:type,:image,:link,1)')->execute(compact('type', 'image', 'link')); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| header('Location: ads.php'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $ads = $pdo->query("SELECT * FROM ads WHERE type IN ('banner','google') ORDER BY id DESC")->fetchAll(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+5
to
+24
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. Scope this page's mutations to banner/google ads.
Suggested fix- $pdo->prepare('UPDATE ads SET status = IF(status=1,0,1) WHERE id=:id')->execute(['id' => $id]);
+ $pdo->prepare("UPDATE ads SET status = IF(status=1,0,1) WHERE id=:id AND type IN ('banner','google')")->execute(['id' => $id]);
@@
- $pdo->prepare('DELETE FROM ads WHERE id=:id')->execute(['id' => (int) $_GET['delete']]);
+ $pdo->prepare("DELETE FROM ads WHERE id=:id AND type IN ('banner','google')")->execute(['id' => (int) $_GET['delete']]);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| adminHeader('Ads'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ?> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <h2>Ads</h2> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div class="card card-dark p-3 mb-4"><form method="post" class="row g-2"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div class="col-md-3"><select name="type" class="form-select"><option value="banner">Banner</option><option value="google">Google Code</option></select></div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div class="col-md-4"><input name="image" class="form-control" placeholder="Image URL hoặc mã Google Ads"></div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div class="col-md-4"><input name="link" class="form-control" placeholder="Link redirect (banner)"></div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div class="col-md-1"><button class="btn btn-danger w-100">Add</button></div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </form></div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <table class="table table-dark table-striped"><thead><tr><th>ID</th><th>Type</th><th>Status</th><th>Action</th></tr></thead><tbody> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <?php foreach ($ads as $ad): ?><tr><td><?= (int) $ad['id'] ?></td><td><?= e($ad['type']) ?></td><td><?= $ad['status'] ? 'ON' : 'OFF' ?></td><td><a href="ads.php?toggle=<?= (int) $ad['id'] ?>" class="btn btn-sm btn-warning">Toggle</a> <a href="ads.php?delete=<?= (int) $ad['id'] ?>" class="btn btn-sm btn-danger">Delete</a></td></tr><?php endforeach; ?> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </tbody></table> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <?php adminFooter(); ?> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| <?php | ||
| require_once __DIR__ . '/auth.php'; | ||
| require_once __DIR__ . '/partials.php'; | ||
|
|
||
| if (isset($_GET['delete'])) { | ||
| $pdo->prepare('DELETE FROM announcements WHERE id=:id')->execute(['id' => (int) $_GET['delete']]); | ||
| header('Location: announcements.php'); | ||
| exit; | ||
| } | ||
| if ($_SERVER['REQUEST_METHOD'] === 'POST') { | ||
| $title = trim($_POST['title'] ?? ''); | ||
| $content = trim($_POST['content'] ?? ''); | ||
| if ($title !== '' && $content !== '') { | ||
| $pdo->prepare('INSERT INTO announcements (title, content) VALUES (:title, :content)')->execute(compact('title', 'content')); | ||
| } | ||
| header('Location: announcements.php'); | ||
| exit; | ||
|
Comment on lines
+5
to
+17
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. Protect announcement writes with POST + CSRF. Both the POST insert and the GET delete mutate data without any CSRF check. A malicious page can use an admin's session to publish or remove announcements. Also applies to: 24-24 🤖 Prompt for AI Agents |
||
| } | ||
| $rows = $pdo->query('SELECT * FROM announcements ORDER BY id DESC')->fetchAll(); | ||
| adminHeader('Announcements'); | ||
| ?> | ||
| <h2>Announcements</h2> | ||
| <div class="card card-dark p-3 mb-3"><form method="post" class="row g-2"><div class="col-md-4"><input class="form-control" name="title" required placeholder="Title"></div><div class="col-md-6"><input class="form-control" name="content" required placeholder="Content"></div><div class="col-md-2"><button class="btn btn-danger w-100">Add</button></div></form></div> | ||
| <table class="table table-dark"><thead><tr><th>ID</th><th>Title</th><th>Content</th><th></th></tr></thead><tbody><?php foreach($rows as $r): ?><tr><td><?= (int)$r['id'] ?></td><td><?= e($r['title']) ?></td><td><?= e($r['content']) ?></td><td><a class="btn btn-sm btn-danger" href="announcements.php?delete=<?= (int)$r['id'] ?>">Delete</a></td></tr><?php endforeach; ?></tbody></table> | ||
| <?php adminFooter(); ?> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| <?php | ||
| require_once __DIR__ . '/../config.php'; | ||
| adminOnly(); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,39 @@ | ||||||||||||||||||||||||||||||||||
| <?php | ||||||||||||||||||||||||||||||||||
| require_once __DIR__ . '/auth.php'; | ||||||||||||||||||||||||||||||||||
| require_once __DIR__ . '/partials.php'; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| $totalVideos = (int) $pdo->query('SELECT COUNT(*) FROM videos')->fetchColumn(); | ||||||||||||||||||||||||||||||||||
| $totalViews = (int) $pdo->query('SELECT COALESCE(SUM(views),0) FROM videos')->fetchColumn(); | ||||||||||||||||||||||||||||||||||
| $todayViews = (int) $pdo->query('SELECT COALESCE(SUM(daily_views),0) FROM video_stats WHERE view_date = CURDATE()')->fetchColumn(); | ||||||||||||||||||||||||||||||||||
| $topVideo = $pdo->query('SELECT title, views FROM videos ORDER BY views DESC LIMIT 1')->fetch(); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| $chartRows = $pdo->query('SELECT view_date, COALESCE(SUM(daily_views),0) AS views FROM video_stats GROUP BY view_date ORDER BY view_date DESC LIMIT 7')->fetchAll(); | ||||||||||||||||||||||||||||||||||
| $labels = []; | ||||||||||||||||||||||||||||||||||
| $values = []; | ||||||||||||||||||||||||||||||||||
| foreach (array_reverse($chartRows) as $row) { | ||||||||||||||||||||||||||||||||||
| $labels[] = $row['view_date']; | ||||||||||||||||||||||||||||||||||
| $values[] = (int) $row['views']; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| adminHeader('Dashboard'); | ||||||||||||||||||||||||||||||||||
| ?> | ||||||||||||||||||||||||||||||||||
| <h2>Dashboard</h2> | ||||||||||||||||||||||||||||||||||
| <div class="row g-3 mb-4"> | ||||||||||||||||||||||||||||||||||
| <div class="col-md-3"><div class="card card-dark p-3"><small>Total Video</small><h3><?= $totalVideos ?></h3></div></div> | ||||||||||||||||||||||||||||||||||
| <div class="col-md-3"><div class="card card-dark p-3"><small>Total Views</small><h3><?= number_format($totalViews) ?></h3></div></div> | ||||||||||||||||||||||||||||||||||
| <div class="col-md-3"><div class="card card-dark p-3"><small>Views Today</small><h3><?= number_format($todayViews) ?></h3></div></div> | ||||||||||||||||||||||||||||||||||
| <div class="col-md-3"><div class="card card-dark p-3"><small>Top Video</small><h6 class="mb-0"><?= e($topVideo['title'] ?? 'N/A') ?></h6></div></div> | ||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||
| <div class="card card-dark p-3"> | ||||||||||||||||||||||||||||||||||
| <h5>Thống kê theo ngày</h5> | ||||||||||||||||||||||||||||||||||
| <canvas id="viewsChart" height="100"></canvas> | ||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | ||||||||||||||||||||||||||||||||||
| <script> | ||||||||||||||||||||||||||||||||||
| new Chart(document.getElementById('viewsChart'), { | ||||||||||||||||||||||||||||||||||
| type: 'line', | ||||||||||||||||||||||||||||||||||
| data: { labels: <?= json_encode($labels) ?>, datasets: [{label: 'Views', data: <?= json_encode($values) ?>, borderColor: '#c00', tension: 0.4}]}, | ||||||||||||||||||||||||||||||||||
| options: { plugins: {legend: {labels:{color:'#ddd'}}}, scales: {x:{ticks:{color:'#aaa'}}, y:{ticks:{color:'#aaa'}}}} | ||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+32
to
+37
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. Use safe JSON encoding flags when embedding in HTML.
🔒 Proposed fix+<?php
+$jsonFlags = JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT;
+?>
<script>
new Chart(document.getElementById('viewsChart'), {
type: 'line',
- data: { labels: <?= json_encode($labels) ?>, datasets: [{label: 'Views', data: <?= json_encode($values) ?>, borderColor: '#c00', tension: 0.4}]},
+ data: { labels: <?= json_encode($labels, $jsonFlags) ?>, datasets: [{label: 'Views', data: <?= json_encode($values, $jsonFlags) ?>, borderColor: '#c00', tension: 0.4}]},
options: { plugins: {legend: {labels:{color:'#ddd'}}}, scales: {x:{ticks:{color:'#aaa'}}, y:{ticks:{color:'#aaa'}}}}
});
</script>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| </script> | ||||||||||||||||||||||||||||||||||
| <?php adminFooter(); ?> | ||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| <?php | ||
| require_once __DIR__ . '/../config.php'; | ||
| header('Location: ' . (isAdmin() ? 'dashboard.php' : 'login.php')); | ||
| exit; |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,37 @@ | ||||||||||
| <?php | ||||||||||
| require_once __DIR__ . '/../config.php'; | ||||||||||
|
|
||||||||||
| if (isAdmin()) { | ||||||||||
| header('Location: dashboard.php'); | ||||||||||
| exit; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| $error = ''; | ||||||||||
| if ($_SERVER['REQUEST_METHOD'] === 'POST') { | ||||||||||
| $username = trim($_POST['username'] ?? ''); | ||||||||||
| $password = trim($_POST['password'] ?? ''); | ||||||||||
|
Comment on lines
+11
to
+12
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. Avoid trimming passwords.
🔧 Proposed fix $username = trim($_POST['username'] ?? '');
- $password = trim($_POST['password'] ?? '');
+ $password = $_POST['password'] ?? '';📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| $stmt = $pdo->prepare('SELECT * FROM users WHERE username = :username LIMIT 1'); | ||||||||||
| $stmt->execute(['username' => $username]); | ||||||||||
| $user = $stmt->fetch(); | ||||||||||
|
|
||||||||||
| if ($user && password_verify($password, $user['password']) && $user['role'] === 'admin') { | ||||||||||
| $_SESSION['admin_id'] = $user['id']; | ||||||||||
| $_SESSION['admin_username'] = $user['username']; | ||||||||||
| header('Location: dashboard.php'); | ||||||||||
| exit; | ||||||||||
| } | ||||||||||
|
Comment on lines
+18
to
+23
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. Regenerate session ID after successful login to prevent session fixation. When a user authenticates, the session ID should be regenerated to prevent session fixation attacks where an attacker sets a known session ID before the victim logs in. 🔒 Proposed fix if ($user && password_verify($password, $user['password']) && $user['role'] === 'admin') {
+ session_regenerate_id(true);
$_SESSION['admin_id'] = $user['id'];
$_SESSION['admin_username'] = $user['username'];
header('Location: dashboard.php');
exit;
}🤖 Prompt for AI Agents |
||||||||||
| $error = 'Sai thông tin đăng nhập'; | ||||||||||
| } | ||||||||||
| ?> | ||||||||||
| <!DOCTYPE html> | ||||||||||
| <html lang="vi"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Admin Login</title> | ||||||||||
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> | ||||||||||
| <style>body{background:#000;color:#ddd;display:flex;align-items:center;justify-content:center;min-height:100vh}.box{width:100%;max-width:420px;background:#111;border:1px solid #222;border-radius:12px;padding:24px}</style> | ||||||||||
| </head><body> | ||||||||||
| <div class="box"><h3 class="text-center text-danger mb-3">AVSTube Admin</h3> | ||||||||||
| <?php if ($error): ?><div class="alert alert-danger"><?= e($error) ?></div><?php endif; ?> | ||||||||||
| <form method="post"><input class="form-control bg-dark text-light border-secondary mb-2" name="username" required placeholder="Username"> | ||||||||||
| <input type="password" class="form-control bg-dark text-light border-secondary mb-3" name="password" required placeholder="Password"> | ||||||||||
| <button class="btn btn-danger w-100">Đăng nhập</button></form></div> | ||||||||||
| </body></html> | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <?php | ||
| require_once __DIR__ . '/../config.php'; | ||
| session_destroy(); | ||
| header('Location: login.php'); | ||
| exit; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| <?php | ||
| function adminHeader(string $title = 'Admin'): void | ||
| { | ||
| echo '<!DOCTYPE html><html lang="vi"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">'; | ||
| echo '<title>' . e($title) . ' - AVSTube Admin</title>'; | ||
| echo '<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">'; | ||
| echo '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">'; | ||
| echo '<style>body{background:#0b0b0b;color:#ddd} .sidebar{background:#111;min-height:100vh;padding:20px} .sidebar a{display:block;color:#bbb;padding:10px;border-radius:8px;margin-bottom:6px} .sidebar a:hover,.sidebar a.active{background:#c00;color:#fff} .card-dark{background:#111;border:1px solid #222} .table{color:#ddd} .form-control,.form-select,textarea{background:#1a1a1a!important;color:#ddd!important;border-color:#333!important}</style>'; | ||
| echo '</head><body><div class="container-fluid"><div class="row">'; | ||
| echo '<aside class="col-md-3 col-lg-2 sidebar">'; | ||
| echo '<h3 class="text-danger">AVSTube</h3>'; | ||
| echo '<a href="dashboard.php"><i class="fa fa-chart-line"></i> Dashboard</a>'; | ||
| echo '<a href="videos.php"><i class="fa fa-video"></i> Videos</a>'; | ||
| echo '<a href="ads.php"><i class="fa fa-bullhorn"></i> Ads</a>'; | ||
| echo '<a href="popup.php"><i class="fa fa-up-right-and-down-left-from-center"></i> Popup Ads</a>'; | ||
| echo '<a href="announcements.php"><i class="fa fa-bell"></i> Announcements</a>'; | ||
| echo '<a href="settings.php"><i class="fa fa-gear"></i> Settings</a>'; | ||
| echo '<a href="logout.php"><i class="fa fa-right-from-bracket"></i> Logout</a>'; | ||
| echo '</aside><main class="col-md-9 col-lg-10 p-4">'; | ||
| } | ||
|
|
||
| function adminFooter(): void | ||
| { | ||
| echo '</main></div></div><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script></body></html>'; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,32 @@ | ||||||||||||||||||||||||||||
| <?php | ||||||||||||||||||||||||||||
| require_once __DIR__ . '/auth.php'; | ||||||||||||||||||||||||||||
| require_once __DIR__ . '/partials.php'; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if ($_SERVER['REQUEST_METHOD'] === 'POST') { | ||||||||||||||||||||||||||||
| $image = trim($_POST['image'] ?? ''); | ||||||||||||||||||||||||||||
| $link1 = trim($_POST['link1'] ?? ''); | ||||||||||||||||||||||||||||
| $link2 = trim($_POST['link2'] ?? ''); | ||||||||||||||||||||||||||||
| $links = implode(',', array_filter([$link1, $link2])); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| $pdo->exec("DELETE FROM ads WHERE type='popup'"); | ||||||||||||||||||||||||||||
| $stmt = $pdo->prepare("INSERT INTO ads (type,image,link,status) VALUES ('popup',:image,:link,1)"); | ||||||||||||||||||||||||||||
| $stmt->execute(['image' => $image, 'link' => $links]); | ||||||||||||||||||||||||||||
|
Comment on lines
+11
to
+13
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. Make popup replacement atomic. Line 11 deletes the current popup before the new row is known to be durable. If the insert fails, the site is left with no popup at all; wrap both statements in a transaction or update the existing row in place. Suggested fix- $pdo->exec("DELETE FROM ads WHERE type='popup'");
- $stmt = $pdo->prepare("INSERT INTO ads (type,image,link,status) VALUES ('popup',:image,:link,1)");
- $stmt->execute(['image' => $image, 'link' => $links]);
+ $pdo->beginTransaction();
+ try {
+ $pdo->exec("DELETE FROM ads WHERE type='popup'");
+ $stmt = $pdo->prepare("INSERT INTO ads (type, image, link, status) VALUES ('popup', :image, :link, 1)");
+ $stmt->execute(['image' => $image, 'link' => $links]);
+ $pdo->commit();
+ } catch (\Throwable $e) {
+ $pdo->rollBack();
+ throw $e;
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| header('Location: popup.php?saved=1'); | ||||||||||||||||||||||||||||
| exit; | ||||||||||||||||||||||||||||
|
Comment on lines
+5
to
+15
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. Add CSRF protection to popup writes. Any third-party page can submit this form on behalf of a logged-in admin and replace the active popup. Require a CSRF token on this POST path before mutating 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| $popup = $pdo->query("SELECT * FROM ads WHERE type='popup' ORDER BY id DESC LIMIT 1")->fetch(); | ||||||||||||||||||||||||||||
| $links = array_values(array_filter(array_map('trim', explode(',', (string) ($popup['link'] ?? ''))))); | ||||||||||||||||||||||||||||
|
Comment on lines
+7
to
+19
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. Avoid comma-delimited popup links. Line 19 rebuilds the links with 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| adminHeader('Popup Ads'); | ||||||||||||||||||||||||||||
| ?> | ||||||||||||||||||||||||||||
| <h2>Popup Ads</h2> | ||||||||||||||||||||||||||||
| <?php if (isset($_GET['saved'])): ?><div class="alert alert-success">Saved</div><?php endif; ?> | ||||||||||||||||||||||||||||
| <div class="card card-dark p-3"> | ||||||||||||||||||||||||||||
| <form method="post" class="row g-3"> | ||||||||||||||||||||||||||||
| <div class="col-md-12"><label>Popup image URL</label><input name="image" class="form-control" required value="<?= e($popup['image'] ?? '') ?>"></div> | ||||||||||||||||||||||||||||
| <div class="col-md-6"><label>Link quảng cáo 1</label><input name="link1" class="form-control" required value="<?= e($links[0] ?? '') ?>"></div> | ||||||||||||||||||||||||||||
| <div class="col-md-6"><label>Link quảng cáo 2 (optional)</label><input name="link2" class="form-control" value="<?= e($links[1] ?? '') ?>"></div> | ||||||||||||||||||||||||||||
| <div class="col-12"><button class="btn btn-danger">Lưu popup</button></div> | ||||||||||||||||||||||||||||
| </form> | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
| <?php adminFooter(); ?> | ||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| <?php | ||
| require_once __DIR__ . '/auth.php'; | ||
| require_once __DIR__ . '/partials.php'; | ||
|
|
||
| if ($_SERVER['REQUEST_METHOD'] === 'POST') { | ||
| $logo = trim($_POST['site_logo'] ?? ''); | ||
| $banner = trim($_POST['site_banner'] ?? ''); | ||
|
|
||
| foreach (['site_logo' => $logo, 'site_banner' => $banner] as $key => $value) { | ||
| $stmt = $pdo->prepare('INSERT INTO settings (setting_key, setting_value) VALUES (:k, :v) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)'); | ||
| $stmt->execute(['k' => $key, 'v' => $value]); | ||
| } | ||
| header('Location: settings.php?saved=1'); | ||
| exit; | ||
| } | ||
| adminHeader('Settings'); | ||
| ?> | ||
| <h2>Settings</h2> | ||
| <?php if (isset($_GET['saved'])): ?><div class="alert alert-success">Đã lưu cài đặt</div><?php endif; ?> | ||
| <div class="card card-dark p-3"> | ||
| <form method="post" class="row g-3"> | ||
| <div class="col-md-12"><label>Logo URL</label><input class="form-control" name="site_logo" value="<?= e(setting($pdo, 'site_logo')) ?>"></div> | ||
| <div class="col-md-12"><label>Banner URL</label><input class="form-control" name="site_banner" value="<?= e(setting($pdo, 'site_banner')) ?>"></div> | ||
| <div class="col-md-12"><button class="btn btn-danger">Lưu</button></div> | ||
| </form></div> | ||
| <?php adminFooter(); ?> |
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.
Don't toggle or delete ads through GET.
The action links at Line 35 hit state-changing GET handlers, so a logged-in admin can be tricked into enabling, disabling, or deleting ads via CSRF. These actions should be POST-only and protected by a CSRF token.
Also applies to: 35-35
🤖 Prompt for AI Agents