Skip to content

fix: store cache transient-version markers with an expiration#3266

Open
MdAsifHossainNadim wants to merge 5 commits into
developfrom
feature/clear-dokan-cache
Open

fix: store cache transient-version markers with an expiration#3266
MdAsifHossainNadim wants to merge 5 commits into
developfrom
feature/clear-dokan-cache

Conversation

@MdAsifHossainNadim

@MdAsifHossainNadim MdAsifHossainNadim commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Cache group transient-version markers are written via set_transient() with no expiration, so WordPress stores them with autoload = yes and never clears them. On sites without a persistent object cache this bloats the autoloaded options (a client reported 5,000+ autoloaded rows, mostly _transient_dokan_cache_*-transient-version).

This adds a filterable expiration to the marker so WordPress stores it autoload = no (with a _transient_timeout_ companion) and garbage-collects it like any transient.

Related PR: https://github.com/getdokan/dokan-pro/pull/5765
Closes: https://github.com/getdokan/plugin-internal-tasks/issues/1965

Change

includes/Traits/TransientCache.phpget_transient_version() now sets the marker with:

apply_filters( 'dokan_cache_transient_version_expiration', MONTH_IN_SECONDS, $group )

The default TTL (1 month) comfortably outlives the longest-lived data transient (≤ 1 week), so cache groups are not invalidated prematurely.

How to test

  1. On a site without a persistent object cache, trigger any Dokan caching (load the vendor dashboard / a report).
  2. Inspect a marker row:
    SELECT option_name, autoload FROM wp_options
    WHERE option_name LIKE '_transient_dokan_cache_%-transient-version' LIMIT 5;
    • Before: autoload = yes, no _transient_timeout_ companion.
    • After: autoload = no (or off) with a matching _transient_timeout_… row ~30 days out.
  3. Confirm cached features (reports, product counts) still work.

Verified

Local site, forcing the DB transient path: marker stored autoload = off with a ~2592000s (30-day) timeout; cache read-back unaffected.


Ref: getdokan/plugin-internal-tasks#1965 · Companion Pro PR adds a Clear Dokan Caches admin tool.

🤖 Generated with Claude Code

Summary by CodeRabbit

Summary

  • New Features
    • Added a new Dashboard Tools page with built-in actions to regenerate required Dokan pages and clear Dokan caches.
    • Allows additional Tools sections to be injected into the page for extended functionality.
  • Chores / Improvements
    • Cache version markers now expire automatically (with configurable lifetime) to reduce stale-cache risk and improve cleanup.
  • UI Compatibility
    • Kept the legacy Tools submenu entry available alongside the new Tools page.

Cache group version markers were written via set_transient() without an
expiration, so WordPress stores them with autoload=yes and never clears
them — bloating autoloaded options on sites without a persistent object
cache (5,000+ rows reported).

Pass a filterable expiration (dokan_cache_transient_version_expiration,
default MONTH_IN_SECONDS) so WordPress stores the marker autoload=no with a
timeout and garbage-collects it. The default TTL outlives the longest-lived
data transient to avoid premature group invalidation.

Ref: getdokan/plugin-internal-tasks#1965

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a configurable expiration for transient cache version markers and adds a new Admin Tools dashboard page with functionality to manage default Dokan pages and clear caches. The Tools page is available in both React (new) and Vue (legacy) with extensible sections for Pro features. Backend logic is centralized in a ToolsActions service exposed via AJAX and REST endpoints, with permission gating and proper error handling.

Changes

Transient cache optimization

Layer / File(s) Summary
Configurable transient version expiration
includes/Traits/TransientCache.php
The get_transient_version() method adds a dokan_cache_transient_version_expiration filter (defaulting to MONTH_IN_SECONDS) and passes the resulting TTL to set_transient() so WordPress can garbage-collect the marker rather than storing it indefinitely.

Admin Tools feature

