Skip to content

Add AVSTube Video Manager plugin with upload, player, and REST API#47

Open
huzely wants to merge 1 commit intomasterfrom
codex/create-avstube-video-manager-plugin
Open

Add AVSTube Video Manager plugin with upload, player, and REST API#47
huzely wants to merge 1 commit intomasterfrom
codex/create-avstube-video-manager-plugin

Conversation

@huzely
Copy link
Copy Markdown
Owner

@huzely huzely commented Mar 14, 2026

Motivation

  • Provide AVSTube theme integration for managing video uploads, streaming, metadata, frontend submissions, and a playback REST API.
  • Support secure user uploads that land in a pending review workflow and allow admins to moderate, approve, and view statistics.
  • Expose a simple REST surface and lightweight frontend player so theme templates and clients can fetch and play videos with view/like tracking.

Description

  • Added a complete plugin scaffold at wp-content/plugins/avstube-video-manager/ including avstube-video-manager.php, includes/ classes (class-video-post-type.php, class-video-upload.php, class-video-api.php, class-video-player.php) and assets/ CSS/JS for upload and player features.
  • Registered a video custom post type with supports for title, editor, thumbnail, comments, author, custom fields and meta fields video_mp4_url, video_embed_url, video_duration, video_views, and video_likes, plus admin meta boxes and a dashboard menu for moderation.
  • Implemented a frontend upload shortcode ([avstube_video_upload_form]) that accepts title, description, category, tags, thumbnail, MP4 upload or external MP4/embed URL and saves submissions as pending; files are validated (MP4-only) and limited to 100MB.
  • Implemented a player shortcode ([avstube_video_player]), a video grid ([avstube_video_grid]) and a homepage shortcode ([avstube_homepage]) that render an HTML5 player (or iframe for embeds), and added JS to track views and handle AJAX likes via REST endpoints.
  • Added REST API endpoints GET /wp-json/avstube/v1/video/{id}, POST /wp-json/avstube/v1/video/like/{id}, and POST /wp-json/avstube/v1/video/view/{id} that return/modify the requested fields and use transient caching for performance.
  • Enforced security and hygiene with nonce verification, capability checks, input sanitization, filetype checks, upload size limit, and lazy-loading/poster support for thumbnails.

Testing

  • Ran php -l against all plugin PHP files and received no syntax errors (success).
  • Attempted a Playwright page screenshot to validate the homepage rendering but it failed due to no web server responding at http://localhost:8080 (environment limitation).
  • Created and linted the plugin files in the repository; automated PHP linting passed for each file.

Codex Task

Summary by CodeRabbit

Release Notes

  • New Features
    • Users can upload videos with title, description, category, tags, and thumbnail support through an authenticated form.
    • Video player with like-tracking functionality.
    • Grid display of videos with search capability.
    • Homepage showcase featuring videos, testimonials, and contact information.
    • Automatic view and like counting for all published videos.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 14, 2026

📝 Walkthrough

Walkthrough

Introduces the AVStube Video Manager, a comprehensive WordPress plugin for managing video content. Includes custom post type registration with metadata fields, a REST API with endpoints for video retrieval and interaction tracking (likes/views), frontend upload form with validation, shortcode-based rendering for video player/grid/homepage, and CSS/JavaScript assets for UI and client-side functionality.

Changes

Cohort / File(s) Summary
Styling
wp-content/plugins/avstube-video-manager/assets/css/avstube-video-manager.css
CSS stylesheet defining dark theme, responsive video grid layout, video card styling with hover effects, thumbnail and duration badges, player wrapper, like button, and form styling.
Client-Side Logic
wp-content/plugins/avstube-video-manager/assets/js/avstube-player.js, wp-content/plugins/avstube-video-manager/assets/js/avstube-upload.js
JavaScript for player interactions (like button clicks, view tracking via REST API calls) and upload form client-side validation (title, file/URL requirements).
Plugin Bootstrap
wp-content/plugins/avstube-video-manager/avstube-video-manager.php
Main plugin file defining version constants, including core classes, initializing plugin on plugins_loaded, and managing activation/deactivation hooks with rewrite rule flushing.
Post Type & Admin
wp-content/plugins/avstube-video-manager/includes/class-video-post-type.php
Registers custom "video" post type with REST support, defines meta fields (MP4 URL, embed URL, duration, views, likes), adds meta box editor, and builds admin dashboard with video counts and quick-action links.
Video Upload
wp-content/plugins/avstube-video-manager/includes/class-video-upload.php
Renders authenticated user upload form with fields for title, description, category, tags, thumbnail, MP4, external URL, and embed URL; validates and processes submissions to create pending video posts with media attachment and metadata.
REST API
wp-content/plugins/avstube-video-manager/includes/class-video-api.php
Registers three REST endpoints: GET /video/{id} (cached video data), POST /video/like/{id} (increment likes), and POST /video/view/{id} (increment views); implements cache invalidation and validation.
Video Rendering
wp-content/plugins/avstube-video-manager/includes/class-video-player.php
Registers three shortcodes (avstube_video_player, avstube_video_grid, avstube_homepage) that render individual player units, video grids with thumbnails/metadata, and a homepage layout with search and testimonials; enqueues assets and localizes API URL.

