Add full AVSTube PHP/MySQL video sharing site with admin, popup ads, and SQL schema#46
Add full AVSTube PHP/MySQL video sharing site with admin, popup ads, and SQL schema#46
Conversation
📝 WalkthroughWalkthroughThis PR introduces AVSTube, a complete Vietnamese-language video platform with a public-facing video gallery and admin control panel. It includes a MySQL database schema with tables for videos, comments, users, ads, announcements, and analytics; admin pages for content and settings management; and public pages for browsing, searching, and commenting on videos. Changes
Estimated code review effort🎯 5 (Critical) | ⏱️ ~95 minutes
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2d2fe40d48
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| $stats = $pdo->prepare('INSERT INTO video_stats (video_id, view_date, daily_views) VALUES (:video_id, CURDATE(), 1) ON DUPLICATE KEY UPDATE daily_views = daily_views + 1'); | ||
| $stats->execute(['video_id' => $id]); |
There was a problem hiding this comment.
Validate video before writing to
video_stats
This inserts a stats row before confirming the video exists, so requests like video.php?id=<nonexistent> hit the video_stats.video_id foreign key and throw an uncaught PDOException instead of following the later redirect path. In production this turns a normal invalid-ID request into a 500 error; fetch/validate the video first (or gate the insert on a successful lookup) to avoid hard failures.
Useful? React with 👍 / 👎.
| <?php endif; ?> | ||
| </div> | ||
| <h2><?= e($video['title']) ?></h2> | ||
| <p class="views mb-0"><i class="fa fa-eye"></i> <?= number_format((int) $video['views'] + 1) ?> lượt xem</p> |
There was a problem hiding this comment.
Show persisted view count without adding another increment
The page increments views before loading the record, so $video['views'] already reflects the current view. Adding + 1 here makes the displayed counter consistently one higher than the stored value and dashboard totals, which will confuse users and admins when comparing counts.
Useful? React with 👍 / 👎.
| if (isset($_GET['delete'])) { | ||
| $id = (int) $_GET['delete']; | ||
| $stmt = $pdo->prepare('DELETE FROM videos WHERE id = :id'); | ||
| $stmt->execute(['id' => $id]); |
There was a problem hiding this comment.
Require CSRF-safe method for video deletion
Deleting videos is triggered directly from a GET parameter with no CSRF token or origin check, so any site can force an authenticated admin’s browser to request videos.php?delete=<id> and delete content unintentionally. Destructive actions should require a POST (or DELETE) plus CSRF validation to prevent cross-site request forgery.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 19
🧹 Nitpick comments (6)
sql/avstube.sql (1)
4-14: Consider adding an index oncreated_atfor query performance.The homepage and search queries order by
created_at DESC. Without an index, this requires a full table scan as the videos table grows.📈 Suggested index
views INT NOT NULL DEFAULT 0, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX idx_created_at (created_at) );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sql/avstube.sql` around lines 4 - 14, The videos table lacks an index on the created_at column which causes ORDER BY created_at DESC queries (homepage/search) to do full table scans; add a dedicated index on videos.created_at (e.g., create an index named like idx_videos_created_at) via your schema migration or ALTER TABLE so ordering and date-range filters use the index; if you also filter by other columns in those queries consider a composite index including those filter columns plus created_at for best performance.README.md (1)
47-51: Add security warning for default credentials.While documenting default credentials is helpful for initial setup, consider adding a prominent warning to change them before deploying to production.
📝 Suggested addition
## 3) Tài khoản admin mặc định +> ⚠️ **Bảo mật**: Đổi mật khẩu admin ngay sau khi cài đặt! + - Username: `admin` - Password: `admin2006` - URL: `/admin/login.php`🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` around lines 47 - 51, Update the "3) Tài khoản admin mặc định" README section to include a clear security warning that the default Username `admin` and Password `admin2006` must be changed before any production deployment; explicitly instruct users to log in to `/admin/login.php` and immediately change the admin password to a strong, unique password (or create a new admin user and disable/remove the default), and optionally note best practices like using a password manager, enforcing strong password policies, and rotating credentials regularly.admin/logout.php (1)
1-5: Incomplete session cleanup on logout.
session_destroy()alone doesn't unset the$_SESSIONsuperglobal or clear the session cookie. For a more secure logout, clear session data and optionally invalidate the cookie.🔒 Proposed fix for complete session cleanup
<?php require_once __DIR__ . '/../config.php'; +$_SESSION = []; +if (ini_get('session.use_cookies')) { + $p = session_get_cookie_params(); + setcookie(session_name(), '', time() - 42000, $p['path'], $p['domain'], $p['secure'], $p['httponly']); +} session_destroy(); header('Location: login.php'); exit;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@admin/logout.php` around lines 1 - 5, The logout flow currently only calls session_destroy(); improve it to fully clear session state by ensuring the session is started (call session_start() if needed), unset all session data (set $_SESSION = array()), remove the session cookie using session_name() and session_get_cookie_params() with setcookie(..., time()-3600, ...) to match path/domain/secure/httponly, then call session_destroy() and proceed with the existing header('Location: login.php') and exit; update the logout.php logic to perform these steps around the existing session_destroy/header/exit sequence.admin/settings.php (2)
5-15: Missing URL validation for logo and banner inputs.The settings accept arbitrary strings for URLs. Consider validating that inputs are valid URLs or empty strings to prevent storing malformed data.
🛡️ Optional validation
if ($_SERVER['REQUEST_METHOD'] === 'POST') { $logo = trim($_POST['site_logo'] ?? ''); $banner = trim($_POST['site_banner'] ?? ''); + + // Optional: validate URLs if not empty + if ($logo !== '' && !filter_var($logo, FILTER_VALIDATE_URL)) { + // Handle invalid URL + } + if ($banner !== '' && !filter_var($banner, FILTER_VALIDATE_URL)) { + // Handle invalid URL + } foreach (['site_logo' => $logo, 'site_banner' => $banner] as $key => $value) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@admin/settings.php` around lines 5 - 15, The POST handling in settings.php currently stores whatever is submitted into $logo and $banner without validation; update the logic before the foreach loop to validate that $logo and $banner are either empty or valid URLs using filter_var(..., FILTER_VALIDATE_URL) (or equivalent) and only persist those sanitized values, otherwise set them to an empty string or return an error response; keep the existing prepared statement and loop (the $logo/$banner variables and the foreach mapping) but replace/overwrite invalid inputs with a safe value so malformed strings are never written to the settings table.
9-12: Update deprecated MySQL syntax in ON DUPLICATE KEY UPDATE.
VALUES(column)inON DUPLICATE KEY UPDATEis deprecated as of MySQL 8.0.20. Use row aliases instead.♻️ Modernize with alias syntax
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 = $pdo->prepare('INSERT INTO settings (setting_key, setting_value) VALUES (:k, :v) AS new ON DUPLICATE KEY UPDATE setting_value = new.setting_value'); $stmt->execute(['k' => $key, 'v' => $value]); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@admin/settings.php` around lines 9 - 12, The INSERT uses the deprecated VALUES(column) form in the ON DUPLICATE KEY UPDATE clause; change the query in admin/settings.php (the $stmt prepared in the foreach loop) to use an INSERT alias and reference that alias instead — e.g. add "AS new" after the VALUES(...) clause and replace "setting_value = VALUES(setting_value)" with "setting_value = new.setting_value" so the prepared statement uses the modern alias syntax.admin/partials.php (1)
12-18: Consider adding active state detection for sidebar links.All sidebar links have the same styling. Adding active state detection would improve navigation UX.
♻️ Example approach using basename comparison
$current = basename($_SERVER['PHP_SELF']); echo '<a href="dashboard.php"' . ($current === 'dashboard.php' ? ' class="active"' : '') . '>...';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@admin/partials.php` around lines 12 - 18, Sidebar anchors in admin/partials.php lack an active state; compute the current page (e.g., $current = basename($_SERVER['PHP_SELF'])) and append a conditional class attribute to each echoed anchor so the link whose href matches $current gets class="active" (apply this logic to the existing anchors for "dashboard.php", "videos.php", "ads.php", "popup.php", "announcements.php", "settings.php", "logout.php"); ensure you only alter the echo strings to include the ternary/conditional that injects ' class="active"' when matched so styling picks up the active state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@admin/ads.php`:
- Around line 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.
- Around line 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.
In `@admin/announcements.php`:
- Around line 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.
In `@admin/dashboard.php`:
- Around line 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.
In `@admin/login.php`:
- Around line 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.
- Around line 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.
In `@admin/popup.php`:
- Around line 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.
- Around line 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').
- Around line 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.
In `@admin/videos.php`:
- Around line 5-10: The delete handler currently uses $_GET['delete'] for state
changes and create/update POSTs lack CSRF checks; change the delete action to
require a POST with a CSRF token and add CSRF validation to the existing
create/update POST handling (the POST-processing block that handles
insert/update around lines 13-65 and the delete handling that checks
$_GET['delete']). Implement generation of a per-session token stored in
$_SESSION['csrf_token'], output it as a hidden input named e.g. csrf_token in
all mutation forms, and verify it on each POST using a constant-time comparison
(hash_equals) before executing SQL; if missing/invalid, abort the action and
respond with an error. Ensure the deletion code stops reading $_GET['delete']
and instead reads the id from $_POST['delete'] (or a named delete_id) and that
redirects/exits remain after successful operations.
- Around line 15-20: The code currently allows empty video_url/thumbnail to be
inserted; add server-side validation in the create/update handlers to reject
incomplete records based on $type: if $type === 'embed' require non-empty
$videoUrl; if $type === 'mp4' require non-empty $videoUrl and $thumbnail; if
validation fails, stop the insert/update, set an error message and return to the
form (do not write to the videos table). Apply these checks around the blocks
that use $title/$type/$videoUrl/$thumbnail (the create and edit code paths) so
both insert and update paths enforce the same rules.
- Around line 22-39: The current upload logic uses move_uploaded_file() but
unconditionally sets $videoUrl and $thumbnail even if the move fails, which can
create DB records pointing to non-existent files; update the blocks handling
$_FILES['mp4_file'] and $_FILES['thumbnail_file'] to check the boolean return of
move_uploaded_file() before assigning $videoUrl or $thumbnail (e.g., only set
$videoUrl = 'uploads/videos/'.$name after
move_uploaded_file($_FILES['mp4_file']['tmp_name'], $target) returns true, and
similarly for $thumbnail), and on failure handle the error (log, set an error
flag, or stop processing) so you never persist a path unless the file was
successfully moved.
In `@assets/js/main.js`:
- Around line 22-28: The popup's click listener (popup.addEventListener('click',
redirect)) causes clicks on the close button to bubble and trigger redirect; add
a dedicated handler for the close button (select the element with class
'popup-close') that calls event.stopPropagation(), marks the popup as dismissed
(e.g., set localStorage 'popupViewed' as appropriate) and hides/removes the
popup, and keep the existing redirect() handler only for clicks that are not
from the close control (or alternatively guard inside the popup click handler
with if (event.target.closest('.popup-close')) return;). Update references to
redirect, popup, and the '.popup-close' element accordingly so the X button
dismisses without navigating.
In `@comment.php`:
- Around line 8-18: Add CSRF token validation around the comment insertion:
require a session-based token (e.g. $_SESSION['csrf_token']) and check the
submitted token (e.g. $_POST['csrf_token']) before running the existing
insertion logic that uses $videoId, $username, $comment; if the token is missing
or invalid, abort and do not execute $stmt->execute(...). Also ensure the token
is generated and injected as a hidden field in the comment form (video.php) so
the submitted form includes the expected token.
In `@config.php`:
- Around line 21-23: The catch block that handles PDOException ($e) currently
dies with the exception message (die('Database connection failed: ' .
htmlspecialchars($e->getMessage()))), which can leak sensitive DB details;
change it to log the full exception internally (use error_log or your
application logger with $e->getMessage() and stack trace) and replace the die()
output with a generic user-facing message like "Database connection error" (no
exception details), then exit with a non-zero status; ensure you still
htmlspecialchars any user-visible text and keep the exception logging call
adjacent to the catch handling for PDOException.
- Around line 4-8: The config file currently hardcodes DB_HOST, DB_NAME,
DB_USER, DB_PASS and BASE_URL; replace these constant definitions to read from
environment/config instead (use getenv or $_ENV for DB_HOST, DB_NAME, DB_USER,
DB_PASS, BASE_URL), provide sensible defaults only for non-sensitive values, and
validate required DB credentials at startup (throw/log a clear error if
DB_USER/DB_PASS/DB_NAME are missing) so secrets are not committed—also document
that secrets should be set via environment variables or a non-committed local
config file.
In `@index.php`:
- Around line 95-97: The foreach rendering of $googleCodes in index.php emits
$code['image'] unescaped which creates XSS risk; either sanitize/validate the
image HTML before output or explicitly-document/limit this as intentional raw
output. Update the loop that outputs $code['image'] to (a) sanitize/strip
disallowed tags or run it through a safe sanitizer function (e.g.,
sanitizeAdHtml or a whitelist-based purifier) and/or (b) add a clear inline
comment above the output explaining why raw HTML is used and that input is
restricted to trusted admins, and consider adding CSP headers elsewhere;
reference the foreach over $googleCodes and the $code['image'] key when making
the change.
In `@video.php`:
- Around line 62-67: The comment form in video.php must include a CSRF token and
server-side validation to match comment.php; generate a session token if missing
(use $_SESSION['csrf_token'] = $_SESSION['csrf_token'] ??
bin2hex(random_bytes(32))) and add a hidden input named csrf_token inside the
existing form (near the other hidden video_id input), then ensure comment.php
validates the submitted csrf_token against $_SESSION['csrf_token' ] (and rejects
the request if missing/invalid); escape the token output with your existing e()
helper when rendering the hidden input.
- Around line 9-22: The code updates views and inserts into video_stats before
checking the video exists, causing a foreign key PDOException for invalid $id;
move the SELECT query (the $stmt prepared with 'SELECT * FROM videos WHERE id =
:id') and the fetch($stmt->fetch()) ahead of the $update and $stats statements,
check if $video is falsy and redirect/exit if so, and only then execute the
$update (views increment) and $stats (INSERT ... ON DUPLICATE KEY UPDATE) using
the same $id; ensure to reuse or re-prepare the $update and $stats statements
after the existence check if needed.
---
Nitpick comments:
In `@admin/logout.php`:
- Around line 1-5: The logout flow currently only calls session_destroy();
improve it to fully clear session state by ensuring the session is started (call
session_start() if needed), unset all session data (set $_SESSION = array()),
remove the session cookie using session_name() and session_get_cookie_params()
with setcookie(..., time()-3600, ...) to match path/domain/secure/httponly, then
call session_destroy() and proceed with the existing header('Location:
login.php') and exit; update the logout.php logic to perform these steps around
the existing session_destroy/header/exit sequence.
In `@admin/partials.php`:
- Around line 12-18: Sidebar anchors in admin/partials.php lack an active state;
compute the current page (e.g., $current = basename($_SERVER['PHP_SELF'])) and
append a conditional class attribute to each echoed anchor so the link whose
href matches $current gets class="active" (apply this logic to the existing
anchors for "dashboard.php", "videos.php", "ads.php", "popup.php",
"announcements.php", "settings.php", "logout.php"); ensure you only alter the
echo strings to include the ternary/conditional that injects ' class="active"'
when matched so styling picks up the active state.
In `@admin/settings.php`:
- Around line 5-15: The POST handling in settings.php currently stores whatever
is submitted into $logo and $banner without validation; update the logic before
the foreach loop to validate that $logo and $banner are either empty or valid
URLs using filter_var(..., FILTER_VALIDATE_URL) (or equivalent) and only persist
those sanitized values, otherwise set them to an empty string or return an error
response; keep the existing prepared statement and loop (the $logo/$banner
variables and the foreach mapping) but replace/overwrite invalid inputs with a
safe value so malformed strings are never written to the settings table.
- Around line 9-12: The INSERT uses the deprecated VALUES(column) form in the ON
DUPLICATE KEY UPDATE clause; change the query in admin/settings.php (the $stmt
prepared in the foreach loop) to use an INSERT alias and reference that alias
instead — e.g. add "AS new" after the VALUES(...) clause and replace
"setting_value = VALUES(setting_value)" with "setting_value = new.setting_value"
so the prepared statement uses the modern alias syntax.
In `@README.md`:
- Around line 47-51: Update the "3) Tài khoản admin mặc định" README section to
include a clear security warning that the default Username `admin` and Password
`admin2006` must be changed before any production deployment; explicitly
instruct users to log in to `/admin/login.php` and immediately change the admin
password to a strong, unique password (or create a new admin user and
disable/remove the default), and optionally note best practices like using a
password manager, enforcing strong password policies, and rotating credentials
regularly.
In `@sql/avstube.sql`:
- Around line 4-14: The videos table lacks an index on the created_at column
which causes ORDER BY created_at DESC queries (homepage/search) to do full table
scans; add a dedicated index on videos.created_at (e.g., create an index named
like idx_videos_created_at) via your schema migration or ALTER TABLE so ordering
and date-range filters use the index; if you also filter by other columns in
those queries consider a composite index including those filter columns plus
created_at for best performance.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0471d1dd-7ac6-425f-ab9c-0679b7fed89a
📒 Files selected for processing (20)
README.mdadmin/ads.phpadmin/announcements.phpadmin/auth.phpadmin/dashboard.phpadmin/index.phpadmin/login.phpadmin/logout.phpadmin/partials.phpadmin/popup.phpadmin/settings.phpadmin/videos.phpassets/css/style.cssassets/js/main.jscomment.phpconfig.phpindex.phpsearch.phpsql/avstube.sqlvideo.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; | ||
| } |
There was a problem hiding this comment.
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 (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(); |
There was a problem hiding this comment.
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.
| 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.
| 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; |
There was a problem hiding this comment.
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.
| <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'}}}} | ||
| }); |
There was a problem hiding this comment.
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.
| <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.
| $username = trim($_POST['username'] ?? ''); | ||
| $password = trim($_POST['password'] ?? ''); |
There was a problem hiding this comment.
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.
| $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.
| define('DB_HOST', 'localhost'); | ||
| define('DB_NAME', 'avstube'); | ||
| define('DB_USER', 'root'); | ||
| define('DB_PASS', ''); | ||
| define('BASE_URL', ''); |
There was a problem hiding this comment.
Hardcoded database credentials pose a security risk for production.
Storing credentials directly in source code exposes them in version control and makes deployment configuration inflexible. Consider using environment variables or a separate non-committed config file.
♻️ Suggested approach using environment variables
-define('DB_HOST', 'localhost');
-define('DB_NAME', 'avstube');
-define('DB_USER', 'root');
-define('DB_PASS', '');
-define('BASE_URL', '');
+define('DB_HOST', getenv('DB_HOST') ?: 'localhost');
+define('DB_NAME', getenv('DB_NAME') ?: 'avstube');
+define('DB_USER', getenv('DB_USER') ?: 'root');
+define('DB_PASS', getenv('DB_PASS') ?: '');
+define('BASE_URL', getenv('BASE_URL') ?: '');📝 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.
| define('DB_HOST', 'localhost'); | |
| define('DB_NAME', 'avstube'); | |
| define('DB_USER', 'root'); | |
| define('DB_PASS', ''); | |
| define('BASE_URL', ''); | |
| define('DB_HOST', getenv('DB_HOST') ?: 'localhost'); | |
| define('DB_NAME', getenv('DB_NAME') ?: 'avstube'); | |
| define('DB_USER', getenv('DB_USER') ?: 'root'); | |
| define('DB_PASS', getenv('DB_PASS') ?: ''); | |
| define('BASE_URL', getenv('BASE_URL') ?: ''); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@config.php` around lines 4 - 8, The config file currently hardcodes DB_HOST,
DB_NAME, DB_USER, DB_PASS and BASE_URL; replace these constant definitions to
read from environment/config instead (use getenv or $_ENV for DB_HOST, DB_NAME,
DB_USER, DB_PASS, BASE_URL), provide sensible defaults only for non-sensitive
values, and validate required DB credentials at startup (throw/log a clear error
if DB_USER/DB_PASS/DB_NAME are missing) so secrets are not committed—also
document that secrets should be set via environment variables or a non-committed
local config file.
| } catch (PDOException $e) { | ||
| die('Database connection failed: ' . htmlspecialchars($e->getMessage())); | ||
| } |
There was a problem hiding this comment.
Exposing database exception details can leak sensitive information.
The error message may reveal database host, schema names, or driver details to end users. Log the full error internally and show a generic message to users.
🛡️ Proposed fix
} catch (PDOException $e) {
- die('Database connection failed: ' . htmlspecialchars($e->getMessage()));
+ error_log('Database connection failed: ' . $e->getMessage());
+ die('Database connection failed. Please try again later.');
}📝 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.
| } catch (PDOException $e) { | |
| die('Database connection failed: ' . htmlspecialchars($e->getMessage())); | |
| } | |
| } catch (PDOException $e) { | |
| error_log('Database connection failed: ' . $e->getMessage()); | |
| die('Database connection failed. Please try again later.'); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@config.php` around lines 21 - 23, The catch block that handles PDOException
($e) currently dies with the exception message (die('Database connection failed:
' . htmlspecialchars($e->getMessage()))), which can leak sensitive DB details;
change it to log the full exception internally (use error_log or your
application logger with $e->getMessage() and stack trace) and replace the die()
output with a generic user-facing message like "Database connection error" (no
exception details), then exit with a non-zero status; ensure you still
htmlspecialchars any user-visible text and keep the exception logging call
adjacent to the catch handling for PDOException.
| <?php foreach ($googleCodes as $code): ?> | ||
| <div class="panel-dark p-3 mb-4"><?= $code['image'] ?></div> | ||
| <?php endforeach; ?> |
There was a problem hiding this comment.
Unescaped output creates XSS risk if admin account is compromised.
The $code['image'] field is rendered without escaping, allowing arbitrary HTML/JS execution. While this may be intentional for embedding Google ad scripts, a compromised admin account could inject malicious code affecting all visitors.
Consider adding a sanitization layer or Content Security Policy headers to mitigate the risk. At minimum, add a comment documenting this intentional raw output:
<?php foreach ($googleCodes as $code): ?>
- <div class="panel-dark p-3 mb-4"><?= $code['image'] ?></div>
+ <?php // Raw HTML output for Google ad code - admin-only input ?>
+ <div class="panel-dark p-3 mb-4"><?= $code['image'] ?></div>
<?php endforeach; ?>📝 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.
| <?php foreach ($googleCodes as $code): ?> | |
| <div class="panel-dark p-3 mb-4"><?= $code['image'] ?></div> | |
| <?php endforeach; ?> | |
| <?php foreach ($googleCodes as $code): ?> | |
| <?php // Raw HTML output for Google ad code - admin-only input ?> | |
| <div class="panel-dark p-3 mb-4"><?= $code['image'] ?></div> | |
| <?php endforeach; ?> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@index.php` around lines 95 - 97, The foreach rendering of $googleCodes in
index.php emits $code['image'] unescaped which creates XSS risk; either
sanitize/validate the image HTML before output or explicitly-document/limit this
as intentional raw output. Update the loop that outputs $code['image'] to (a)
sanitize/strip disallowed tags or run it through a safe sanitizer function
(e.g., sanitizeAdHtml or a whitelist-based purifier) and/or (b) add a clear
inline comment above the output explaining why raw HTML is used and that input
is restricted to trusted admins, and consider adding CSP headers elsewhere;
reference the foreach over $googleCodes and the $code['image'] key when making
the change.
| $update = $pdo->prepare('UPDATE videos SET views = views + 1 WHERE id = :id'); | ||
| $update->execute(['id' => $id]); | ||
|
|
||
| $stats = $pdo->prepare('INSERT INTO video_stats (video_id, view_date, daily_views) VALUES (:video_id, CURDATE(), 1) ON DUPLICATE KEY UPDATE daily_views = daily_views + 1'); | ||
| $stats->execute(['video_id' => $id]); | ||
|
|
||
| $stmt = $pdo->prepare('SELECT * FROM videos WHERE id = :id LIMIT 1'); | ||
| $stmt->execute(['id' => $id]); | ||
| $video = $stmt->fetch(); | ||
|
|
||
| if (!$video) { | ||
| header('Location: index.php'); | ||
| exit; | ||
| } |
There was a problem hiding this comment.
View count update before existence check causes exception for invalid IDs.
If a non-existent video ID is provided, the UPDATE silently affects 0 rows, but the INSERT INTO video_stats will throw a PDOException due to the foreign key constraint violation. This crashes the page instead of redirecting gracefully.
🐛 Proposed fix: fetch video first, then update stats
if ($id <= 0) {
header('Location: index.php');
exit;
}
-$update = $pdo->prepare('UPDATE videos SET views = views + 1 WHERE id = :id');
-$update->execute(['id' => $id]);
-
-$stats = $pdo->prepare('INSERT INTO video_stats (video_id, view_date, daily_views) VALUES (:video_id, CURDATE(), 1) ON DUPLICATE KEY UPDATE daily_views = daily_views + 1');
-$stats->execute(['video_id' => $id]);
-
$stmt = $pdo->prepare('SELECT * FROM videos WHERE id = :id LIMIT 1');
$stmt->execute(['id' => $id]);
$video = $stmt->fetch();
if (!$video) {
header('Location: index.php');
exit;
}
+
+$update = $pdo->prepare('UPDATE videos SET views = views + 1 WHERE id = :id');
+$update->execute(['id' => $id]);
+
+$stats = $pdo->prepare('INSERT INTO video_stats (video_id, view_date, daily_views) VALUES (:video_id, CURDATE(), 1) ON DUPLICATE KEY UPDATE daily_views = daily_views + 1');
+$stats->execute(['video_id' => $id]);📝 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.
| $update = $pdo->prepare('UPDATE videos SET views = views + 1 WHERE id = :id'); | |
| $update->execute(['id' => $id]); | |
| $stats = $pdo->prepare('INSERT INTO video_stats (video_id, view_date, daily_views) VALUES (:video_id, CURDATE(), 1) ON DUPLICATE KEY UPDATE daily_views = daily_views + 1'); | |
| $stats->execute(['video_id' => $id]); | |
| $stmt = $pdo->prepare('SELECT * FROM videos WHERE id = :id LIMIT 1'); | |
| $stmt->execute(['id' => $id]); | |
| $video = $stmt->fetch(); | |
| if (!$video) { | |
| header('Location: index.php'); | |
| exit; | |
| } | |
| if ($id <= 0) { | |
| header('Location: index.php'); | |
| exit; | |
| } | |
| $stmt = $pdo->prepare('SELECT * FROM videos WHERE id = :id LIMIT 1'); | |
| $stmt->execute(['id' => $id]); | |
| $video = $stmt->fetch(); | |
| if (!$video) { | |
| header('Location: index.php'); | |
| exit; | |
| } | |
| $update = $pdo->prepare('UPDATE videos SET views = views + 1 WHERE id = :id'); | |
| $update->execute(['id' => $id]); | |
| $stats = $pdo->prepare('INSERT INTO video_stats (video_id, view_date, daily_views) VALUES (:video_id, CURDATE(), 1) ON DUPLICATE KEY UPDATE daily_views = daily_views + 1'); | |
| $stats->execute(['video_id' => $id]); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@video.php` around lines 9 - 22, The code updates views and inserts into
video_stats before checking the video exists, causing a foreign key PDOException
for invalid $id; move the SELECT query (the $stmt prepared with 'SELECT * FROM
videos WHERE id = :id') and the fetch($stmt->fetch()) ahead of the $update and
$stats statements, check if $video is falsy and redirect/exit if so, and only
then execute the $update (views increment) and $stats (INSERT ... ON DUPLICATE
KEY UPDATE) using the same $id; ensure to reuse or re-prepare the $update and
$stats statements after the existence check if needed.
| <form method="post" action="comment.php" class="mb-3"> | ||
| <input type="hidden" name="video_id" value="<?= (int) $video['id'] ?>"> | ||
| <input class="form-control bg-dark text-light border-secondary mb-2" name="username" placeholder="Tên của bạn" required maxlength="50"> | ||
| <textarea class="form-control bg-dark text-light border-secondary mb-2" name="comment" rows="3" placeholder="Nội dung bình luận" required maxlength="500"></textarea> | ||
| <button class="btn btn-red w-100" type="submit">Gửi bình luận</button> | ||
| </form> |
There was a problem hiding this comment.
Comment form lacks CSRF protection.
Same issue as in comment.php — the form should include a CSRF token to prevent cross-site request forgery.
Add a hidden CSRF token field:
<?php $_SESSION['csrf_token'] = $_SESSION['csrf_token'] ?? bin2hex(random_bytes(32)); ?>
<input type="hidden" name="csrf_token" value="<?= e($_SESSION['csrf_token']) ?>">🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@video.php` around lines 62 - 67, The comment form in video.php must include a
CSRF token and server-side validation to match comment.php; generate a session
token if missing (use $_SESSION['csrf_token'] = $_SESSION['csrf_token'] ??
bin2hex(random_bytes(32))) and add a hidden input named csrf_token inside the
existing form (near the other hidden video_id input), then ensure comment.php
validates the submitted csrf_token against $_SESSION['csrf_token' ] (and rejects
the request if missing/invalid); escape the token output with your existing e()
helper when rendering the hidden input.
Motivation
#111header,#dddtext, red brand#c00, thumbnail/duration, hover scale, responsive grid).localStorage.Description
index.php,video.php,search.php,comment.php,config.php,assets/css/style.css,assets/js/main.js,uploads/,admin/*, andsql/avstube.sqlwith initial seed data and admin account.embedandmp4, comments (stored incommentstable), search by title viasearch.php, and banner/Google ad zones.adsrecord, displays full-screen image, redirects on click or close, and setslocalStorage.popupViewed = trueso the popup does not reappear; also added daily view stats (video_stats) updated on each video view.Testing
rg --files -g '*.php' | xargs -n1 php -land all PHP files reportedNo syntax errors detected.php -S 0.0.0.0:8080to smoke-test the app startup successfully.SIGSEGVin this environment so a screenshot could not be captured.Codex Task
Summary by CodeRabbit
New Features
Documentation
Style