com um clique
dokan-backend-dev
// Add or modify Dokan backend PHP code following project conventions. Use when creating new classes, methods, hooks, REST controllers, or modifying existing backend code. Invoke before writing PHP unit tests.
// Add or modify Dokan backend PHP code following project conventions. Use when creating new classes, methods, hooks, REST controllers, or modifying existing backend code. Invoke before writing PHP unit tests.
Build, scaffold, and run the Dokan Lite/Pro Playwright suite. Use when the user asks to add tests for a feature, scaffold from test-cases.md, or run the automation suite (Lite Only / PR / Full). Knows the folderized format, tag system, Docker / wp-env preconditions, and the .env / license-key requirements for Pro runs.
Execute the Dokan Playwright test suite (E2E + API), locally or via GitHub Actions. Invoke when the user asks to run, kick off, trigger, re-run, debug, or inspect the automated test runs. Phrases such as "run the suite", "run e2e tests", "trigger CI", "execute the QA suite", or "check the failed run" should activate this skill.
Build new vendor dashboard DataViews list pages from scratch OR migrate legacy Filter/StatusFilter/DataViewTable components to the unified @wedevs/plugin-ui DataViews component. Covers fresh builds (types, hook, list, route, PHP nav) and legacy migration (Scenario A status tabs, Scenario B multi-list merge).
Review Dokan code changes and pull requests for coding standards, security, and architectural compliance. Use when reviewing PRs, performing code audits, or checking code quality.
Run tests, linting, and quality checks for Dokan development. Use when running tests, fixing code style, building assets, or following the development workflow.
Add or modify Dokan frontend code (React, TypeScript, Vue, Tailwind). Use when creating components, hooks, stores, or modifying webpack configuration.
| name | dokan-backend-dev |
| description | Add or modify Dokan backend PHP code following project conventions. Use when creating new classes, methods, hooks, REST controllers, or modifying existing backend code. Invoke before writing PHP unit tests. |
This skill provides guidance for developing Dokan Lite backend PHP code according to project standards.
Invoke this skill before:
WeDevs\Dokan\WeDevs\Dokan\ maps to includes/WeDevs\Dokan\Order\Manager → includes/Order/Manager.phpWeDevs\Dokan\ThirdParty\Packages\ → lib/packages/snake_case (WordPress convention) — e.g., register_routes(), get_stores()protected bool $should_adjust_refund = true;UPPER_SNAKE_CASEMost subsystems use a Manager class as the primary facade:
namespace WeDevs\Dokan\Order;
class Manager {
public function all( $args = [] ) { ... }
public function get( $id ) { ... }
public function create( $args ) { ... }
}
Access via: dokan()->order->all()
Classes that register WordPress hooks should implement Hookable:
namespace WeDevs\Dokan\Product;
use WeDevs\Dokan\Contracts\Hookable;
class Hooks implements Hookable {
public function register_hooks(): void {
add_action( 'save_post_product', [ $this, 'handle_product_save' ], 10, 2 );
add_filter( 'dokan_product_listing_args', [ $this, 'filter_listing_args' ] );
}
}
Classes implementing Hookable are auto-registered in CommonServiceProvider — their hooks load automatically.
Uses League Container v4 (namespaced under WeDevs\Dokan\ThirdParty\Packages\League\Container).
Add to the appropriate ServiceProvider in includes/DependencyManagement/Providers/:
// In ServiceProvider.php (main) for core services:
protected $services = [
'my_service' => \WeDevs\Dokan\MyDomain\Manager::class,
];
// In CommonServiceProvider.php for Hookable classes:
protected $services = [
\WeDevs\Dokan\MyDomain\Hooks::class,
];
// Via magic getter (most common)
dokan()->order->get( $order_id );
dokan()->vendor->get( $vendor_id );
// Via container directly
dokan()->get_container()->get( 'order' );
Use share_with_implements_tags() to auto-tag services by their interfaces:
$this->share_with_implements_tags( MyService::class );
WP_REST_Controller (WordPress core)
└── DokanBaseController (dokan/v1)
├── DokanBaseAdminController (dokan/v1/admin) — admin-only endpoints
├── DokanBaseVendorController (dokan/v1) — vendor endpoints (uses VendorAuthorizable trait)
└── DokanBaseCustomerController (dokan/v1) — customer endpoints
Choose the appropriate base class:
DokanBaseAdminController — For admin-only endpoints (dokan/v1/admin/*). Has built-in check_permission() checking manage_woocommerce capability.DokanBaseVendorController — For vendor endpoints. Includes VendorAuthorizable trait for store access checks.DokanBaseController — For general endpoints that don't fit the above.Note: Some older controllers extend
WP_REST_Controllerdirectly (e.g.,StoreController,WithdrawController). New controllers should extend one of the Dokan base classes.
namespace WeDevs\Dokan\REST;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
use WeDevs\Dokan\Traits\RESTResponseError;
class MyResourceController extends DokanBaseAdminController {
use RESTResponseError;
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'my-resource';
/**
* Register routes.
*
* @return void
*/
public function register_routes() {
register_rest_route(
$this->namespace, '/' . $this->rest_base, [
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_items' ],
'args' => array_merge(
$this->get_collection_params(),
[
'status' => [
'description' => __( 'Filter by status.', 'dokan-lite' ),
'type' => 'string',
'enum' => [ 'active', 'inactive' ],
'default' => 'active',
],
]
),
'permission_callback' => [ $this, 'check_permission' ],
],
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'create_item' ],
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
'permission_callback' => [ $this, 'check_permission' ],
],
'schema' => [ $this, 'get_item_schema' ],
]
);
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', [
'args' => [
'id' => [
'description' => __( 'Unique identifier for the object.', 'dokan-lite' ),
'type' => 'integer',
],
],
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_item' ],
'permission_callback' => [ $this, 'check_permission' ],
],
[
'methods' => WP_REST_Server::EDITABLE,
'callback' => [ $this, 'update_item' ],
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
'permission_callback' => [ $this, 'check_permission' ],
],
[
'methods' => WP_REST_Server::DELETABLE,
'callback' => [ $this, 'delete_item' ],
'permission_callback' => [ $this, 'check_permission' ],
],
]
);
// Batch endpoint
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/batch', [
[
'methods' => WP_REST_Server::EDITABLE,
'callback' => [ $this, 'batch_items' ],
'permission_callback' => [ $this, 'check_permission' ],
'args' => $this->get_public_batch_schema()['properties'],
],
'schema' => [ $this, 'get_public_batch_schema' ],
]
);
}
}
Every controller must implement prepare_item_for_response(). This method transforms the internal data model into the REST API response shape, adds HATEOAS links, and applies an extensibility filter:
/**
* Prepare a single item for response.
*
* @param MyModel $item Data object.
* @param WP_REST_Request $request Request object.
*
* @return WP_REST_Response
*/
public function prepare_item_for_response( $item, $request ) {
$data = [
'id' => absint( $item->get_id() ),
'title' => $item->get_title(),
'status' => $item->get_status(),
'amount' => floatval( $item->get_amount() ),
'created' => mysql_to_rfc3339( $item->get_date() ),
];
$data = apply_filters( 'dokan_rest_prepare_my_resource_data', $data, $item, $request );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $item, $request ) );
return apply_filters( 'dokan_rest_prepare_my_resource_object', $response, $item, $request );
}
Provide self and collection links for discoverability:
/**
* Prepare links for the request.
*
* @param MyModel $item Object data.
* @param WP_REST_Request $request Request object.
*
* @return array Links for the given item.
*/
protected function prepare_links( $item, $request ) {
return [
'self' => [
'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $item->get_id() ) ),
],
'collection' => [
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
],
];
}
Use format_collection_response() (inherited from DokanBaseController) to add pagination headers:
public function get_items( $request ) {
$args = [
'number' => (int) $request['per_page'],
'offset' => (int) ( $request['page'] - 1 ) * $request['per_page'],
];
$items = $this->get_my_items( $args );
$total_items = $this->get_my_items_count( $args );
$data = [];
foreach ( $items as $item ) {
$item_data = $this->prepare_item_for_response( $item, $request );
$data[] = $this->prepare_response_for_collection( $item_data );
}
$response = rest_ensure_response( $data );
$response = $this->format_collection_response( $response, $request, $total_items );
return $response;
}
format_collection_response() sets these headers automatically:
X-WP-Total — Total item countX-WP-TotalPages — Total page countLink: <url>; rel="prev" / Link: <url>; rel="next" — Pagination linksDefine get_item_schema() to enable automatic argument validation via get_endpoint_args_for_item_schema():
public function get_item_schema() {
$schema = [
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'my-resource',
'type' => 'object',
'properties' => [
'id' => [
'description' => __( 'Unique identifier for the object.', 'dokan-lite' ),
'type' => 'integer',
'context' => [ 'view', 'edit' ],
'readonly' => true,
],
'title' => [
'description' => __( 'Resource title.', 'dokan-lite' ),
'type' => 'string',
'context' => [ 'view', 'edit' ],
'required' => true,
],
'status' => [
'description' => __( 'Resource status.', 'dokan-lite' ),
'type' => 'string',
'enum' => [ 'active', 'inactive' ],
'context' => [ 'view', 'edit' ],
'default' => 'active',
],
'amount' => [
'description' => __( 'Amount value.', 'dokan-lite' ),
'type' => 'number',
'context' => [ 'view', 'edit' ],
'required' => true,
],
],
];
return $this->add_additional_fields_schema( $schema );
}
Use WP_Error with descriptive error codes and HTTP status:
return new WP_Error(
'dokan_rest_resource_not_found',
__( 'Resource not found.', 'dokan-lite' ),
[ 'status' => 404 ]
);
For exception-based error handling, use the RESTResponseError trait:
use WeDevs\Dokan\Traits\RESTResponseError;
class MyController extends DokanBaseController {
use RESTResponseError;
public function create_item( $request ) {
try {
// ... business logic that may throw DokanException
} catch ( Exception $e ) {
return $this->send_response_error( $e );
}
}
}
Admin controllers inherit check_permission() from DokanBaseAdminController.
Vendor controllers use VendorAuthorizable trait methods:
// Check if current user can access a vendor's store
$this->can_access_vendor_store( $store_id );
// Resolve vendor ID (handles vendor staff → vendor mapping)
$store_id = $this->get_vendor_id_for_user( $requested_id );
Custom permission checks — use WordPress capabilities:
public function get_items_permissions_check( $request ) {
return current_user_can( 'dokan_manage_withdraw' );
}
Define args inline with type, enum, required, default, sanitize_callback, validate_callback:
'args' => [
'id' => [
'description' => __( 'Unique identifier for the object.', 'dokan-lite' ),
'type' => 'integer',
'sanitize_callback' => 'absint',
'validate_callback' => [ $this, 'validate_resource_id' ],
],
],
Controllers should apply filters at key extension points:
// Filter collection query args
$args = apply_filters( 'dokan_rest_get_my_resource_args', $args, $request );
// Filter collection endpoint params
'args' => apply_filters( 'dokan_rest_api_my_resource_collection_params', $this->get_collection_params() ),
// Filter prepared response
return apply_filters( 'dokan_rest_prepare_my_resource_object', $response, $item, $request );
// Action after create/update
do_action( 'dokan_rest_insert_my_resource', $item, $request, $creating );
Add via the dokan_rest_api_class_map filter in REST\Manager:
add_filter( 'dokan_rest_api_class_map', function ( $class_map ) {
$class_map[ DOKAN_DIR . '/includes/REST/MyResourceController.php' ] = '\WeDevs\Dokan\REST\MyResourceController';
return $class_map;
} );
Multiple versions exist side-by-side: OrderController.php (v1), OrderControllerV2.php, OrderControllerV3.php. Namespace changes accordingly (dokan/v1, dokan/v2, dokan/v3).
includes/REST/DokanBaseController.php — Base controller with format_collection_response() paginationincludes/REST/DokanBaseAdminController.php — Admin base (dokan/v1/admin, check_permission())includes/REST/DokanBaseVendorController.php — Vendor base (VendorAuthorizable trait)includes/REST/DokanBaseCustomerController.php — Customer baseincludes/REST/Manager.php — Controller registration & dokan_rest_api_class_map filterincludes/Traits/RESTResponseError.php — Exception-to-WP_Error traitincludes/Traits/VendorAuthorizable.php — Vendor store access authorization$value = apply_filters( 'dokan_get_vendor_orders', $orders, $args );
$params = apply_filters( 'dokan_rest_api_store_collection_params', $this->get_store_collection_params() );
do_action( 'dokan_rest_insert_product_object', $product, $request, true );
do_action( 'dokan_new_seller_created', $vendor_id, $data );
$value = dokan_get_option( 'key', 'dokan_option_group', 'default' );
Text domain: dokan-lite — used for ALL translatable strings in Lite.
| Function | Usage |
|---|---|
__( 'Text', 'dokan-lite' ) | Return translated string |
_e( 'Text', 'dokan-lite' ) | Echo translated string |
esc_html__( 'Text', 'dokan-lite' ) | Return translated + HTML-escaped |
esc_html_e( 'Text', 'dokan-lite' ) | Echo translated + HTML-escaped |
esc_attr__( 'Text', 'dokan-lite' ) | Return translated + attribute-escaped |
esc_attr_e( 'Text', 'dokan-lite' ) | Echo translated + attribute-escaped |
_n( 'single', 'plural', $count, 'dokan-lite' ) | Pluralization |
_x( 'Text', 'context', 'dokan-lite' ) | Context-aware translation |
_nx( 'single', 'plural', $count, 'context', 'dokan-lite' ) | Context-aware pluralization |
Always add /* translators: */ comments before sprintf() with placeholders:
/* translators: 1: Required PHP version 2: Running PHP version */
__( 'Minimum PHP version required is %1$s. You are running %2$s.', 'dokan-lite' )
Use sprintf() for dynamic content — never concatenate translated strings:
// CORRECT
sprintf( __( 'Account Name: %s', 'dokan-lite' ), $payment['ac_name'] )
// WRONG — don't concatenate
__( 'Account Name: ', 'dokan-lite' ) . $payment['ac_name']
sprintf(
_n( '%d vendor approved.', '%d vendors approved.', $count, 'dokan-lite' ),
$count
)
Always use locale-aware functions:
// WordPress locale-aware date
date_i18n( wc_date_format(), strtotime( $date_string ) );
// Translated day names
dokan_get_translated_days( 'monday' );
In templates, always escape translated output:
// In template files
<?php esc_html_e( 'Payment Methods', 'dokan-lite' ); ?>
<input placeholder="<?php esc_attr_e( 'Search...', 'dokan-lite' ); ?>">
npm run makepot # Generates languages/dokan-lite.pot
Handled in dokan-class.php via load_plugin_textdomain() on woocommerce_loaded hook. No manual setup needed.
WordPress-Extra + WordPress (via phpcs.xml.dist)in_array() strict mode: requireddokan-lite for all __(), _e(), esc_html__(), etc.wc_clean, wc_esc_json, dokan_sanitize_phone_number are registereddokan.php — Main plugin file, container bootstrapdokan-class.php — WeDevs_Dokan singletonincludes/DependencyManagement/Providers/ServiceProvider.php — Main service registrationincludes/DependencyManagement/Providers/CommonServiceProvider.php — Hookable class registrationincludes/REST/DokanBaseController.php — Base REST controllerincludes/REST/Manager.php — REST API management & controller mapincludes/Contracts/Hookable.php — Hookable interfaceincludes/functions.php — Core utility functions (3,290 lines)