Sequence Diagrams

sequenceDiagram
    participant User as User (Browser)
    participant Form as Upload Form
    participant Server as WordPress Server
    participant DB as Database
    participant Media as Media Library

    User->>Form: Submit video upload
    Form->>Form: Validate (title, file/URL)
    alt Validation fails
        Form->>User: Show alert
    else Validation passes
        Form->>Server: POST form submission
        Server->>Server: Verify nonce & permissions
        Server->>DB: Create pending video post
        Server->>Media: Upload/process MP4 file
        Media->>DB: Attach media & thumbnail
        Server->>DB: Store metadata (views, likes, URLs)
        Server->>User: Redirect with success
    end
Loading
sequenceDiagram
    participant User as User (Browser)
    participant Player as Video Player
    participant JS as JavaScript Handler
    participant API as REST API
    participant DB as Database

    User->>Player: Click like button
    Player->>JS: Event triggered (avstube-like-button)
    JS->>API: POST /avstube/v1/video/like/{id}
    API->>DB: Increment video_likes meta
    DB->>API: Return updated count
    API->>JS: Response with likes data
    JS->>Player: Update like count display

    User->>Player: Play video
    Player->>JS: Play event triggered (avstube-player)
    alt View not yet tracked
        JS->>API: POST /avstube/v1/video/view/{id}
        API->>DB: Increment video_views meta
        DB->>API: Return updated count
        API->>JS: Response
        JS->>Player: Mark view as tracked
    end
Loading
sequenceDiagram
    participant Browser as User (Browser)
    participant Shortcode as Shortcode Renderer
    participant API as REST API
    participant DB as Database
    participant Cache as Transient Cache

    Browser->>Shortcode: Request video grid page
    Shortcode->>DB: Query video posts
    DB->>Shortcode: Return video list
    loop For each video
        Shortcode->>Cache: Check cached video data
        alt Cache hit
            Cache->>Shortcode: Return cached data
        else Cache miss
            Shortcode->>API: GET /avstube/v1/video/{id}
            API->>DB: Fetch post & metadata
            DB->>API: Return video data
            API->>Cache: Store in transient (5min)
            API->>Shortcode: Return data
        end
    end
    Shortcode->>Browser: Render grid with cards
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Hops with glee through code so fine,
Videos now in grid align,
REST calls tracking likes and views,
Upload forms with nonce-proof clues,
AVStube blooms—the plugin's mine! 🎬

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add AVSTube Video Manager plugin with upload, player, and REST API' directly and comprehensively describes the main changes in the PR, covering the three core components being introduced.
Docstring Coverage ✅ Passed Docstring coverage is 96.43% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/create-avstube-video-manager-plugin
📝 Coding Plan
  • Generate coding plan for human review comments

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can use Trivy to scan for security misconfigurations and secrets in Infrastructure as Code files.

Add a .trivyignore file to your project to customize which findings Trivy reports.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 930180e8a4

ℹ️ 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".

