Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ phpcs-report.txt
!/assets/css/vue-vendor.css
!/assets/css/vue-frontend.css
!/assets/css/wp-version-before-5-3.css
.vscode
191 changes: 191 additions & 0 deletions includes/REST/ProductController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace WeDevs\Dokan\REST;

use WC_Customer_Download;
use WC_Data;
use WC_Data_Store;
use WC_Product;
use WC_Product_Variation;
use WP_Error;
Expand Down Expand Up @@ -316,6 +318,73 @@ public function register_routes() {
],
]
);

register_rest_route(
$this->namespace, '/' . $this->base . '/grant-downloadable-access', [
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'grant_downloadable_access' ],
'permission_callback' => [ $this, 'get_product_permissions_check' ],
'args' => [
'order_id' => [
'description' => __( 'Order ID', 'dokan-lite' ),
'type' => 'integer',
'required' => true,
],
'product_ids' => [
'description' => __( 'Product IDs', 'dokan-lite' ),
'type' => 'array',
'items' => [
'type' => 'integer',
],
'required' => true,
],
'download_remaining' => [
'description' => __( 'Download Remaining', 'dokan-lite' ),
'type' => 'integer',
'required' => false,
],
'access_expires' => [
'description' => __( 'Access Expires', 'dokan-lite' ),
'type' => 'date',
'required' => false,
],
Copy link
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

🌐 Web query:

WordPress REST API register_rest_route argument schema type date datetime supported values

💡 Result:

In register_rest_route( ... 'args' => ... ), date/datetime are not valid JSON Schema type values in WordPress. For a date/time argument you should use:

'my_date' => [
  'type'   => 'string',
  'format' => 'date-time', // RFC3339
]

Supported type values (WP REST arg schema) are: array, object, string, number, integer, boolean, null. [3]

Supported string format values in WordPress are: date-time (RFC3339), uri, email, ip, uuid, hex-color. [1]


🏁 Script executed:

# First, let's locate and examine the file
find . -name "ProductController.php" -type f

Repository: getdokan/dokan

Length of output: 95


🏁 Script executed:

# Read the ProductController.php file around lines 323-351
cat -n includes/REST/ProductController.php | sed -n '320,355p'

Repository: getdokan/dokan

Length of output: 1980


Fix REST argument type for access_expires.

Line 349 uses 'type' => 'date', which is not a supported WordPress REST API argument type. Use 'type' => 'string' with 'format' => 'date-time' instead. Valid REST arg types are: array, object, string, number, integer, boolean, null. The current type will cause REST schema validation failures.

♻️ Proposed fix
 'access_expires' => [
     'description' => __( 'Access Expires', 'dokan-lite' ),
-    'type'        => 'date',
+    'type'        => 'string',
+    'format'      => 'date-time',
     'required'    => 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
$this->namespace, '/' . $this->base . '/grant-downloadable-access', [
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'grant_downloadable_access' ],
'permission_callback' => [ $this, 'get_product_permissions_check' ],
'args' => [
'order_id' => [
'description' => __( 'Order ID', 'dokan-lite' ),
'type' => 'integer',
'required' => true,
],
'product_ids' => [
'description' => __( 'Product IDs', 'dokan-lite' ),
'type' => 'array',
'items' => [
'type' => 'integer',
],
'required' => true,
],
'download_remaining' => [
'description' => __( 'Download Remaining', 'dokan-lite' ),
'type' => 'integer',
'required' => false,
],
'access_expires' => [
'description' => __( 'Access Expires', 'dokan-lite' ),
'type' => 'date',
'required' => false,
],
$this->namespace, '/' . $this->base . '/grant-downloadable-access', [
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'grant_downloadable_access' ],
'permission_callback' => [ $this, 'get_product_permissions_check' ],
'args' => [
'order_id' => [
'description' => __( 'Order ID', 'dokan-lite' ),
'type' => 'integer',
'required' => true,
],
'product_ids' => [
'description' => __( 'Product IDs', 'dokan-lite' ),
'type' => 'array',
'items' => [
'type' => 'integer',
],
'required' => true,
],
'download_remaining' => [
'description' => __( 'Download Remaining', 'dokan-lite' ),
'type' => 'integer',
'required' => false,
],
'access_expires' => [
'description' => __( 'Access Expires', 'dokan-lite' ),
'type' => 'string',
'format' => 'date-time',
'required' => false,
],
🤖 Prompt for AI Agents
In `@includes/REST/ProductController.php` around lines 323 - 351, The REST arg
definition for access_expires is using an unsupported type 'date'; update the
'access_expires' entry in the register_route args (the array passed to
register_rest_route in ProductController) to use 'type' => 'string' and add
'format' => 'date-time' (and optionally keep 'required' => false) so it conforms
to WP REST API valid types and date-time formatting rules; locate the
'access_expires' key near the grant-downloadable-access route callback
(grant_downloadable_access) and replace the 'date' type accordingly.

],
],
]
);

register_rest_route(
$this->namespace, '/' . $this->base . '/revoke-downloadable-access', [
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'revoke_access_to_download' ],
'permission_callback' => [ $this, 'get_product_permissions_check' ],
'args' => [
'download_id' => [
'description' => __( 'Download ID', 'dokan-lite' ),
'type' => 'string',
'required' => true,
],
'product_id' => [
'description' => __( 'Product ID', 'dokan-lite' ),
'type' => 'integer',
'required' => true,
],
'order_id' => [
'description' => __( 'Order ID', 'dokan-lite' ),
'type' => 'integer',
'required' => true,
],
'permission_id' => [
'description' => __( 'Permission ID', 'dokan-lite' ),
'type' => 'integer',
'required' => true,
],
],
],
]
);
}