Layer / File(s) Summary
ToolsActions service and core logic
includes/Admin/Tools/ToolsActions.php
Core ToolsActions class provides create_default_pages() (creates missing Dokan pages, adopts existing ones by slug, sets rewrite rules), check_all_dokan_pages_exists() (returns flag from option), and clear_dokan_caches() (deletes transients, flushes object cache, fires action).
AJAX and REST endpoint handlers
includes/Ajax.php, includes/REST/ToolsController.php, includes/REST/Manager.php
AJAX handlers in Ajax and REST routes in ToolsController expose the three ToolsActions methods with manage_woocommerce capability checks. REST Manager registers the controller so routes are auto-loaded.
Admin dashboard page and menu integration
includes/Admin/Dashboard/Pages/Tools.php, includes/Admin/Menu.php, includes/DependencyManagement/Providers/AdminDashboardServiceProvider.php
New Tools dashboard page class with localized metadata and menu position 98, registered via DI provider. Legacy Vue tools submenu entry added to Menu alongside the new React tools item.
Asset and routing configuration
includes/Assets.php, src/admin/router/index.js, src/admin/dashboard/components/Dashboard.tsx
Vue and React admin routes registered: Vue /tools route points to Tools.vue, React /tools route renders <Tools /> in Dashboard.
Reusable ToolsSection UI component
src/components/ToolsSection.tsx, src/components/index.tsx
New ToolsSection React component renders card-row UI with optional progress bar, configurable button state (loading/disabled), and title/description; exported from components barrel for reuse.
React Tools page and section types
src/admin/dashboard/components/Tools/types.ts, src/admin/dashboard/components/Tools/HeaderImage.tsx, src/admin/dashboard/components/Tools/Tools.tsx
TypeScript types for Tools props and tool descriptors; HeaderImage component renders static SVG; Tools container defines base sections (InstallationGuide, ClearDokanCaches), applies extension filter, and renders card layout with toaster.
React Tools section implementations
src/admin/dashboard/components/Tools/sections/InstallationGuide.tsx, src/admin/dashboard/components/Tools/sections/ClearDokanCaches.tsx
InstallationGuide calls /dokan/v1/admin/tools/create-pages on click, pre-checks page existence to disable button when done; ClearDokanCaches calls /dokan/v1/admin/tools/clear-caches with loading/error toast feedback.
Vue Tools page implementation
src/admin/pages/Tools.vue
Vue component renders sections from computed list (free + filtered injected), supports built-in actions (create_pages, clear_dokan_caches) via AJAX with progress/loading UI, and forwards unknown actions to dokan_admin_tools_do_action hook.

Sequence Diagram

sequenceDiagram
  participant Admin as Admin User
  participant UI as React/Vue UI
  participant REST as REST/AJAX
  participant ToolsActions as ToolsActions Service
  participant DB as WordPress DB
  participant Cache as WP Object Cache

  Admin->>UI: Click "Create Pages" or "Clear Caches"
  UI->>REST: POST /dokan/v1/admin/tools/create-pages or clear-caches
  REST->>ToolsActions: call create_default_pages() or clear_dokan_caches()
  
  alt Create Default Pages
    ToolsActions->>DB: Query wp_posts by page slug
    DB-->>ToolsActions: Return existing pages
    ToolsActions->>DB: Insert missing pages
    ToolsActions->>DB: Update dokan_pages option
    ToolsActions->>DB: Flush rewrite rules
    DB-->>ToolsActions: Success
  else Clear Caches
    ToolsActions->>DB: DELETE FROM wp_options WHERE option_name LIKE '_transient_dokan_%'
    ToolsActions->>Cache: wp_cache_flush()
    Cache-->>ToolsActions: Cache cleared
    ToolsActions->>REST: Fire dokan_caches_cleared action
  end

  ToolsActions-->>REST: Return { success, message }
  REST-->>UI: JSON response
  UI->>Admin: Show success/error toast, update button state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • getdokan/dokan#3251: Adds Playwright E2E coverage for the new Admin Tools page, including REST endpoint interactions for create-pages and clear-caches features introduced in this PR.

Suggested labels

Needs: Dev Review

Suggested reviewers

  • mrabbani

Poem

🐰 A toolkit appears in the admin's domain,
With pages to birth and caches to drain—
React and Vue dance, Pro can extend,
Transients now expire, and bloat finds its end! 🎉

🚥 Pre-merge checks | ✅ 1 | ❌ 4