Comment on lines +67 to +68
if ( ! $video || 'video' !== $video->post_type ) {
return new WP_REST_Response( array( 'message' => 'Video not found' ), 404 );
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Restrict video API responses to published posts

This endpoint is publicly accessible (permission_callback is always true), but it only checks post type and not post status, so /wp-json/avstube/v1/video/{id} will return data for pending videos as soon as they are uploaded. Because frontend submissions are intentionally saved as pending for moderation, unauthenticated users can enumerate IDs and read unreviewed content and media URLs before approval.

Useful? React with 👍 / 👎.

Comment on lines +95 to +99
$video_id = absint( $request['id'] );
$likes = (int) get_post_meta( $video_id, 'video_likes', true );
$likes++;
update_post_meta( $video_id, 'video_likes', $likes );
delete_transient( 'avstube_video_api_' . $video_id );
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Validate target post before incrementing likes/views

The counter mutation routes update post meta immediately from the numeric path param without verifying that the ID belongs to a real video post; increment_view follows the same pattern. Since both endpoints are public, callers can POST arbitrary IDs and skew metrics on unrelated content (or create orphaned metadata entries), which corrupts analytics and increases unnecessary database writes.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (5)
wp-content/plugins/avstube-video-manager/includes/class-video-player.php (1)

146-146: Avoid nested shortcode execution for internal rendering.

Line 146 calls do_shortcode('[avstube_video_grid]') from within the same class. Calling $this->render_video_grid_shortcode() is simpler and avoids extra shortcode parser overhead.

Direct internal call
-		<section class="section"><div class="container"><h2 class="text-center mb-5"><?php esc_html_e( 'Video Nổi Bật', 'avstube-video-manager' ); ?></h2><?php echo do_shortcode( '[avstube_video_grid]' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div></section>
+		<section class="section"><div class="container"><h2 class="text-center mb-5"><?php esc_html_e( 'Video Nổi Bật', 'avstube-video-manager' ); ?></h2><?php echo $this->render_video_grid_shortcode(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div></section>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wp-content/plugins/avstube-video-manager/includes/class-video-player.php` at
line 146, The template currently calls do_shortcode('[avstube_video_grid]')
inside class-video-player.php which re-parses shortcodes unnecessarily; replace
that call with a direct internal method invocation like
$this->render_video_grid_shortcode() (or the existing method that generates the
same output) to avoid nested shortcode execution, remove the phpcs ignore, and
ensure the returned HTML is properly escaped/treated the same way as the
original shortcode output.
wp-content/plugins/avstube-video-manager/assets/css/avstube-video-manager.css (1)

105-111: Prefer aspect-ratio over fixed min-height: 520px for embeds/videos.

Line 108 forces tall players on small screens and can push critical content below the fold.

Responsive alternative
 .avstube-player,
 .avstube-embed-container iframe {
   width: 100%;
-  min-height: 520px;
+  aspect-ratio: 16 / 9;
+  height: auto;
   border: 0;
   background: `#000`;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@wp-content/plugins/avstube-video-manager/assets/css/avstube-video-manager.css`
around lines 105 - 111, Replace the fixed min-height on the .avstube-player and
.avstube-embed-container iframe rule with a responsive aspect-ratio approach:
remove min-height: 520px, add an aspect-ratio (e.g., 16/9) and ensure width:100%
and height:auto so the embed scales by width on small screens; optionally keep a
max-height or a media-query override for very tall viewports. Target the
.avstube-player and .avstube-embed-container iframe selectors when making this
change.
wp-content/plugins/avstube-video-manager/assets/js/avstube-upload.js (1)

10-24: Guard missing form controls before dereferencing .value.

On Line 16 and Line 22, the handler assumes all queried elements exist. If markup is customized and one field is missing, validation throws and submit proceeds without checks.

Proposed hardening
     form.addEventListener('submit', function (event) {
       var title = form.querySelector('#avstube_video_title');
       var file = form.querySelector('#avstube_video_file');
       var external = form.querySelector('#avstube_video_external_mp4');
       var embed = form.querySelector('#avstube_video_embed_url');
+      if (!title || !file || !external || !embed) {
+        return;
+      }

       if (!title.value.trim()) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wp-content/plugins/avstube-video-manager/assets/js/avstube-upload.js` around
lines 10 - 24, The submit handler assumes DOM elements exist and dereferences
.value on title, file, external, and embed which can throw if any control is
missing; update the form.addEventListener('submit', function (event) { ... })
logic to null-check each queried element (the variables title, file, external,
embed returned by form.querySelector('#avstube_video_title'),
'#avstube_video_file', '#avstube_video_external_mp4',
'#avstube_video_embed_url') before accessing .value, treating missing elements
as empty strings for validation, and only call event.preventDefault()/alert when
the computed values indicate invalid input.
wp-content/plugins/avstube-video-manager/assets/js/avstube-player.js (1)

10-12: Handle non-2xx responses explicitly before JSON parsing.

Line 10 currently parses JSON regardless of HTTP status, and Line 35 swallows all failures. This hides server-side errors and complicates client recovery.

Proposed error handling
     }).then(function (response) {
-      return response.json();
+      if (!response.ok) {
+        throw new Error('Request failed: ' + response.status);
+      }
+      return response.json();
     });
   }

Also applies to: 35-35

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

In `@wp-content/plugins/avstube-video-manager/assets/js/avstube-player.js` around
lines 10 - 12, The fetch response is being JSON-parsed unconditionally in the
then(response) => response.json() chain and the downstream .catch swallows all
failures; update the promise chain in avstube-player.js so you first check
response.ok (or response.status) in the then handler and, if not ok, read and
include the error body/text and throw a descriptive Error (including status and
server message) before calling response.json(); also ensure the existing catch
handler does not silently swallow errors but logs/propagates them so callers can
handle server-side errors appropriately.
wp-content/plugins/avstube-video-manager/includes/class-video-upload.php (1)

149-152: Use Post/Redirect/Get after successful submission to prevent duplicate posts.

After Line 149–152, a page refresh can resubmit the same POST and create duplicate pending videos.

PRG follow-up
 		$this->handle_thumbnail_upload( $video_id );
 		update_post_meta( $video_id, 'video_views', 0 );
 		update_post_meta( $video_id, 'video_likes', 0 );
+
+		$redirect = wp_get_referer() ? wp_get_referer() : home_url( '/' );
+		wp_safe_redirect( add_query_arg( 'avstube_upload', 'success', $redirect ) );
+		exit;
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wp-content/plugins/avstube-video-manager/includes/class-video-upload.php`
around lines 149 - 152, After creating the post and doing the thumbnail/meta
updates (the block that calls $this->handle_thumbnail_upload( $video_id ); and
update_post_meta( $video_id, 'video_views', 0 ); update_post_meta( $video_id,
'video_likes', 0 );), implement a Post/Redirect/Get by redirecting the user away
from the POST handler: call wp_safe_redirect(...) or wp_redirect(...) to a
confirmation or list page (for example use add_query_arg to append a success
flag to wp_get_referer() or to an admin page), then call exit; to stop
execution. This prevents the browser from resubmitting the form on refresh;
place the redirect immediately after those update_post_meta calls in the class
(class Video_Upload / method that handles the submission) and ensure no further
output occurs before the redirect.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@wp-content/plugins/avstube-video-manager/assets/css/avstube-video-manager.css`:
- Around line 1-5: The stylesheet uses global selectors (body, .header, footer)
that override site styles; update all top-level selectors (e.g., body, .header,
footer and any other generic element selectors) to be scoped under a plugin root
container by prefixing them with .avstube-vm-root (e.g., .avstube-vm-root
body-equivalent rules or .avstube-vm-root .header, .avstube-vm-root footer) and
ensure the plugin’s rendered markup is wrapped with <div
class="avstube-vm-root">…</div> so the CSS only affects plugin UI.

In `@wp-content/plugins/avstube-video-manager/includes/class-video-api.php`:
- Around line 30-47: Replace the permissive 'permission_callback' =>
'__return_true' on both register_rest_route calls with a real permission check
(e.g. 'permission_callback' => array($this, 'check_video_permission')) and
implement check_video_permission($request) to: 1) validate the ID param and load
the post via get_post($id), 2) ensure the post exists and has the expected
post_type (e.g. your plugin's video post type) and post_status === 'publish',
and 3) enforce an authorization rule (either require a logged-in user with a
capability like current_user_can('edit_post', $id) or verify a REST nonce/token
for public endpoints); also update like_video() and increment_view() to
re-validate the post_type/status at the start (and add rate-limiting or
anti-abuse checks for increment_view if you want to allow unauthenticated hits)
so the handlers cannot be called to modify arbitrary or unpublished posts.
- Around line 66-69: The endpoint currently only validates the post_type for the
retrieved $video and returns its payload even for non-published posts; update
the check after $video = get_post( $video_id ) to also verify publication by
ensuring $video->post_status === 'publish' (or, if you need to allow private
access for authenticated users, add a current_user_can('read_post', $video_id)
branch); if the status is not publish (and the user lacks read capability),
return the existing WP_REST_Response( array( 'message' => 'Video not found' ),
404 ) to avoid exposing draft/pending/private content.
- Around line 94-121: Both like_video and increment_view lack the post
existence/post-type validation and use non-atomic read-modify-write updates; add
the same validation checks used in get_video_data (verify post exists and is the
correct post_type) before modifying meta, and replace the
get_post_meta/update_post_meta sequence in like_video and increment_view with an
atomic increment (e.g. perform a single $wpdb->query() UPDATE ... meta_value =
meta_value + 1 on the appropriate postmeta row or implement a CAS-style retry
loop) to avoid lost counts under concurrency, then clear the transient as
before.

In `@wp-content/plugins/avstube-video-manager/includes/class-video-player.php`:
- Line 138: The string "V1.0 (2026)" is hardcoded in class-video-player.php
output; update the rendering code (the method that outputs this paragraph in
class Video_Player) to use the plugin's actual version constant or retrieved
version (e.g., AVSTUBE_VERSION or get_plugin_data()) and the current year via
date('Y'), and output them with proper escaping/translation (esc_html__ /
printf) instead of the fixed text so the displayed version and year remain
correct automatically.

In `@wp-content/plugins/avstube-video-manager/includes/class-video-upload.php`:
- Around line 17-21: The plugin currently enqueues upload CSS/JS globally from
enqueue_assets, causing collisions; change the pattern so assets are only
enqueued when the shortcode is actually used: register your scripts/styles early
(in __construct or enqueue_assets) but do not call wp_enqueue_* there globally —
instead enqueue them inside render_upload_form (or conditionally in
enqueue_assets by checking has_shortcode(get_post()->post_content,
'avstube_video_upload_form') or is_singular() with has_shortcode) so files load
only when render_upload_form outputs the shortcode markup; update
enqueue_assets, __construct, and render_upload_form accordingly to register vs
enqueue and remove unconditional global enqueuing.
- Around line 104-122: The current flow calls wp_insert_post() regardless of
client-side checks; add a server-side validation gate before the
wp_insert_post() call that ensures $title is non-empty and at least one source
is provided (check $video_external_mp4, $video_embed_url and the uploaded file
variable used later), and if validation fails return/exit with a proper error
(e.g., return new WP_Error or wp_die with an explanatory message) so no post is
created; apply the same validation to the other insertion path referenced around
the later block that uses the same variables to prevent inserting empty/invalid
videos.

---

Nitpick comments:
In
`@wp-content/plugins/avstube-video-manager/assets/css/avstube-video-manager.css`:
- Around line 105-111: Replace the fixed min-height on the .avstube-player and
.avstube-embed-container iframe rule with a responsive aspect-ratio approach:
remove min-height: 520px, add an aspect-ratio (e.g., 16/9) and ensure width:100%
and height:auto so the embed scales by width on small screens; optionally keep a
max-height or a media-query override for very tall viewports. Target the
.avstube-player and .avstube-embed-container iframe selectors when making this
change.

In `@wp-content/plugins/avstube-video-manager/assets/js/avstube-player.js`:
- Around line 10-12: The fetch response is being JSON-parsed unconditionally in
the then(response) => response.json() chain and the downstream .catch swallows
all failures; update the promise chain in avstube-player.js so you first check
response.ok (or response.status) in the then handler and, if not ok, read and
include the error body/text and throw a descriptive Error (including status and
server message) before calling response.json(); also ensure the existing catch
handler does not silently swallow errors but logs/propagates them so callers can
handle server-side errors appropriately.

In `@wp-content/plugins/avstube-video-manager/assets/js/avstube-upload.js`:
- Around line 10-24: The submit handler assumes DOM elements exist and
dereferences .value on title, file, external, and embed which can throw if any
control is missing; update the form.addEventListener('submit', function (event)
{ ... }) logic to null-check each queried element (the variables title, file,
external, embed returned by form.querySelector('#avstube_video_title'),
'#avstube_video_file', '#avstube_video_external_mp4',
'#avstube_video_embed_url') before accessing .value, treating missing elements
as empty strings for validation, and only call event.preventDefault()/alert when
the computed values indicate invalid input.

In `@wp-content/plugins/avstube-video-manager/includes/class-video-player.php`:
- Line 146: The template currently calls do_shortcode('[avstube_video_grid]')
inside class-video-player.php which re-parses shortcodes unnecessarily; replace
that call with a direct internal method invocation like
$this->render_video_grid_shortcode() (or the existing method that generates the
same output) to avoid nested shortcode execution, remove the phpcs ignore, and
ensure the returned HTML is properly escaped/treated the same way as the
original shortcode output.

In `@wp-content/plugins/avstube-video-manager/includes/class-video-upload.php`:
- Around line 149-152: After creating the post and doing the thumbnail/meta
updates (the block that calls $this->handle_thumbnail_upload( $video_id ); and
update_post_meta( $video_id, 'video_views', 0 ); update_post_meta( $video_id,
'video_likes', 0 );), implement a Post/Redirect/Get by redirecting the user away
from the POST handler: call wp_safe_redirect(...) or wp_redirect(...) to a
confirmation or list page (for example use add_query_arg to append a success
flag to wp_get_referer() or to an admin page), then call exit; to stop
execution. This prevents the browser from resubmitting the form on refresh;
place the redirect immediately after those update_post_meta calls in the class
(class Video_Upload / method that handles the submission) and ensure no further
output occurs before the redirect.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e4bdb072-73a7-480b-b189-7bde0f458412

📥 Commits

Reviewing files that changed from the base of the PR and between c5b1ce9 and 930180e.

📒 Files selected for processing (8)
  • wp-content/plugins/avstube-video-manager/assets/css/avstube-video-manager.css
  • wp-content/plugins/avstube-video-manager/assets/js/avstube-player.js
  • wp-content/plugins/avstube-video-manager/assets/js/avstube-upload.js
  • wp-content/plugins/avstube-video-manager/avstube-video-manager.php
  • wp-content/plugins/avstube-video-manager/includes/class-video-api.php
  • wp-content/plugins/avstube-video-manager/includes/class-video-player.php
  • wp-content/plugins/avstube-video-manager/includes/class-video-post-type.php
  • wp-content/plugins/avstube-video-manager/includes/class-video-upload.php

Comment on lines +1 to +5
body {
background: #000;
color: #ddd;
font-family: Arial, Helvetica, sans-serif;
}
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 global selectors to plugin containers to avoid site-wide theme overrides.

Line 1 (body), Line 7 (.header), and Line 122 (footer) target generic elements. Because this stylesheet is globally enqueued, it can re-theme unrelated pages.

Proposed scoping direction
-body {
+.avstube-vm-root {
   background: `#000`;
   color: `#ddd`;
   font-family: Arial, Helvetica, sans-serif;
 }

-.header {
+.avstube-vm-root .header {
   background: `#111`;
   ...
 }

-footer {
+.avstube-vm-root footer {
   background: `#000`;
   ...
 }

Also wrap plugin-rendered markup with <div class="avstube-vm-root">...</div>.

Also applies to: 7-12, 122-129

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

In
`@wp-content/plugins/avstube-video-manager/assets/css/avstube-video-manager.css`
around lines 1 - 5, The stylesheet uses global selectors (body, .header, footer)
that override site styles; update all top-level selectors (e.g., body, .header,
footer and any other generic element selectors) to be scoped under a plugin root
container by prefixing them with .avstube-vm-root (e.g., .avstube-vm-root
body-equivalent rules or .avstube-vm-root .header, .avstube-vm-root footer) and
ensure the plugin’s rendered markup is wrapped with <div
class="avstube-vm-root">…</div> so the CSS only affects plugin UI.

Comment on lines +30 to +47
register_rest_route(
'avstube/v1',
'/video/like/(?P<id>\d+)',
array(
'methods' => 'POST',
'callback' => array( $this, 'like_video' ),
'permission_callback' => '__return_true',
)
);

register_rest_route(
'avstube/v1',
'/video/view/(?P<id>\d+)',
array(
'methods' => 'POST',
'callback' => array( $this, 'increment_view' ),
'permission_callback' => '__return_true',
)
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify mutating routes are currently public.
rg -n "video/(like|view).*permission_callback|permission_callback'\\s*=>\\s*'__return_true'" wp-content/plugins/avstube-video-manager/includes/class-video-api.php -n -A3 -B3

Repository: huzely/Decrypt

Length of output: 624


🏁 Script executed:

#!/bin/bash
# Inspect the like_video and increment_view method implementations
sed -n '1,200p' wp-content/plugins/avstube-video-manager/includes/class-video-api.php | cat -n

Repository: huzely/Decrypt

Length of output: 4374


POST endpoints for likes and views are publicly writable with no validation.

Lines 36 and 46 use permission_callback => '__return_true', allowing unauthenticated requests to call like_video() and increment_view(). These methods perform no checks on post type or publication status—they accept any video ID and increment counters on arbitrary posts, including unpublished/draft content. Metrics are trivially gameable and can be abused via automated requests.

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

In `@wp-content/plugins/avstube-video-manager/includes/class-video-api.php` around
lines 30 - 47, Replace the permissive 'permission_callback' => '__return_true'
on both register_rest_route calls with a real permission check (e.g.
'permission_callback' => array($this, 'check_video_permission')) and implement
check_video_permission($request) to: 1) validate the ID param and load the post
via get_post($id), 2) ensure the post exists and has the expected post_type
(e.g. your plugin's video post type) and post_status === 'publish', and 3)
enforce an authorization rule (either require a logged-in user with a capability
like current_user_can('edit_post', $id) or verify a REST nonce/token for public
endpoints); also update like_video() and increment_view() to re-validate the
post_type/status at the start (and add rate-limiting or anti-abuse checks for
increment_view if you want to allow unauthenticated hits) so the handlers cannot
be called to modify arbitrary or unpublished posts.

Comment on lines +66 to +69
$video = get_post( $video_id );
if ( ! $video || 'video' !== $video->post_type ) {
return new WP_REST_Response( array( 'message' => 'Video not found' ), 404 );
}
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

🧩 Analysis chain

🏁 Script executed:

find . -name "class-video-api.php" -type f 2>/dev/null

Repository: huzely/Decrypt

Length of output: 129


🏁 Script executed:

cat -n wp-content/plugins/avstube-video-manager/includes/class-video-api.php | head -100

Repository: huzely/Decrypt

Length of output: 3483


🏁 Script executed:

# Check if this endpoint requires authentication
rg "register_rest_route|wp_rest_user_can" wp-content/plugins/avstube-video-manager/includes/class-video-api.php -B 5 -A 5

Repository: huzely/Decrypt

Length of output: 790


🏁 Script executed:

# Search for the method containing lines 66-69 to understand the full context
rg -B 15 -A 5 "get_post\( \$video_id \)" wp-content/plugins/avstube-video-manager/includes/class-video-api.php

Repository: huzely/Decrypt

Length of output: 40


Add publication status check to prevent disclosure of non-published video content.

Line 67 only checks post_type but not post_status. With the endpoint's permission_callback set to __return_true, any anonymous user can request non-published videos (draft, pending, private) by ID and receive the full payload, including URL, metadata, and content.

Add a publication gate:

Proposed fix
 		$video = get_post( $video_id );
 		if ( ! $video || 'video' !== $video->post_type ) {
 			return new WP_REST_Response( array( 'message' => 'Video not found' ), 404 );
 		}
+		if ( 'publish' !== $video->post_status && ! current_user_can( 'edit_post', $video_id ) ) {
+			return new WP_REST_Response( array( 'message' => 'Video not found' ), 404 );
+		}
📝 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
$video = get_post( $video_id );
if ( ! $video || 'video' !== $video->post_type ) {
return new WP_REST_Response( array( 'message' => 'Video not found' ), 404 );
}
$video = get_post( $video_id );
if ( ! $video || 'video' !== $video->post_type ) {
return new WP_REST_Response( array( 'message' => 'Video not found' ), 404 );
}
if ( 'publish' !== $video->post_status && ! current_user_can( 'edit_post', $video_id ) ) {
return new WP_REST_Response( array( 'message' => 'Video not found' ), 404 );
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wp-content/plugins/avstube-video-manager/includes/class-video-api.php` around
lines 66 - 69, The endpoint currently only validates the post_type for the
retrieved $video and returns its payload even for non-published posts; update
the check after $video = get_post( $video_id ) to also verify publication by
ensuring $video->post_status === 'publish' (or, if you need to allow private
access for authenticated users, add a current_user_can('read_post', $video_id)
branch); if the status is not publish (and the user lacks read capability),
return the existing WP_REST_Response( array( 'message' => 'Video not found' ),
404 ) to avoid exposing draft/pending/private content.

Comment on lines +94 to +121
public function like_video( WP_REST_Request $request ) {
$video_id = absint( $request['id'] );
$likes = (int) get_post_meta( $video_id, 'video_likes', true );
$likes++;
update_post_meta( $video_id, 'video_likes', $likes );
delete_transient( 'avstube_video_api_' . $video_id );

return new WP_REST_Response(
array(
'video_id' => $video_id,
'likes' => $likes,
),
200
);
}

/**
* Increment view counter when playback starts.
*
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response
*/
public function increment_view( WP_REST_Request $request ) {
$video_id = absint( $request['id'] );
$views = (int) get_post_meta( $video_id, 'video_views', true );
$views++;
update_post_meta( $video_id, 'video_views', $views );
delete_transient( 'avstube_video_api_' . $video_id );
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

🧩 Analysis chain

🏁 Script executed:

cat -n wp-content/plugins/avstube-video-manager/includes/class-video-api.php | sed -n '90,125p'

Repository: huzely/Decrypt

Length of output: 1313


🏁 Script executed:

# Find where the REST routes are registered for these endpoints
rg -A 5 "like_video|increment_view" wp-content/plugins/avstube-video-manager/includes/class-video-api.php | head -50

Repository: huzely/Decrypt

Length of output: 908


🏁 Script executed:

# Check class definition and constructor
cat -n wp-content/plugins/avstube-video-manager/includes/class-video-api.php | head -100

Repository: huzely/Decrypt

Length of output: 3483


Add post validation and atomic counter updates to prevent invalid post access and lost increments.

The like_video and increment_view methods lack validation before updating counters, while get_video_data (line 66–69) already demonstrates the correct pattern. Additionally, lines 96–98 and 118–120 use non-atomic read-modify-write operations that can lose counts under concurrent requests.

Add post existence and post-type checks before any counter updates, matching the pattern in get_video_data. For increment integrity, replace the read-modify-write pattern with an atomic update strategy (single SQL increment via $wpdb->query() or a CAS-style retry loop) rather than plain get_post_meta/update_post_meta.

Example safety checks for like_video
 	public function like_video( WP_REST_Request $request ) {
 		$video_id = absint( $request['id'] );
+		$video    = get_post( $video_id );
+		if ( ! $video || 'video' !== $video->post_type ) {
+			return new WP_REST_Response( array( 'message' => 'Video not found' ), 404 );
+		}
 		$likes = (int) get_post_meta( $video_id, 'video_likes', true );
 		$likes++;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wp-content/plugins/avstube-video-manager/includes/class-video-api.php` around
lines 94 - 121, Both like_video and increment_view lack the post
existence/post-type validation and use non-atomic read-modify-write updates; add
the same validation checks used in get_video_data (verify post exists and is the
correct post_type) before modifying meta, and replace the
get_post_meta/update_post_meta sequence in like_video and increment_view with an
atomic increment (e.g. perform a single $wpdb->query() UPDATE ... meta_value =
meta_value + 1 on the appropriate postmeta row or implement a CAS-style retry
loop) to avoid lost counts under concurrency, then clear the transient as
before.

<header class="header">
<h1>AVSTube</h1>
<p class="lead"><?php esc_html_e( 'Nền tảng chia sẻ video chất lượng cao', 'avstube-video-manager' ); ?></p>
<p><?php esc_html_e( 'Phiên bản mới nhất: V1.0 (2026)', 'avstube-video-manager' ); ?></p>
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

Hardcoded year/version text will age quickly.

Line 138 embeds V1.0 (2026) directly in output, which will become stale and user-visible over time.

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

In `@wp-content/plugins/avstube-video-manager/includes/class-video-player.php` at
line 138, The string "V1.0 (2026)" is hardcoded in class-video-player.php
output; update the rendering code (the method that outputs this paragraph in
class Video_Player) to use the plugin's actual version constant or retrieved
version (e.g., AVSTUBE_VERSION or get_plugin_data()) and the current year via
date('Y'), and output them with proper escaping/translation (esc_html__ /
printf) instead of the fixed text so the displayed version and year remain
correct automatically.

Comment on lines +17 to +21
public function __construct() {
add_shortcode( 'avstube_video_upload_form', array( $this, 'render_upload_form' ) );
add_action( 'init', array( $this, 'handle_form_submission' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
}
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 global frontend enqueue for upload assets.

Line 20 + Line 28–31 enqueue upload CSS/JS on every frontend request, even where the shortcode is not used. This is also what makes global CSS collisions much more likely.

Safer enqueue pattern
 	public function __construct() {
 		add_shortcode( 'avstube_video_upload_form', array( $this, 'render_upload_form' ) );
 		add_action( 'init', array( $this, 'handle_form_submission' ) );
-		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
 	}
@@
 	public function render_upload_form() {
+		$this->enqueue_assets();
 		if ( ! is_user_logged_in() ) {

Also applies to: 28-31

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

In `@wp-content/plugins/avstube-video-manager/includes/class-video-upload.php`
around lines 17 - 21, The plugin currently enqueues upload CSS/JS globally from
enqueue_assets, causing collisions; change the pattern so assets are only
enqueued when the shortcode is actually used: register your scripts/styles early
(in __construct or enqueue_assets) but do not call wp_enqueue_* there globally —
instead enqueue them inside render_upload_form (or conditionally in
enqueue_assets by checking has_shortcode(get_post()->post_content,
'avstube_video_upload_form') or is_singular() with has_shortcode) so files load
only when render_upload_form outputs the shortcode markup; update
enqueue_assets, __construct, and render_upload_form accordingly to register vs
enqueue and remove unconditional global enqueuing.

Comment on lines +104 to +122
$title = isset( $_POST['avstube_video_title'] ) ? sanitize_text_field( wp_unslash( $_POST['avstube_video_title'] ) ) : '';
$description = isset( $_POST['avstube_video_description'] ) ? wp_kses_post( wp_unslash( $_POST['avstube_video_description'] ) ) : '';
$category = isset( $_POST['avstube_video_category'] ) ? absint( wp_unslash( $_POST['avstube_video_category'] ) ) : 0;
$tags = isset( $_POST['avstube_video_tags'] ) ? sanitize_text_field( wp_unslash( $_POST['avstube_video_tags'] ) ) : '';

$video_external_mp4 = isset( $_POST['avstube_video_external_mp4'] ) ? esc_url_raw( wp_unslash( $_POST['avstube_video_external_mp4'] ) ) : '';
$video_embed_url = isset( $_POST['avstube_video_embed_url'] ) ? esc_url_raw( wp_unslash( $_POST['avstube_video_embed_url'] ) ) : '';

$video_id = wp_insert_post(
array(
'post_type' => 'video',
'post_title' => $title,
'post_content' => $description,
'post_status' => 'pending',
'post_author' => get_current_user_id(),
),
true
);

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

Enforce required fields server-side before wp_insert_post().

Line 112 inserts a pending video even when title/source checks are bypassed client-side. JS validation is not a security boundary; backend must reject empty titles and submissions without MP4/external/embed source.

Proposed validation gate
 		$video_external_mp4 = isset( $_POST['avstube_video_external_mp4'] ) ? esc_url_raw( wp_unslash( $_POST['avstube_video_external_mp4'] ) ) : '';
 		$video_embed_url    = isset( $_POST['avstube_video_embed_url'] ) ? esc_url_raw( wp_unslash( $_POST['avstube_video_embed_url'] ) ) : '';
+		$has_uploaded_mp4   = ! empty( $_FILES['avstube_video_file']['name'] );
+		$has_external_mp4   = ! empty( $video_external_mp4 ) && preg_match( '/\.mp4($|\?)/i', $video_external_mp4 );
+		$has_embed_url      = ! empty( $video_embed_url );
+
+		if ( '' === $title || ( ! $has_uploaded_mp4 && ! $has_external_mp4 && ! $has_embed_url ) ) {
+			return;
+		}

 		$video_id = wp_insert_post(

Also applies to: 138-147

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

In `@wp-content/plugins/avstube-video-manager/includes/class-video-upload.php`
around lines 104 - 122, The current flow calls wp_insert_post() regardless of
client-side checks; add a server-side validation gate before the
wp_insert_post() call that ensures $title is non-empty and at least one source
is provided (check $video_external_mp4, $video_embed_url and the uploaded file
variable used later), and if validation fails return/exit with a proper error
(e.g., return new WP_Error or wp_die with an explanatory message) so no post is
created; apply the same validation to the other insertion path referenced around
the later block that uses the same variables to prevent inserting empty/invalid
videos.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant