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
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
.env
backend/node_modules
frontend/node_modules
dist
coverage
.DS_Store
74 changes: 55 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,64 @@
# 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.***
# NightFlix PHP Video Website

<img src="https://raw.githubusercontent.com/htr-tech/release-download/master/images/decrypter.png" alt="" border="0" />
A shared-hosting-friendly video website built with pure PHP, MySQL, HTML, CSS, and JavaScript. It is structured for cPanel deployment and does not require Node.js.

### [+] Installation
```apt update```
## Folder structure

```apt install git python2 -y```
- `public_html/` - public pages (`index.php`, `video.php`, `upload.php`, `search.php`)
- `admin/` - session-based admin dashboard and CRUD pages
- `assets/` - CSS and JavaScript assets
- `includes/` - configuration, helpers, shared header, shared footer
- `uploads/` - reserved folder for future local file uploads
- `database.sql` - MySQL schema and seed data

```git clone https://github.com/hax0rtahm1d/decrypt```
## Features

```cd decrypt```
### Public site
- Responsive dark mode homepage with YouTube-style video grid
- Search by title or tags
- Category and tag filters
- Video detail page with MP4 or iframe embed support
- View counter and related videos
- Comment system
- Upload form
- Popup ad (once per session), banner ads, and announcement area

```python2 dec.py```
### Admin panel
- Session-based login
- Dashboard with total videos, total views, and daily/monthly statistics tables
- Video CRUD
- Category CRUD
- Tag CRUD
- Ads CRUD with enable/disable
- Announcement edit/enable toggle
- Site settings for logo, primary color, and popup ads

### Or, Use Single Command
## Default admin account

```
apt update && apt install git python2 -y && git clone https://github.com/hax0rtahm1d/decrypt && cd decrypt && python2 dec.py
```
- Username: `admin`
- Password: `admin123`

## [+] 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)
## Deploy on cPanel / shared hosting

1. Create a MySQL database and user in cPanel.
2. Import `database.sql` using phpMyAdmin.
3. Upload the project files so that these folders exist in your hosting account:
- `public_html/`
- `admin/`
- `assets/`
- `includes/`
- `uploads/`
4. Edit `includes/config.php` and update:
- `DB_HOST`
- `DB_NAME`
- `DB_USER`
- `DB_PASS`
- `BASE_URL` if your site is installed in a subdirectory
5. Open `public_html/index.php` in the browser.
6. Open `admin/login.php` to access the admin dashboard.

## Notes

- This project uses PDO and PHP sessions, which are supported on standard cPanel hosting.
- The popup ad is controlled by JavaScript and `sessionStorage`.
- `uploads/` is included for future file upload storage if you later switch from URL-based media to hosted files.
19 changes: 19 additions & 0 deletions admin/ads.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php
require_once __DIR__ . '/../includes/functions.php';
require_admin();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!empty($_POST['id'])) {
$stmt = $pdo->prepare('UPDATE ads SET image=:image, link=:link, position=:position, active=:active WHERE id=:id');
$stmt->execute(['image' => trim($_POST['image']), 'link' => trim($_POST['link']), 'position' => trim($_POST['position']), 'active' => !empty($_POST['active']) ? 1 : 0, 'id' => (int) $_POST['id']]);
} else {
$stmt = $pdo->prepare('INSERT INTO ads (image, link, position, active) VALUES (:image, :link, :position, :active)');
$stmt->execute(['image' => trim($_POST['image']), 'link' => trim($_POST['link']), 'position' => trim($_POST['position']), 'active' => !empty($_POST['active']) ? 1 : 0]);
Comment on lines +4 to +10
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

Validate position, link, and image before saving.

A tampered request can still send any position and any URI scheme here. database.sql:46-53 only rejects bad positions after the DB round-trip, and the stored ad URLs are later rendered directly into public href/src attributes in includes/header.php:40-41 and includes/footer.php:2-5,12. Enforce an allow-list for position and restrict URLs to safe schemes such as http/https.

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

In `@admin/ads.php` around lines 4 - 10, Validate and sanitize inputs before
executing the INSERT/UPDATE: enforce an allow-list for position (e.g., only
accept known values used by the app) and coerce/return an error if
$_POST['position'] is not in that list; validate $_POST['link'] and
$_POST['image'] to allow only safe URL schemes (http and https) and reject or
normalize any other schemes or malformed URIs; trim and validate lengths for
image/link and convert active to 0/1 as already done; perform these checks in
the request handler that executes the prepared statements (the branch handling
$_SERVER['REQUEST_METHOD'] === 'POST' that runs the INSERT INTO ads and UPDATE
ads queries) and abort the DB call with an error response if validation fails.

}
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 +4 to +14
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 | 🔴 Critical