❌ Failed checks (2 warnings, 2 inconclusive)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title describes only a small transient-cache fix, but the changeset includes extensive admin tooling infrastructure (Tools page, REST controller, components, Vue integration) unrelated to the cache marker expiration. Update title to reflect the full scope, e.g., 'feat: add admin Tools page with cache management and page creation actions' or split into multiple focused PRs.
Out of Scope Changes check ⚠️ Warning The changeset includes substantial out-of-scope additions beyond issue #1965: a complete admin Tools page infrastructure (React/TSX components, Vue components, menu entries, service classes) and page creation/management actions unrelated to the transient-marker fix or cache clearing. Clarify whether admin Tools page and page-creation infrastructure are part of issue #1965 scope; if not, extract into separate PRs or update the issue to document these as explicit requirements.
Description check ❓ Inconclusive The description focuses on the transient-version marker fix and references a companion Pro PR for 'Clear Dokan Caches', but does not document the extensive React/Vue components, REST controller, admin menu changes, or new admin Tools page infrastructure also present in this changeset. Clarify whether the admin tooling is final Lite functionality or placeholder scaffolding; expand description to document all substantial changes including the new admin Tools page, components, and REST endpoints.
Linked Issues check ❓ Inconclusive The PR implements objectives 1, 3, and 4 from issue #1965 (transient-version expiration, Tools page, REST endpoints, admin UI), but does not explicitly document or complete objective 2 (cleanup/migration commands like WP CLI or SQL), and scope extends beyond the stated objectives into Lite admin tooling. Clarify whether cleanup commands are deferred to a follow-up PR; confirm that new admin tooling (Tools page, page creation actions) aligns with issue scope or belongs in a separate issue.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 85.71% 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
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/clear-dokan-cache

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.

@MdAsifHossainNadim MdAsifHossainNadim marked this pull request as ready for review June 11, 2026 12:19

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@includes/Traits/TransientCache.php`:
- Line 51: Replace the placeholder docblock tag "`@since` DOKAN_SINCE" in the
TransientCache trait with the concrete release version string (e.g., "`@since`
3.4.0" or the appropriate current release) so the method/hook documentation is
accurate; locate the docblock inside the includes/Traits/TransientCache.php file
and update the `@since` annotation accordingly for the associated method/hook.
- Around line 56-58: The filtered expiration value from
apply_filters('dokan_cache_transient_version_expiration', MONTH_IN_SECONDS,
$group) must be validated before calling set_transient to avoid non-expiring
markers; update the TransientCache logic to coerce and guard $expiration (ensure
it is a positive integer greater than zero, e.g. (int)$expiration and if <= 0
fallback to MONTH_IN_SECONDS) and then pass the validated value into
set_transient($transient_name, $transient_value, $expiration).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d2a46952-3382-4c10-b5ad-f512f99ed9f4

📥 Commits

Reviewing files that changed from the base of the PR and between 782cd09 and c282683.

📒 Files selected for processing (1)
  • includes/Traits/TransientCache.php

* core garbage-collect it. The value must outlive the longest-lived data
* transient in any group, otherwise the group is invalidated prematurely.
*
* @since DOKAN_SINCE

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Replace placeholder @since DOKAN_SINCE with a concrete version

Line 51 still uses a placeholder token in a committed docblock. Please set the actual release version for this hook before merge.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@includes/Traits/TransientCache.php` at line 51, Replace the placeholder
docblock tag "`@since` DOKAN_SINCE" in the TransientCache trait with the concrete
release version string (e.g., "`@since` 3.4.0" or the appropriate current release)
so the method/hook documentation is accurate; locate the docblock inside the
includes/Traits/TransientCache.php file and update the `@since` annotation
accordingly for the associated method/hook.

Comment on lines +56 to +58
$expiration = apply_filters( 'dokan_cache_transient_version_expiration', MONTH_IN_SECONDS, $group );

set_transient( $transient_name, $transient_value, $expiration );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard filtered expiration to prevent non-expiring markers

Line 56 accepts arbitrary filter output and passes it directly to set_transient(). If a hook returns 0/negative/non-numeric, the marker can become non-expiring again, which defeats this PR’s autoload-bloat fix.