/**
Expand Down Expand Up @@ -1739,6 +1808,128 @@ public function get_multistep_categories() {
return rest_ensure_response( $categories );
}

public function grant_downloadable_access( $request ) {
$order_id = $request->get_param( 'order_id' );
$product_ids = $request->get_param( 'product_ids' );
$remaining = $request->get_param( 'download_remaining' );
$expiry = $request->get_param( 'access_expires' );

if ( ! is_array( $product_ids ) ) {
$product_ids = [ $product_ids ];
}

$order = dokan()->order->get( $order_id );
$granted_files = [];

foreach ( $product_ids as $product_id ) {
$product = wc_get_product( $product_id );
if ( ! $product ) {
continue;
}
$files = $product->get_downloads();

if ( ! $order->get_billing_email() ) {
continue;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate order existence and vendor/order ownership before granting access.

dokan()->order->get() can return null/invalid; calling $order->get_billing_email() would fatal. Also, without verifying that the order (or sub‑order) and product belong to the current vendor, a seller could grant access for other vendors’ orders/products.

🐛 Minimal guard for missing/invalid order
-    $order         = dokan()->order->get( $order_id );
+    $order         = dokan()->order->get( $order_id );
+    if ( ! $order || ! $order->get_id() ) {
+        return new WP_Error(
+            'dokan_rest_invalid_order',
+            __( 'Invalid order ID.', 'dokan-lite' ),
+            [ 'status' => 404 ]
+        );
+    }

Please also add ownership checks (e.g., ensure the order/sub‑order and each product belong to the current vendor before granting).

🤖 Prompt for AI Agents
In `@includes/REST/ProductController.php` around lines 1811 - 1834, The
grant_downloadable_access handler must first ensure the order returned by
dokan()->order->get($order_id) is a valid object before calling
$order->get_billing_email(), and must verify ownership so a vendor cannot grant
access for other vendors' orders/products. Fix by: validate $order is not
null/false and return a proper REST error if missing; fetch the current vendor
ID (e.g. dokan_get_current_user_id()) and check that the order (or sub‑order)
belongs to that vendor and that each $product (from $product->get_id() /
$product post author) belongs to the same vendor before proceeding to build
$granted_files; if any ownership check fails, skip that product or return a
permission error. Implement these checks in grant_downloadable_access around the
existing dokan()->order->get(...) and product loop.

if ( $files ) {
foreach ( $files as $download_id => $file ) {
$data_store = WC_Data_Store::load( 'customer-download' );
$existing_permissions = $data_store->get_downloads(
[
'order_id' => $order->get_id(),
'product_id' => $product_id,
'download_id' => $download_id,
'limit' => 1,
]
);

$download = null;
$inserted_id = 0;

if ( ! empty( $existing_permissions ) ) {
$download = reset( $existing_permissions );
$inserted_id = $download->get_id();
} else {
$inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order );
if ( $inserted_id ) {
$download = new WC_Customer_Download( $inserted_id );
}
}

if ( $download ) {
if ( $remaining ) {
$download->set_downloads_remaining( $remaining );
}
if ( $expiry ) {
$download->set_access_expires( $expiry );
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Allow download_remaining = 0 (and explicit expiry updates).

Using truthy checks prevents setting zero downloads remaining or explicitly clearing/updating expiry. Use null checks instead.

🐛 Proposed fix
-    if ( $remaining ) {
+    if ( null !== $remaining ) {
         $download->set_downloads_remaining( $remaining );
     }
-    if ( $expiry ) {
+    if ( null !== $expiry && '' !== $expiry ) {
         $download->set_access_expires( $expiry );
     }
🤖 Prompt for AI Agents
In `@includes/REST/ProductController.php` around lines 1860 - 1866, The current
truthy checks prevent setting downloads remaining to 0 or explicitly clearing
expiry; modify the conditional tests around $remaining and $expiry in the
ProductController download update block so they check for null (e.g. $remaining
!== null and $expiry !== null) before calling
$download->set_downloads_remaining(...) and $download->set_access_expires(...),
ensuring zero values and explicit expiry updates are applied while leaving other
values untouched.

$download->save();
$granted_files[] = [
'name' => $file->get_name(),
'file' => $file->get_file(),
'download_id' => $download->get_download_id(),
'permission_id' => $download->get_id(),
'download_name' => $file->get_name(),
'order_id' => $download->get_order_id(),
'order_key' => $download->get_order_key(),
'remaining' => $download->get_downloads_remaining(),
'access_expires' => $download->get_access_expires() ? wc_rest_prepare_date_response( $download->get_access_expires() ) : null,
];
}
}
}
}

return rest_ensure_response(
[
'success' => true,
'message' => __( 'Downloadable access granted successfully.', 'dokan-lite' ),
'files' => $granted_files,
]
);
}

/**
* Revoke file download access for customer.
*
* @since 4.0.0
*
* @param WP_REST_Request $request Request object.
*
* @return WP_REST_Response|WP_Error
*/
public function revoke_access_to_download( $request ) {
$download_id = $request->get_param( 'download_id' );
$product_id = $request->get_param( 'product_id' );
$order_id = $request->get_param( 'order_id' );
$permission_id = $request->get_param( 'permission_id' );

if ( empty( $permission_id ) ) {
return new WP_Error(
'dokan_rest_missing_permission_id',
__( 'Permission ID is required.', 'dokan-lite' ),
[ 'status' => 400 ]
);
}

$data_store = WC_Data_Store::load( 'customer-download' );
$data_store->delete_by_id( absint( $permission_id ) );

do_action( 'woocommerce_ajax_revoke_access_to_product_download', $download_id, $product_id, $order_id, $permission_id );

return rest_ensure_response(
[
'success' => true,
'message' => __( 'Download access revoked successfully.', 'dokan-lite' ),
'download_id' => $download_id,
'product_id' => $product_id,
'order_id' => $order_id,
'permission_id' => $permission_id,
]
);
}

/**
* Get the Product's schema, conforming to JSON Schema.
*
Expand Down
Loading