Add AVSTube Video Manager plugin with upload, player, and REST API#47
Add AVSTube Video Manager plugin with upload, player, and REST API#47
Conversation
📝 WalkthroughWalkthroughIntroduces 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
Sequence DiagramssequenceDiagram
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
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
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
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
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 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. |
There was a problem hiding this comment.
💡 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".
| if ( ! $video || 'video' !== $video->post_type ) { | ||
| return new WP_REST_Response( array( 'message' => 'Video not found' ), 404 ); |
There was a problem hiding this comment.
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 👍 / 👎.
| $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 ); |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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 fixedmin-height: 520pxfor 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
📒 Files selected for processing (8)
wp-content/plugins/avstube-video-manager/assets/css/avstube-video-manager.csswp-content/plugins/avstube-video-manager/assets/js/avstube-player.jswp-content/plugins/avstube-video-manager/assets/js/avstube-upload.jswp-content/plugins/avstube-video-manager/avstube-video-manager.phpwp-content/plugins/avstube-video-manager/includes/class-video-api.phpwp-content/plugins/avstube-video-manager/includes/class-video-player.phpwp-content/plugins/avstube-video-manager/includes/class-video-post-type.phpwp-content/plugins/avstube-video-manager/includes/class-video-upload.php
| body { | ||
| background: #000; | ||
| color: #ddd; | ||
| font-family: Arial, Helvetica, sans-serif; | ||
| } |
There was a problem hiding this comment.
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.
| 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', | ||
| ) |
There was a problem hiding this comment.
🧩 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 -B3Repository: 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 -nRepository: 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.
| $video = get_post( $video_id ); | ||
| if ( ! $video || 'video' !== $video->post_type ) { | ||
| return new WP_REST_Response( array( 'message' => 'Video not found' ), 404 ); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "class-video-api.php" -type f 2>/dev/nullRepository: huzely/Decrypt
Length of output: 129
🏁 Script executed:
cat -n wp-content/plugins/avstube-video-manager/includes/class-video-api.php | head -100Repository: 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 5Repository: 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.phpRepository: 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.
| $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.
| 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 ); |
There was a problem hiding this comment.
🧩 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 -50Repository: 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 -100Repository: 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> |
There was a problem hiding this comment.
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.
| 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' ) ); | ||
| } |
There was a problem hiding this comment.
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.
| $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 | ||
| ); | ||
|
|
There was a problem hiding this comment.
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.
Motivation
Description
wp-content/plugins/avstube-video-manager/includingavstube-video-manager.php,includes/classes (class-video-post-type.php,class-video-upload.php,class-video-api.php,class-video-player.php) andassets/CSS/JS for upload and player features.videocustom post type with supports for title, editor, thumbnail, comments, author, custom fields and meta fieldsvideo_mp4_url,video_embed_url,video_duration,video_views, andvideo_likes, plus admin meta boxes and a dashboard menu for moderation.[avstube_video_upload_form]) that accepts title, description, category, tags, thumbnail, MP4 upload or external MP4/embed URL and saves submissions aspending; files are validated (MP4-only) and limited to 100MB.[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.GET /wp-json/avstube/v1/video/{id},POST /wp-json/avstube/v1/video/like/{id}, andPOST /wp-json/avstube/v1/video/view/{id}that return/modify the requested fields and use transient caching for performance.Testing
php -lagainst all plugin PHP files and received no syntax errors (success).http://localhost:8080(environment limitation).Codex Task
Summary by CodeRabbit
Release Notes