Suggested patch
-            $expiration = apply_filters( 'dokan_cache_transient_version_expiration', MONTH_IN_SECONDS, $group );
+            $expiration = (int) apply_filters( 'dokan_cache_transient_version_expiration', MONTH_IN_SECONDS, $group );
+            $expiration = max( 1, $expiration );
 
             set_transient( $transient_name, $transient_value, $expiration );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@includes/Traits/TransientCache.php` around lines 56 - 58, The filtered
expiration value from apply_filters('dokan_cache_transient_version_expiration',
MONTH_IN_SECONDS, $group) must be validated before calling set_transient to
avoid non-expiring markers; update the TransientCache logic to coerce and guard
$expiration (ensure it is a positive integer greater than zero, e.g.
(int)$expiration and if <= 0 fallback to MONTH_IN_SECONDS) and then pass the
validated value into set_transient($transient_name, $transient_value,
$expiration).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@includes/Admin/Tools/ToolsActions.php`:
- Around line 97-99: The `update_option( 'dokan_pages_created', 1 )` call is
executed unconditionally regardless of whether the page creation operations
succeeded or failed. Add conditional logic to only set `dokan_pages_created` to
1 after verifying that all required pages were actually created successfully.
Check the return values or results from the preceding page creation operations
(before the `update_option( 'dokan_pages', $dokan_pages )` call) and only
proceed with marking `dokan_pages_created` as complete if all operations were
successful, otherwise leave the flag unset or set it to 0 to indicate incomplete
setup.
- Around line 142-154: Replace the global wp_cache_flush() call in the
ToolsActions.php file with targeted cache deletion that only removes
Dokan-specific cache keys. Instead of clearing all WordPress caches, use
wp_cache_delete() to selectively clear only the Dokan-related cache entries that
need to be invalidated. This prevents clearing unrelated site caches from other
plugins and services. Identify all critical Dokan cache keys (such as those for
products, settings, vendor data, etc.) and call wp_cache_delete() for each of
them individually.
- Around line 113-116: The method check_all_dokan_pages_exists() is returning
string values '1' and '0' instead of proper booleans, which causes the frontend
contract to break because in JavaScript '0' is truthy. Additionally, the method
only checks a stale dokan_pages_created option rather than verifying actual page
state. Modify the method to return real boolean values (true and false) instead
of strings, and update the logic to verify the actual existence of dokan pages
by querying current page records instead of only relying on the potentially
outdated get_option call.

In `@includes/Ajax.php`:
- Around line 73-80: The create_pages() method is missing nonce verification
which is required to prevent CSRF attacks. Even though the method has a
capability check with current_user_can(), nonce validation is necessary for
state-changing operations. Add nonce verification at the beginning of the
create_pages() method using wp_verify_nonce() to check for a nonce parameter in
the request, and return an error response if verification fails. Follow the
pattern used in the clear_caches() method (referenced at Lines 108-110) which
already implements nonce validation correctly.

In `@src/admin/dashboard/components/Tools/HeaderImage.tsx`:
- Line 1: The HeaderImage function parameter has implicit typing which violates
strict TypeScript mode. Define an explicit props interface or type for the
function parameters. Create a type that specifies the structure of the props
object, explicitly declaring that className is a string type with a default
value of an empty string. Apply this type annotation to the destructured
parameter in the HeaderImage function signature to ensure all component props
are strictly typed.

In `@src/admin/dashboard/components/Tools/sections/InstallationGuide.tsx`:
- Around line 45-54: The useEffect hook that calls apiFetch for the
'/dokan/v1/admin/tools/check-all-dokan-pages-exists' endpoint has a .then
handler but is missing a .catch handler, which can cause unhandled promise
rejections if the request fails. Add a .catch block after the .then to handle
rejection cases appropriately, such as logging any errors that occur during the
API call and optionally setting a fallback state to ensure the component behaves
predictably regardless of whether the request succeeds or fails.

In `@src/admin/pages/Tools.vue`:
- Around line 103-137: The `createPages()` and `checkAllPages()` methods send
AJAX requests without CSRF nonce protection, making them vulnerable to CSRF
attacks. Additionally, `createPages()` only clears the loading flag on success,
which locks the UI if the request fails. Fix this by adding nonce parameters to
both jQuery.post calls (the nonce should be available from the dokan object) and
ensure the loading flag in `createPages()` is cleared in both success and error
scenarios by using error handlers or wrapping the callback logic to guarantee
cleanup on all outcomes.