Make ad mutations POST-only and CSRF-protected.

The delete branch can be triggered by a simple cross-site navigation, prefetch, or crawler while an admin session is active, and the POST write path has no token check either. Use POST for deletion and validate a CSRF token for every mutation.

Also applies to: 19-19

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

In `@admin/ads.php` around lines 4 - 14, Change all ad mutations in ads.php to
require POST and a validated CSRF token: stop using the GET delete path (remove
the $_GET['delete'] branch) and accept deletions only via POST (e.g. a POST
field like delete_id). Add a CSRF token check in the top mutation block
(validate a token submitted in $_POST, e.g. compare $_POST['csrf_token'] to a
session token $_SESSION['csrf_token'] or call an existing validate_csrf
function) before executing the INSERT/UPDATE/DELETE prepared statements (the
code that uses $_SERVER['REQUEST_METHOD'] === 'POST', the UPDATE/INSERT
statements and the DELETE execution must all be gated by the same token
validation). Ensure forms generate and include the same token in their POST
payloads and reject/redirect if the token is missing or invalid.

$editing = null;
if (isset($_GET['edit'])) { $editing = $pdo->prepare('SELECT * FROM ads WHERE id=:id'); $editing->execute(['id' => (int) $_GET['edit']]); $editing = $editing->fetch(); }
$ads = get_ads(null, false);
?>
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Ads</title><link rel="stylesheet" href="../assets/css/style.css"></head><body><div class="admin-layout"><?php include __DIR__ . '/partials/sidebar.php'; ?><main class="admin-main"><section class="panel"><h1>Ads</h1><form method="post" class="admin-form"><?php if ($editing): ?><input type="hidden" name="id" value="<?php echo (int) $editing['id']; ?>"><?php endif; ?><label>Image URL<input name="image" value="<?php echo esc($editing['image'] ?? ''); ?>" required></label><label>Link<input name="link" value="<?php echo esc($editing['link'] ?? ''); ?>" required></label><label>Position<select name="position"><?php foreach (['popup','header','middle','footer'] as $position): ?><option value="<?php echo $position; ?>" <?php echo (($editing['position'] ?? '') === $position) ? 'selected' : ''; ?>><?php echo ucfirst($position); ?></option><?php endforeach; ?></select></label><label class="inline-check"><input type="checkbox" name="active" value="1" <?php echo !isset($editing['active']) || !empty($editing['active']) ? 'checked' : ''; ?>> Active</label><button class="btn-primary" type="submit"><?php echo $editing ? 'Update' : 'Create'; ?> Ad</button></form></section><section class="panel"><table class="data-table"><tr><th>Position</th><th>Active</th><th>Actions</th></tr><?php foreach ($ads as $ad): ?><tr><td><?php echo esc($ad['position']); ?></td><td><?php echo $ad['active'] ? 'Yes' : 'No'; ?></td><td><a href="?edit=<?php echo (int) $ad['id']; ?>">Edit</a> | <a href="?delete=<?php echo (int) $ad['id']; ?>">Delete</a></td></tr><?php endforeach; ?></table></section></main></div></body></html>
17 changes: 17 additions & 0 deletions admin/announcement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php
require_once __DIR__ . '/../includes/functions.php';
require_admin();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$current = $pdo->query('SELECT id FROM announcements ORDER BY id DESC LIMIT 1')->fetchColumn();
if ($current) {
$stmt = $pdo->prepare('UPDATE announcements SET title=:title, content=:content, active=:active WHERE id=:id');
$stmt->execute(['title' => trim($_POST['title']), 'content' => trim($_POST['content']), 'active' => !empty($_POST['active']) ? 1 : 0, 'id' => $current]);
} else {
$stmt = $pdo->prepare('INSERT INTO announcements (title, content, active) VALUES (:title, :content, :active)');
$stmt->execute(['title' => trim($_POST['title']), 'content' => trim($_POST['content']), 'active' => !empty($_POST['active']) ? 1 : 0]);
}
header('Location: announcement.php'); exit;
Comment on lines +4 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

