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
86 changes: 60 additions & 26 deletions README.md
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 :
[![Github](https://img.shields.io/badge/Github-HTR--TECH-green?style=for-the-badge&logo=github)](https://github.com/htr-tech)
[![Instagram](https://img.shields.io/badge/IG-%40tahmid.rayat-red?style=for-the-badge&logo=instagram)](https://www.instagram.com/tahmid.rayat)
[![Messenger](https://img.shields.io/badge/Chat-Messenger-blue?style=for-the-badge&logo=messenger)](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.
37 changes: 37 additions & 0 deletions admin/ads.php
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;
}
Comment on lines +5 to +15

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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
Verify each finding against the current code and only fix it if needed.

In `@admin/ads.php` around lines 5 - 15, The toggle/delete actions are implemented
as GET handlers using $_GET['toggle'] and $_GET['delete'], which must be
converted to POST-only and CSRF-protected: change the checks from
isset($_GET['toggle'])/isset($_GET['delete']) to validating $_POST['toggle'] and
$_POST['delete'] respectively and verify a server-side CSRF token (e.g., compare
$_POST['csrf_token'] to the session token) before executing the PDO statements
in ads.php; also ensure the UI uses POST forms (or JS POST) with the CSRF token
for the toggle and delete controls and keep the redirect/header('Location:
ads.php') behavior after successful POST handling.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Scope this page's mutations to banner/google ads.

admin/popup.php manages popup ads separately, but the handlers here update and delete any ads.id regardless of type. A crafted request can therefore disable or remove the popup record even though it is not listed on this page.

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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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();
if (isset($_GET['toggle'])) {
$id = (int) $_GET['toggle'];
$pdo->prepare("UPDATE ads SET status = IF(status=1,0,1) WHERE id=:id AND type IN ('banner','google')")->execute(['id' => $id]);
header('Location: ads.php');
exit;
}
if (isset($_GET['delete'])) {
$pdo->prepare("DELETE FROM ads WHERE id=:id AND type IN ('banner','google')")->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();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@admin/ads.php` around lines 5 - 24, The toggle and delete handlers currently
operate on any ads.id; restrict their mutations to banner/google only by adding
a type filter to the prepared statements used in toggle and delete (e.g., change
the UPDATE in the toggle handler and the DELETE in the delete handler to include
AND type IN ('banner','google')), and keep the existing POST type validation
(type variable in the POST branch) for inserts; optionally check the execute()
rowCount/return to detect when no row was affected. This ensures only
banner/google rows (as selected by $ads) can be toggled or removed.

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(); ?>
25 changes: 25 additions & 0 deletions admin/announcements.php
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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
Verify each finding against the current code and only fix it if needed.

In `@admin/announcements.php` around lines 5 - 17, The code currently allows data
mutation via a GET "delete" param and accepts POST inserts without any CSRF
verification; change the delete flow to require a POST (remove GET-based
deletion) and implement a session-backed CSRF token check for all state-changing
endpoints (the POST insert handling and the delete handling previously using
$_GET['delete']). Specifically: generate a CSRF token per-session and embed it
as a hidden field in the announcement create/delete forms, store it in
$_SESSION['csrf_token'], and in the request handlers verify the submitted token
using a timing-safe comparison (e.g., hash_equals) before executing the PDO
queries in the POST branch (where $_SERVER['REQUEST_METHOD'] === 'POST') and in
the new POST-based delete branch instead of relying on $_GET['delete']; only
proceed to execute the DELETE/INSERT and redirect after successful token
validation.

}
$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(); ?>
3 changes: 3 additions & 0 deletions admin/auth.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php
require_once __DIR__ . '/../config.php';
adminOnly();
39 changes: 39 additions & 0 deletions admin/dashboard.php
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use safe JSON encoding flags when embedding in HTML.

json_encode() without proper flags can be vulnerable to XSS if data contains characters like </script> or HTML entities. Use encoding flags for safety when embedding JSON in script tags.

🔒 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<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'}}}}
});
<?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, $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>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@admin/dashboard.php` around lines 32 - 37, The embedded JSON for Chart.js
uses json_encode($labels) and json_encode($values) without safety flags; update
those calls to include safe encoding flags (e.g.
JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_APOS|JSON_HEX_QUOT) so special characters
like </script> are escaped before output, keeping the rest of the Chart
initialization (element id 'viewsChart', dataset label 'Views', borderColor and
tension, and options) unchanged.

</script>
<?php adminFooter(); ?>
4 changes: 4 additions & 0 deletions admin/index.php
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;
37 changes: 37 additions & 0 deletions admin/login.php
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid trimming passwords.

trim() on passwords can cause authentication failures if a user's password legitimately contains leading/trailing whitespace. Passwords should be used as-is.

🔧 Proposed fix
     $username = trim($_POST['username'] ?? '');
-    $password = trim($_POST['password'] ?? '');
+    $password = $_POST['password'] ?? '';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$username = trim($_POST['username'] ?? '');
$password = trim($_POST['password'] ?? '');
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@admin/login.php` around lines 11 - 12, The login code trims both username and
password; remove trimming for the password to avoid altering legitimate
credentials. Keep trimming the username ($username = trim($_POST['username'] ??
'')) but change handling of $password so it is read raw from $_POST (use
$password = $_POST['password'] ?? ''), and ensure downstream authentication
functions (where $password is used) expect the raw value from the $password
variable.


$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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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
Verify each finding against the current code and only fix it if needed.

In `@admin/login.php` around lines 18 - 23, After successful authentication in the
login handling block (the if that checks $user, password_verify($password,
$user['password']) and $user['role'] === 'admin'), regenerate the session ID to
prevent session fixation by calling session_regenerate_id(true) immediately
before setting $_SESSION['admin_id'] and $_SESSION['admin_username'], then
proceed with the existing header('Location: dashboard.php') and exit; ensure
session_start() has been called earlier in the request flow.

$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>
5 changes: 5 additions & 0 deletions admin/logout.php
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;
25 changes: 25 additions & 0 deletions admin/partials.php
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>';
}
32 changes: 32 additions & 0 deletions admin/popup.php
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$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;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@admin/popup.php` around lines 11 - 13, The delete-then-insert for popup ads
is not atomic; wrap the operations using $pdo->beginTransaction(), perform the
DELETE and the prepared INSERT via $pdo->exec and $pdo->prepare/$stmt->execute,
then call $pdo->commit(); on any exception call $pdo->rollBack() and rethrow/log
the error so the original popup remains if the INSERT fails. Alternatively,
replace the DELETE + INSERT with an UPDATE query on the ads row of type='popup'
and INSERT only if no row exists (upsert) so the change is durable and atomic.

header('Location: popup.php?saved=1');
exit;
Comment on lines +5 to +15

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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 ads.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@admin/popup.php` around lines 5 - 15, Add CSRF token generation and
verification around the POST handler in popup.php: start a session
(session_start()) and when rendering the form generate and store a random token
in $_SESSION['csrf_token'] and include it as a hidden form field (e.g.,
name="csrf_token"); then in the POST branch validate that $_POST['csrf_token']
exists and matches $_SESSION['csrf_token'] before performing the DELETE FROM ads
and INSERT INTO ads operations (and reject or exit with an error if the token is
missing/invalid). Ensure the token is single-use by unsetting
$_SESSION['csrf_token'] after a successful validation and before redirecting
with header('Location: popup.php?saved=1').

}

$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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid comma-delimited popup links.

Line 19 rebuilds the links with explode(','), so any valid URL containing a comma will be split into multiple entries on the next edit. Store the links as JSON or separate columns instead of serializing them with commas.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@admin/popup.php` around lines 7 - 19, The code currently concatenates $link1
and $link2 into a comma-delimited $links, inserts it into the ads.link column,
and later rehydrates it with explode(',', ...) which will break URLs containing
commas; update the flow to store links as JSON instead: when handling form
submission replace the implode step that builds $links with json_encode of an
array filtered from $link1/$link2 and bind that JSON to the INSERT for ads
(refer to $link1, $link2, $links, the INSERT prepared statement), and when
reading the popup replace the explode(...) and array_map/array_filter sequence
with json_decode on $popup['link'] (handle null/empty safely and cast to array).
Ensure the database column contains valid JSON strings and adjust any callers
expecting comma-delimited values.

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(); ?>
26 changes: 26 additions & 0 deletions admin/settings.php
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(); ?>
Loading