In `@src/components/ToolsSection.tsx`:
- Line 62: The className string in ToolsSection.tsx contains Tailwind CSS
important modifiers using the legacy prefix syntax (exclamation mark before the
utility class). Update all important modifiers in the className from prefix
placement (like !h-[28px]) to the Tailwind v4 recommended suffix placement
(h-[28px]!) by moving the exclamation mark to the end of each utility class.
This affects all four utility classes in the style definition: h-[28px],
font-[500], text-[12px], and leading-[16px].
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a44b1dea-fc10-4c7d-a3a0-d475e30bafd5

📥 Commits

Reviewing files that changed from the base of the PR and between c282683 and 5be325b.

📒 Files selected for processing (18)
  • includes/Admin/Dashboard/Pages/Tools.php
  • includes/Admin/Menu.php
  • includes/Admin/Tools/ToolsActions.php
  • includes/Ajax.php
  • includes/Assets.php
  • includes/DependencyManagement/Providers/AdminDashboardServiceProvider.php
  • includes/REST/Manager.php
  • includes/REST/ToolsController.php
  • src/admin/dashboard/components/Dashboard.tsx
  • src/admin/dashboard/components/Tools/HeaderImage.tsx
  • src/admin/dashboard/components/Tools/Tools.tsx
  • src/admin/dashboard/components/Tools/sections/ClearDokanCaches.tsx
  • src/admin/dashboard/components/Tools/sections/InstallationGuide.tsx
  • src/admin/dashboard/components/Tools/types.ts
  • src/admin/pages/Tools.vue
  • src/admin/router/index.js
  • src/components/ToolsSection.tsx
  • src/components/index.tsx
✅ Files skipped from review due to trivial changes (1)
  • src/components/index.tsx

Comment on lines +97 to +99
update_option( 'dokan_pages', $dokan_pages );
update_option( 'dokan_pages_created', 1 );
flush_rewrite_rules();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not mark dokan_pages_created as complete unconditionally.

Lines 97–99 set dokan_pages_created = 1 even when one or more inserts fail. That can permanently report a healthy state while required pages are still missing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@includes/Admin/Tools/ToolsActions.php` around lines 97 - 99, The
`update_option( 'dokan_pages_created', 1 )` call is executed unconditionally
regardless of whether the page creation operations succeeded or failed. Add
conditional logic to only set `dokan_pages_created` to 1 after verifying that
all required pages were actually created successfully. Check the return values
or results from the preceding page creation operations (before the
`update_option( 'dokan_pages', $dokan_pages )` call) and only proceed with
marking `dokan_pages_created` as complete if all operations were successful,
otherwise leave the flag unset or set it to 0 to indicate incomplete setup.

Comment on lines +113 to +116
public function check_all_dokan_pages_exists() {
return [
'all_pages_exists' => get_option( 'dokan_pages_created', false ) ? '1' : '0',
];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return a real boolean and derive page existence from actual page records.

At Line 115, returning '1'/'0' strings breaks the frontend contract: in JS, '0' is truthy, so src/admin/dashboard/components/Tools/sections/InstallationGuide.tsx can disable the button even when pages are missing. Also, this method only mirrors dokan_pages_created, which can be stale and does not verify current page state.

Suggested fix
 public function check_all_dokan_pages_exists() {
+    $dokan_pages = get_option( 'dokan_pages', [] );
+    $dokan_pages = is_array( $dokan_pages ) ? $dokan_pages : [];
+    $all_exists  = true;
+
+    foreach ( $this->get_default_pages() as $page ) {
+        $id = isset( $dokan_pages[ $page['page_id'] ] ) ? absint( $dokan_pages[ $page['page_id'] ] ) : 0;
+        if ( ! $id || 'page' !== get_post_type( $id ) || 'trash' === get_post_status( $id ) ) {
+            $all_exists = false;
+            break;
+        }
+    }
+
     return [
-        'all_pages_exists' => get_option( 'dokan_pages_created', false ) ? '1' : '0',
+        'all_pages_exists' => $all_exists,
     ];
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@includes/Admin/Tools/ToolsActions.php` around lines 113 - 116, The method
check_all_dokan_pages_exists() is returning string values '1' and '0' instead of
proper booleans, which causes the frontend contract to break because in
JavaScript '0' is truthy. Additionally, the method only checks a stale
dokan_pages_created option rather than verifying actual page state. Modify the
method to return real boolean values (true and false) instead of strings, and
update the logic to verify the actual existence of dokan pages by querying
current page records instead of only relying on the potentially outdated
get_option call.