Protect announcement writes with CSRF validation.

This endpoint changes admin-owned state on any authenticated POST, and the form does not submit a token. Add a per-session/per-form CSRF token and reject writes when it is missing or invalid.

Also applies to: 17-17

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

In `@admin/announcement.php` around lines 4 - 13, Add per-session CSRF protection
to the POST handling block: ensure session_start() is called earlier, generate
and store a token in $_SESSION['csrf_token'] when rendering the form, include
that token as a hidden field named e.g. csrf_token in the form, and in the POST
branch (the $_SERVER['REQUEST_METHOD'] === 'POST' block) validate that
$_POST['csrf_token'] exists and matches $_SESSION['csrf_token']; if missing or
invalid, reject the request (respond with 400/403 and exit) before performing
the SELECT/UPDATE/INSERT operations so announcement writes are blocked without a
valid token.

}
$announcement = $pdo->query('SELECT * FROM announcements ORDER BY id DESC LIMIT 1')->fetch();
?>
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Announcement</title><link rel="stylesheet" href="../assets/css/style.css"></head><body><div class="admin-layout"><?php include __DIR__ . '/partials/sidebar.php'; ?><main class="admin-main"><section class="panel"><h1>Announcement</h1><form method="post" class="admin-form"><label>Title<input name="title" value="<?php echo esc($announcement['title'] ?? ''); ?>" required></label><label>Content<textarea name="content" rows="5" required><?php echo esc($announcement['content'] ?? ''); ?></textarea></label><label class="inline-check"><input type="checkbox" name="active" value="1" <?php echo !isset($announcement['active']) || !empty($announcement['active']) ? 'checked' : ''; ?>> Active</label><button class="btn-primary" type="submit">Save Announcement</button></form></section></main></div></body></html>
55 changes: 55 additions & 0 deletions admin/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php
require_once __DIR__ . '/../includes/functions.php';
require_admin();
$stats = dashboard_stats();
$videos = fetch_videos();
$categories = get_categories();
$tags = get_tags();
$ads = get_ads(null, false);
$announcement = get_announcement();
$settings = site_settings();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Dashboard</title>
<link rel="stylesheet" href="../assets/css/style.css">
</head>
<body>
<div class="admin-layout">
<aside class="admin-sidebar panel">
<h2>Admin Panel</h2>
<p class="muted">Logged in as <?php echo esc($_SESSION['admin_username']); ?></p>
<nav class="admin-nav">
<a href="index.php">Dashboard</a>
<a href="videos.php">Videos</a>
<a href="taxonomy.php">Categories & Tags</a>
<a href="ads.php">Ads</a>
<a href="announcement.php">Announcement</a>
<a href="settings.php">Settings</a>
<a href="logout.php">Logout</a>
</nav>
</aside>
<main class="admin-main">
<section class="stats-grid">
<div class="panel stat-card"><span>Total Videos</span><strong><?php echo $stats['total_videos']; ?></strong></div>
<div class="panel stat-card"><span>Total Views</span><strong><?php echo $stats['total_views']; ?></strong></div>
</section>
<section class="panel">
<h2>Daily Views</h2>
<table class="data-table"><tr><th>Date</th><th>Views</th></tr><?php foreach ($stats['daily'] as $row): ?><tr><td><?php echo esc($row['label']); ?></td><td><?php echo (int) $row['count']; ?></td></tr><?php endforeach; ?></table>
</section>
<section class="panel">
<h2>Monthly Views</h2>
<table class="data-table"><tr><th>Month</th><th>Views</th></tr><?php foreach ($stats['monthly'] as $row): ?><tr><td><?php echo esc($row['label']); ?></td><td><?php echo (int) $row['count']; ?></td></tr><?php endforeach; ?></table>
</section>
<section class="panel">
<h2>Quick Overview</h2>
<p class="muted">Videos: <?php echo count($videos); ?> · Categories: <?php echo count($categories); ?> · Tags: <?php echo count($tags); ?> · Ads: <?php echo count($ads); ?> · Announcement: <?php echo $announcement ? 'Active' : 'None'; ?> · Primary color: <?php echo esc($settings['primary_color']); ?></p>
</section>
</main>
</div>
</body>
</html>
36 changes: 36 additions & 0 deletions admin/login.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php
require_once __DIR__ . '/../includes/functions.php';
if (!empty($_SESSION['admin_id'])) {
header('Location: index.php');
exit;
}
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (admin_login(trim($_POST['username']), $_POST['password'])) {
header('Location: index.php');
exit;
}
$error = 'Invalid login credentials.';
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Login</title>
<link rel="stylesheet" href="../assets/css/style.css">
</head>
<body>
<div class="login-shell">
<form method="post" class="panel login-card admin-form">
<h1>Admin Login</h1>
<?php if ($error): ?><p class="error"><?php echo esc($error); ?></p><?php endif; ?>
<label>Username<input type="text" name="username" required></label>
<label>Password<input type="password" name="password" required></label>
<button type="submit" class="btn-primary">Sign In</button>
<p class="muted">Default admin is created in <code>database.sql</code>.</p>
</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__ . '/../includes/config.php';
session_destroy();
header('Location: login.php');
exit;
13 changes: 13 additions & 0 deletions admin/partials/sidebar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<aside class="admin-sidebar panel">
<h2>Admin Panel</h2>
<p class="muted">Logged in as <?php echo esc($_SESSION['admin_username']); ?></p>
<nav class="admin-nav">
<a href="index.php">Dashboard</a>
<a href="videos.php">Videos</a>
<a href="taxonomy.php">Categories & Tags</a>
<a href="ads.php">Ads</a>
<a href="announcement.php">Announcement</a>
<a href="settings.php">Settings</a>
<a href="logout.php">Logout</a>
</nav>
</aside>
12 changes: 12 additions & 0 deletions admin/settings.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
require_once __DIR__ . '/../includes/functions.php';
require_admin();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$current = site_settings();
$stmt = $pdo->prepare('UPDATE settings SET site_name=:site_name, logo=:logo, primary_color=:primary_color, popup_ads_enabled=:popup_ads_enabled WHERE id=:id');
$stmt->execute(['site_name' => trim($_POST['site_name']), 'logo' => trim($_POST['logo']), 'primary_color' => trim($_POST['primary_color']), 'popup_ads_enabled' => !empty($_POST['popup_ads_enabled']) ? 1 : 0, 'id' => $current['id']]);
header('Location: settings.php'); exit;
}
$settings = site_settings();
?>
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Settings</title><link rel="stylesheet" href="../assets/css/style.css"></head><body><div class="admin-layout"><?php include __DIR__ . '/partials/sidebar.php'; ?><main class="admin-main"><section class="panel"><h1>Settings</h1><form method="post" class="admin-form"><label>Site Name<input name="site_name" value="<?php echo esc($settings['site_name']); ?>" required></label><label>Logo URL<input name="logo" value="<?php echo esc($settings['logo']); ?>"></label><label>Primary Color<input name="primary_color" value="<?php echo esc($settings['primary_color']); ?>" required></label><label class="inline-check"><input type="checkbox" name="popup_ads_enabled" value="1" <?php echo !empty($settings['popup_ads_enabled']) ? 'checked' : ''; ?>> Enable Popup Ads</label><button class="btn-primary" type="submit">Save Settings</button></form></section></main></div></body></html>
43 changes: 43 additions & 0 deletions admin/taxonomy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php
require_once __DIR__ . '/../includes/functions.php';
require_admin();
$editCategory = null;
$editTag = null;
if (isset($_GET['edit_category'])) {
$stmt = $pdo->prepare('SELECT * FROM categories WHERE id=:id');
$stmt->execute(['id' => (int) $_GET['edit_category']]);
$editCategory = $stmt->fetch();
}
if (isset($_GET['edit_tag'])) {
$stmt = $pdo->prepare('SELECT * FROM tags WHERE id=:id');
$stmt->execute(['id' => (int) $_GET['edit_tag']]);
$editTag = $stmt->fetch();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($_POST['entity'] === 'category') {
if (!empty($_POST['id'])) {
$stmt = $pdo->prepare('UPDATE categories SET name=:name, description=:description WHERE id=:id');
$stmt->execute(['name' => trim($_POST['name']), 'description' => trim($_POST['description']), 'id' => (int) $_POST['id']]);
} else {
$stmt = $pdo->prepare('INSERT INTO categories (name, description) VALUES (:name, :description)');
$stmt->execute(['name' => trim($_POST['name']), 'description' => trim($_POST['description'])]);
}
}
if ($_POST['entity'] === 'tag') {
if (!empty($_POST['id'])) {
$stmt = $pdo->prepare('UPDATE tags SET name=:name WHERE id=:id');
$stmt->execute(['name' => trim($_POST['name']), 'id' => (int) $_POST['id']]);
} else {
$stmt = $pdo->prepare('INSERT INTO tags (name) VALUES (:name)');
$stmt->execute(['name' => trim($_POST['name'])]);
}
}
header('Location: taxonomy.php');
exit;
}
Comment on lines +16 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 | 🟠 Major

Consider adding CSRF token validation for form submissions.

The POST handlers for creating/updating categories and tags lack CSRF protection. While less severe than GET-based mutations, this still allows cross-site request forgery attacks.

Consider generating a session-bound CSRF token and validating it on form submission.

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

In `@admin/taxonomy.php` around lines 16 - 37, Add session-bound CSRF token
validation to the POST handling in taxonomy.php: ensure session_start() is
called, generate and store a token in $_SESSION['csrf_token'] when rendering the
forms and include it as a hidden input named e.g. csrf_token, then at the top of
the POST branch (before any DB updates/inserts in the existing blocks handling
'category' and 'tag') verify the submitted $_POST['csrf_token'] exists and
matches $_SESSION['csrf_token'] using a timing-safe comparison (e.g.
hash_equals), and if validation fails send a 403 response/stop processing
instead of executing the prepared statements; optionally rotate the session
token after successful validation.

if (isset($_GET['delete_category'])) { $pdo->prepare('DELETE FROM categories WHERE id=:id')->execute(['id' => (int) $_GET['delete_category']]); header('Location: taxonomy.php'); exit; }
if (isset($_GET['delete_tag'])) { $pdo->prepare('DELETE FROM tags WHERE id=:id')->execute(['id' => (int) $_GET['delete_tag']]); header('Location: taxonomy.php'); exit; }
Comment on lines +38 to +39
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 | 🔴 Critical

DELETE operations via GET are unsafe and vulnerable to CSRF.

Using GET requests for destructive operations violates HTTP semantics and creates significant security risks:

  • Can be triggered by link prefetch, browser extensions, or search crawlers
  • Attackable via <img src="?delete_category=1"> in emails or external pages
  • No CSRF token validation

Convert these to POST requests with CSRF token protection.

🔒 Proposed fix: Use POST with confirmation
-if (isset($_GET['delete_category'])) { $pdo->prepare('DELETE FROM categories WHERE id=:id')->execute(['id' => (int) $_GET['delete_category']]); header('Location: taxonomy.php'); exit; }
-if (isset($_GET['delete_tag'])) { $pdo->prepare('DELETE FROM tags WHERE id=:id')->execute(['id' => (int) $_GET['delete_tag']]); header('Location: taxonomy.php'); exit; }
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_category'])) {
+    $pdo->prepare('DELETE FROM categories WHERE id=:id')->execute(['id' => (int) $_POST['delete_category']]);
+    header('Location: taxonomy.php');
+    exit;
+}
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_tag'])) {
+    $pdo->prepare('DELETE FROM tags WHERE id=:id')->execute(['id' => (int) $_POST['delete_tag']]);
+    header('Location: taxonomy.php');
+    exit;
+}

Then update the delete links in the HTML to be forms:

<form method="post" style="display:inline">
    <input type="hidden" name="delete_category" value="<?php echo (int) $category['id']; ?>">
    <button type="submit" onclick="return confirm('Delete this category?')">Delete</button>
</form>
📝 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['delete_category'])) { $pdo->prepare('DELETE FROM categories WHERE id=:id')->execute(['id' => (int) $_GET['delete_category']]); header('Location: taxonomy.php'); exit; }
if (isset($_GET['delete_tag'])) { $pdo->prepare('DELETE FROM tags WHERE id=:id')->execute(['id' => (int) $_GET['delete_tag']]); header('Location: taxonomy.php'); exit; }
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_category'])) {
$pdo->prepare('DELETE FROM categories WHERE id=:id')->execute(['id' => (int) $_POST['delete_category']]);
header('Location: taxonomy.php');
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_tag'])) {
$pdo->prepare('DELETE FROM tags WHERE id=:id')->execute(['id' => (int) $_POST['delete_tag']]);
header('Location: taxonomy.php');
exit;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@admin/taxonomy.php` around lines 38 - 39, Change the unsafe GET-based
deletions in taxonomy.php (checks for $_GET['delete_category'] and
$_GET['delete_tag']) to handle POST only: add POST handlers that verify a CSRF
token (e.g., $_POST['csrf_token'] validated via your existing
verify_csrf_token() or a new function), cast IDs to int and execute the same
prepared DELETE statements, then redirect; also update the UI delete links to
use inline POST forms with a hidden delete_category/delete_tag field, include
the CSRF token input, and a client-side confirm() on the submit button to prompt
before deletion.

$categories = get_categories();
$tags = get_tags();
?>
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Taxonomy</title><link rel="stylesheet" href="../assets/css/style.css"></head><body><div class="admin-layout"><?php include __DIR__ . '/partials/sidebar.php'; ?><main class="admin-main"><section class="panel split-panels"><div><h1>Categories</h1><form method="post" class="admin-form"><input type="hidden" name="entity" value="category"><?php if ($editCategory): ?><input type="hidden" name="id" value="<?php echo (int) $editCategory['id']; ?>"><?php endif; ?><label>Name<input name="name" value="<?php echo esc($editCategory['name'] ?? ''); ?>" required></label><label>Description<textarea name="description" rows="3"><?php echo esc($editCategory['description'] ?? ''); ?></textarea></label><button class="btn-primary" type="submit"><?php echo $editCategory ? 'Update' : 'Save'; ?> Category</button></form><table class="data-table"><tr><th>Name</th><th>Description</th><th>Action</th></tr><?php foreach ($categories as $category): ?><tr><td><?php echo esc($category['name']); ?></td><td><?php echo esc($category['description']); ?></td><td><a href="?edit_category=<?php echo (int) $category['id']; ?>">Edit</a> | <a href="?delete_category=<?php echo (int) $category['id']; ?>">Delete</a></td></tr><?php endforeach; ?></table></div><div><h1>Tags</h1><form method="post" class="admin-form"><input type="hidden" name="entity" value="tag"><?php if ($editTag): ?><input type="hidden" name="id" value="<?php echo (int) $editTag['id']; ?>"><?php endif; ?><label>Name<input name="name" value="<?php echo esc($editTag['name'] ?? ''); ?>" required></label><button class="btn-primary" type="submit"><?php echo $editTag ? 'Update' : 'Save'; ?> Tag</button></form><table class="data-table"><tr><th>Name</th><th>Action</th></tr><?php foreach ($tags as $tag): ?><tr><td><?php echo esc($tag['name']); ?></td><td><a href="?edit_tag=<?php echo (int) $tag['id']; ?>">Edit</a> | <a href="?delete_tag=<?php echo (int) $tag['id']; ?>">Delete</a></td></tr><?php endforeach; ?></table></div></section></main></div></body></html>
Loading