Comment on lines +142 to +154
wp_cache_flush();

/**
* Fires after all Dokan caches have been cleared from the admin Tools page.
*
* @since DOKAN_SINCE
*/
do_action( 'dokan_caches_cleared' );

return [
'process' => 'success',
'message' => __( 'Dokan caches have been cleared successfully.', 'dokan-lite' ),
];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Avoid full object-cache flush for a Dokan-scoped action.

At Line 142, wp_cache_flush() clears all groups, not just Dokan keys. This can evict unrelated site caches and cause broad performance degradation after a single Tools click.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@includes/Admin/Tools/ToolsActions.php` around lines 142 - 154, Replace the
global wp_cache_flush() call in the ToolsActions.php file with targeted cache
deletion that only removes Dokan-specific cache keys. Instead of clearing all
WordPress caches, use wp_cache_delete() to selectively clear only the
Dokan-related cache entries that need to be invalidated. This prevents clearing
unrelated site caches from other plugins and services. Identify all critical
Dokan cache keys (such as those for products, settings, vendor data, etc.) and
call wp_cache_delete() for each of them individually.

Comment thread includes/Ajax.php
Comment on lines +73 to +80
public function create_pages() {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_send_json_error( __( 'You don\'t have enough permission', 'dokan-lite' ), 403 );
}

$tools = new \WeDevs\Dokan\Admin\Tools\ToolsActions();

wp_send_json_success( $tools->create_default_pages() );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add nonce verification to create_pages() to prevent CSRF.

create_pages() performs a state-changing action but lacks nonce validation (unlike clear_caches() at Lines 108–110). Capability checks alone do not block CSRF.

Suggested fix
 public function create_pages() {
+    if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'dokan_admin' ) ) {
+        wp_send_json_error( __( 'Nonce verification failed', 'dokan-lite' ), 403 );
+    }
+
     if ( ! current_user_can( 'manage_woocommerce' ) ) {
         wp_send_json_error( __( 'You don\'t have enough permission', 'dokan-lite' ), 403 );
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@includes/Ajax.php` around lines 73 - 80, The create_pages() method is missing
nonce verification which is required to prevent CSRF attacks. Even though the
method has a capability check with current_user_can(), nonce validation is
necessary for state-changing operations. Add nonce verification at the beginning
of the create_pages() method using wp_verify_nonce() to check for a nonce
parameter in the request, and return an error response if verification fails.
Follow the pattern used in the clear_caches() method (referenced at Lines
108-110) which already implements nonce validation correctly.

@@ -0,0 +1,103 @@
function HeaderImage( { className = '' } ) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Type the component props explicitly to satisfy strict TypeScript.

className is currently implicit any in the parameter destructuring.

💡 Proposed fix
+type HeaderImageProps = {
+    className?: string;
+};
+
-function HeaderImage( { className = '' } ) {
+function HeaderImage( { className = '' }: HeaderImageProps ) {

As per coding guidelines, **/*.{ts,tsx}: TypeScript must be written in strict mode with ESNext target and React-JSX.

📝 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
function HeaderImage( { className = '' } ) {
type HeaderImageProps = {
className?: string;
};
function HeaderImage( { className = '' }: HeaderImageProps ) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/admin/dashboard/components/Tools/HeaderImage.tsx` at line 1, The
HeaderImage function parameter has implicit typing which violates strict
TypeScript mode. Define an explicit props interface or type for the function
parameters. Create a type that specifies the structure of the props object,
explicitly declaring that className is a string type with a default value of an
empty string. Apply this type annotation to the destructured parameter in the
HeaderImage function signature to ensure all component props are strictly typed.

Source: Coding guidelines

Comment on lines +45 to +54
useEffect( () => {
apiFetch( {
path: '/dokan/v1/admin/tools/check-all-dokan-pages-exists',
method: 'GET',
} ).then( ( res ) => {
if ( res?.all_pages_exists ) {
setDisabled( true );
}
} );
}, [] );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle rejection in the initial pages-existence check.

The mount-time apiFetch call has no .catch, so failed requests can surface as unhandled rejections and silently skip state handling.

💡 Proposed fix
     useEffect( () => {
         apiFetch( {
             path: '/dokan/v1/admin/tools/check-all-dokan-pages-exists',
             method: 'GET',
         } ).then( ( res ) => {
             if ( res?.all_pages_exists ) {
                 setDisabled( true );
             }
-        } );
+        } ).catch( () => {
+            setDisabled( false );
+        } );
     }, [] );
📝 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
useEffect( () => {
apiFetch( {
path: '/dokan/v1/admin/tools/check-all-dokan-pages-exists',
method: 'GET',
} ).then( ( res ) => {
if ( res?.all_pages_exists ) {
setDisabled( true );
}
} );
}, [] );
useEffect( () => {
apiFetch( {
path: '/dokan/v1/admin/tools/check-all-dokan-pages-exists',
method: 'GET',
} ).then( ( res ) => {
if ( res?.all_pages_exists ) {
setDisabled( true );
}
} ).catch( () => {
setDisabled( false );
} );
}, [] );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/admin/dashboard/components/Tools/sections/InstallationGuide.tsx` around
lines 45 - 54, The useEffect hook that calls apiFetch for the
'/dokan/v1/admin/tools/check-all-dokan-pages-exists' endpoint has a .then
handler but is missing a .catch handler, which can cause unhandled promise
rejections if the request fails. Add a .catch block after the .then to handle
rejection cases appropriately, such as logging any errors that occur during the
API call and optionally setting a fallback state to ensure the component behaves
predictably regardless of whether the request succeeds or fails.

Comment thread src/admin/pages/Tools.vue
Comment on lines +103 to +137
createPages() {
let self = this;

if ( self.allPageStatus.loading ) {
return;
}

self.allPageStatus.loading = true;

jQuery.post( dokan.ajaxurl, { action: 'create_pages' }, function ( res ) {
if ( res.success ) {
self.$notify( {
title: self.__( 'Success!', 'dokan-lite' ),
text: res.data.message,
type: 'success',
} );
self.allPageStatus.exists = true;
} else {
self.$notify( {
title: self.__( 'Failure!', 'dokan-lite' ),
text: self.__( 'Something went wrong.', 'dokan-lite' ),
type: 'warn',
} );
}

self.allPageStatus.loading = false;
} );
},

checkAllPages() {
let self = this;

jQuery.post( dokan.ajaxurl, this.allPageStatus.data, function ( res ) {
self.allPageStatus.exists = res.data.all_pages_exists === '1';
} );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add nonce protection for page-install actions and handle AJAX failures consistently.

createPages()/checkAllPages() call privileged AJAX actions without a nonce, and the corresponding backend handlers shown in includes/Ajax.php also skip nonce verification. That leaves create_pages CSRF-prone. Also, createPages() only clears allPageStatus.loading on success, so network/HTTP failures can lock the button until refresh.

Suggested patch (client-side)
         createPages() {
             let self = this;

             if ( self.allPageStatus.loading ) {
                 return;
             }

             self.allPageStatus.loading = true;

-            jQuery.post( dokan.ajaxurl, { action: 'create_pages' }, function ( res ) {
+            jQuery.post( dokan.ajaxurl, { action: 'create_pages', nonce: dokan.nonce } )
+            .done( function ( res ) {
                 if ( res.success ) {
                     self.$notify( {
                         title: self.__( 'Success!', 'dokan-lite' ),
                         text: res.data.message,
                         type: 'success',
                     } );
                     self.allPageStatus.exists = true;
                 } else {
                     self.$notify( {
                         title: self.__( 'Failure!', 'dokan-lite' ),
                         text: self.__( 'Something went wrong.', 'dokan-lite' ),
                         type: 'warn',
                     } );
                 }
-
-                self.allPageStatus.loading = false;
-            } );
+            } )
+            .fail( function ( jqXHR ) {
+                self.$notify( {
+                    title: self.__( 'Failure!', 'dokan-lite' ),
+                    text: jqXHR?.responseJSON?.data || self.__( 'Something went wrong.', 'dokan-lite' ),
+                    type: 'warn',
+                } );
+            } )
+            .always( function () {
+                self.allPageStatus.loading = false;
+            } );
         },

         checkAllPages() {
             let self = this;

-            jQuery.post( dokan.ajaxurl, this.allPageStatus.data, function ( res ) {
-                self.allPageStatus.exists = res.data.all_pages_exists === '1';
-            } );
+            jQuery.post( dokan.ajaxurl, { ...this.allPageStatus.data, nonce: dokan.nonce } )
+                .done( function ( res ) {
+                    self.allPageStatus.exists = res?.data?.all_pages_exists === '1';
+                } )
+                .fail( function () {
+                    self.allPageStatus.exists = false;
+                } );
         },

Also add wp_verify_nonce(..., 'dokan_admin') in create_pages and check_all_dokan_pages_exists handlers in includes/Ajax.php to close the server-side gap.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/admin/pages/Tools.vue` around lines 103 - 137, The `createPages()` and
`checkAllPages()` methods send AJAX requests without CSRF nonce protection,
making them vulnerable to CSRF attacks. Additionally, `createPages()` only
clears the loading flag on success, which locks the UI if the request fails. Fix
this by adding nonce parameters to both jQuery.post calls (the nonce should be
available from the dokan object) and ensure the loading flag in `createPages()`
is cleared in both success and error scenarios by using error handlers or
wrapping the callback logic to guarantee cleanup on all outcomes.

loading={ loading }
onClick={ () => onClick( type ) }
label={ type?.button }
className="!h-[28px] !font-[500] !text-[12px] !leading-[16px]"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find v3-style Tailwind important prefix usage in TS/TSX/JS/JSX files
rg -nP --glob '**/*.{ts,tsx,js,jsx}' '(?<![A-Za-z0-9_-])![A-Za-z0-9_-]+\['

Repository: getdokan/dokan

Length of output: 50371


🏁 Script executed:

# Check Tailwind version and config
find . -maxdepth 3 -name "package.json" -o -name "tailwind.config.*" | head -5

Repository: getdokan/dokan

Length of output: 97


🏁 Script executed:

# Look at package.json to find Tailwind version
rg -A 2 -B 2 "tailwindcss" package.json

Repository: getdokan/dokan

Length of output: 497


🌐 Web query:

Tailwind CSS v4 important modifier syntax ! prefix vs suffix

💡 Result:

In Tailwind CSS v4, the recommended syntax for the important modifier is to use a trailing exclamation mark (!) suffix [1][2]. While Tailwind CSS v4 maintains backwards compatibility for the legacy prefix syntax (e.g., !text-blue-500) [3][4][5], the move to a suffix-based syntax (e.g., text-blue-500!) is the established convention [1][2]. This change aligns the modifier's position with native CSS, where !important is placed after the declaration, and improves clarity when combining the modifier with various variants [1][2]. Key details regarding this syntax include: - Usage: Append the ! character directly to the end of the utility class, such as flex-col! or items-start! [1]. - Backwards Compatibility: The legacy prefix syntax (placing ! before the utility) remains supported in v4 [3][4][5]. - CSS @apply: Within CSS @apply directives, the !important flag syntax used in v3 is no longer supported [1][5]. Instead, you must append the ! suffix to each individual utility class to mark it as important [1][5]. This shift to the suffix convention helps resolve ambiguity and simplifies parsing, particularly when complex variant stacks are involved [1].

Citations:


Update to Tailwind v4 important modifier suffix syntax.

In Tailwind v4, the important modifier should use suffix placement (h-[28px]!) instead of the legacy prefix (!h-[28px]). While both syntaxes remain backwards compatible, the suffix syntax is now the recommended convention.

Proposed fix
-                        className="!h-[28px] !font-[500] !text-[12px] !leading-[16px]"
+                        className="h-[28px]! font-[500]! text-[12px]! leading-[16px]!"
📝 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
className="!h-[28px] !font-[500] !text-[12px] !leading-[16px]"
className="h-[28px]! font-[500]! text-[12px]! leading-[16px]!"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ToolsSection.tsx` at line 62, The className string in
ToolsSection.tsx contains Tailwind CSS important modifiers using the legacy
prefix syntax (exclamation mark before the utility class). Update all important
modifiers in the className from prefix placement (like !h-[28px]) to the
Tailwind v4 recommended suffix placement (h-[28px]!) by moving the exclamation
mark to the end of each utility class. This affects all four utility classes in
the style definition: h-[28px], font-[500], text-[12px], and leading-[16px].

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant