JezK
Edit File: OrderControllerV2.php
<?php namespace WeDevs\Dokan\REST; use WC_Customer_Download; use WC_Data_Store; use WC_Product; use WeDevs\Dokan\Utilities\OrderUtil; use WP_Error; use WP_REST_Server; /** * Dokan Order ControllerV2 Class * * @since 3.7.10 * * @package dokan */ class OrderControllerV2 extends OrderController { /** * Endpoint namespace * * @since 3.7.10 * * @var string */ protected $namespace = 'dokan/v2'; /** * Register the routes for orders. * * @since 3.7.10 * * @return void */ public function register_routes() { parent::register_routes(); register_rest_route( $this->namespace, '/' . $this->base . '/(?P<id>[\d]+)/downloads', [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_order_downloads' ], 'args' => $this->get_collection_params(), 'permission_callback' => [ $this, 'get_single_order_permissions_check' ], ], [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [ $this, 'grant_order_downloads' ], 'permission_callback' => [ $this, 'get_single_order_permissions_check' ], 'args' => [ 'ids' => [ 'type' => 'array', 'description' => __( 'Download IDs.', 'dokan-lite' ), 'required' => true, 'items' => [ 'type' => 'integer', 'description' => __( 'Download product IDs.', 'dokan-lite' ), 'required' => true, ], ], 'download_remaining' => [ 'type' => 'integer', 'description' => esc_html__( 'Download remaining.', 'dokan-lite' ), 'required' => false, 'minimum' => 0, 'sanitize_callback' => 'absint', ], 'access_expires' => [ 'type' => 'string', 'description' => esc_html__( 'Access expires. Format: YYYY-MM-DD.', 'dokan-lite' ), 'required' => false, 'format' => 'date', 'sanitize_callback' => 'sanitize_text_field', ], ], ], [ 'methods' => WP_REST_Server::DELETABLE, 'callback' => [ $this, 'revoke_order_downloads' ], 'permission_callback' => [ $this, 'get_single_order_permissions_check' ], 'args' => [ 'download_id' => [ 'type' => 'string', 'description' => __( 'Download ID.', 'dokan-lite' ), 'required' => false, ], 'product_id' => [ 'type' => 'integer', 'description' => __( 'Product ID.', 'dokan-lite' ), 'required' => false, ], 'permission_id' => [ 'type' => 'integer', 'description' => __( 'Permission ID.', 'dokan-lite' ), 'required' => true, ], ], ], ] ); register_rest_route( $this->namespace, '/' . $this->base . '/(?P<id>[\d]+)/downloads/(?P<permission_id>[\d]+)', [ 'args' => [ 'id' => [ 'description' => esc_html__( 'Unique identifier for the order.', 'dokan-lite' ), 'type' => 'integer', ], 'permission_id' => [ 'description' => esc_html__( 'Unique identifier for the download permission.', 'dokan-lite' ), 'type' => 'integer', ], ], [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [ $this, 'update_order_download' ], 'permission_callback' => [ $this, 'get_single_order_permissions_check' ], 'args' => [ 'download_remaining' => [ 'type' => 'integer', 'description' => esc_html__( 'Download remaining.', 'dokan-lite' ), 'required' => false, 'minimum' => 0, 'sanitize_callback' => 'absint', ], 'access_expires' => [ 'type' => 'string', 'description' => esc_html__( 'Access expires date. Format: YYYY-MM-DD.', 'dokan-lite' ), 'required' => false, 'format' => 'date', 'sanitize_callback' => 'sanitize_text_field', ], ], ], ] ); register_rest_route( $this->namespace, '/' . $this->base . '/bulk-actions', [ [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [ $this, 'process_orders_bulk_action' ], 'args' => [ 'order_ids' => [ 'type' => 'array', 'description' => __( 'Order ids', 'dokan-lite' ), 'required' => true, 'sanitize_callback' => [ $this, 'sanitize_order_ids' ], ], 'status' => [ 'type' => 'string', 'description' => __( 'Order status', 'dokan-lite' ), 'required' => true, 'sanitize_callback' => 'sanitize_text_field', ], ], 'permission_callback' => [ $this, 'update_order_permissions_check' ], ], ] ); } /** * Get Order Downloads. * * @since 3.7.10 * * @param \WP_REST_Request $request Request object. * * @return WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function get_order_downloads( $request ) { global $wpdb; $download_permissions = $wpdb->get_results( $wpdb->prepare( " SELECT * FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE order_id = %d ORDER BY product_id ASC ", $request->get_param( 'id' ) ) ); $product_ids = array_unique( array_map( 'intval', wp_list_pluck( $download_permissions, 'product_id' ) ) ); // Batch-fetch products in a single query and create a lookup map by ID. $products = []; foreach ( wc_get_products( [ 'include' => $product_ids, 'limit' => -1 ] ) as $product ) { $products[ $product->get_id() ] = $product; } // Filter downloads with existing products and prepare response. $downloads = []; foreach ( $download_permissions as $download ) { $product_id = intval( $download->product_id ); if ( isset( $products[ $product_id ] ) ) { $download->product = $products[ $product_id ]; $downloads[] = $this->prepare_data_for_response( $download, $request ); } } $data = $this->format_downloads_data( $downloads, $products ); return rest_ensure_response( $data ); } /** * Format downloads data. * * @since 4.0.0 * * @param \stdClass[] $downloads * @param \WC_Product[] $products * * @return array */ protected function format_downloads_data( $downloads, $products ) { $data = []; $data['downloads'] = $downloads; $data['products'] = array_reduce( $products, function ( $acc, $product ) { $acc[ $product->get_id() ] = $product->get_formatted_name(); return $acc; }, [] ); return apply_filters( 'dokan_rest_prepare_format_downloads_data', $data, $downloads, $products ); } /** * Prepare data for response. * * @since 4.0.0 * * @param \stdClass $download * @param \WP_REST_Request $request * * @return \stdClass */ public function prepare_data_for_response( $download, $request ) { $product = $download->product; /** @var WC_Product $product */ unset( $download->product ); unset( $download->product_id ); $download->product = [ 'id' => $product->get_id(), 'name' => $product->get_name(), 'thumbnail' => wp_get_attachment_url( $product->get_image_id() ), ]; return apply_filters( 'dokan_rest_prepare_order_download_response', $download, $product ); } /** * Grant downloadable product access to the given order. * * @since 3.7.10 * * @param \WP_REST_Request $requests Request object. * * @return WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function grant_order_downloads( $requests ) { $order_id = intval( $requests->get_param( 'id' ) ); $product_ids = array_filter( array_map( 'absint', (array) wp_unslash( $requests->get_param( 'ids' ) ) ) ); $remaining = $requests->get_param( 'download_remaining' ); $expiry = $requests->get_param( 'access_expires' ); $file_counter = 0; $order = dokan()->order->get( $order_id ); $data = []; foreach ( $product_ids as $product_id ) { $product = dokan()->product->get( $product_id ); // Only grant downloads for the vendor's own products, never another vendor's files (admins/shop managers exempt). if ( ! $product || ( ! current_user_can( 'manage_woocommerce' ) && ! dokan_is_product_author( $product_id ) ) ) { continue; } $files = $product->get_downloads(); foreach ( $files as $download_id => $file ) { $inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order ); if ( ! $inserted_id ) { continue; } if ( null !== $remaining || null !== $expiry ) { $download = new WC_Customer_Download( $inserted_id ); if ( null !== $remaining ) { $download->set_downloads_remaining( $remaining ); } if ( null !== $expiry ) { $download->set_access_expires( $expiry ); } $download->save(); } ++$file_counter; if ( $file->get_name() ) { $file_count = $file->get_name(); } else { /* translators: numeric number of files */ $file_count = sprintf( __( 'File %d', 'dokan-lite' ), $file_counter ); } $data[ $inserted_id ] = $file_count; } } return rest_ensure_response( $data ); } /** * Update a downloadable product permission for the given order. * * @since 4.3.1 * * @param \WP_REST_Request $request Request object. * * @return WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function update_order_download( $request ) { $permission_id = absint( $request->get_param( 'permission_id' ) ); $download = new WC_Customer_Download( $permission_id ); if ( ! $download->get_id() ) { return new WP_Error( 'dokan_rest_download_permission_not_found', esc_html__( 'Download permission not found.', 'dokan-lite' ), [ 'status' => 404 ] ); } // Verify the permission belongs to this order. $order_id = absint( $request->get_param( 'id' ) ); if ( $download->get_order_id() !== $order_id ) { return new WP_Error( 'dokan_rest_download_permission_invalid_order', esc_html__( 'Download permission does not belong to this order.', 'dokan-lite' ), [ 'status' => 400 ] ); } $download_remaining = $request->get_param( 'download_remaining' ); $access_expires = $request->get_param( 'access_expires' ); if ( null !== $download_remaining ) { $download->set_downloads_remaining( $download_remaining ); } if ( null !== $access_expires ) { $download->set_access_expires( $access_expires ); } if ( null !== $download_remaining || null !== $access_expires ) { $download->save(); } $expires = $download->get_access_expires(); $response = [ 'permission_id' => $download->get_id(), 'product_id' => $download->get_product_id(), 'download_id' => $download->get_download_id(), 'order_id' => $download->get_order_id(), 'download_remaining' => $download->get_downloads_remaining(), 'access_expires' => $expires ? $expires->date( 'Y-m-d' ) : null, ]; return rest_ensure_response( apply_filters( 'dokan_rest_prepare_order_download_update_response', $response, $download, $request ) ); } /** * Revoke downloadable product access to the given order. * * @since 3.7.10 * * @param \WP_REST_Request $requests Request object. * * @return WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function revoke_order_downloads( $requests ) { $download_id = $requests->get_param( 'download_id' ); $product_id = $requests->get_param( 'product_id' ); $order_id = absint( $requests->get_param( 'id' ) ); $permission_id = absint( $requests->get_param( 'permission_id' ) ); // Only this order's own permission may be revoked; a foreign or unknown id is rejected (WC throws on an unknown id, caught here). try { $download = new WC_Customer_Download( $permission_id ); $belongs_to_order = $download->get_id() && $download->get_order_id() === $order_id; } catch ( \Exception $e ) { $belongs_to_order = false; } if ( ! $belongs_to_order ) { return new WP_Error( 'dokan_rest_download_permission_invalid_order', esc_html__( 'Download permission does not belong to this order.', 'dokan-lite' ), [ 'status' => 400 ] ); } try { $data_store = WC_Data_Store::load( 'customer-download' ); $data_store->delete_by_id( $permission_id ); } catch ( \Exception $e ) { return new WP_Error( 'dokan_rest_cannot_delete', $e->getMessage(), [ 'status' => 500 ] ); } do_action( 'woocommerce_ajax_revoke_access_to_product_download', $download_id, $product_id, $order_id, $permission_id ); return rest_ensure_response( array( 'success' => true ) ); } /** * Updates bulk orders status. * * @since 3.7.10 * * @param \WP_REST_Request $requests Request object. * * @return WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function process_orders_bulk_action( $requests ) { // A vendor may only bulk-update their own orders; admins/shop managers are exempt. $order_ids = array_filter( (array) $requests->get_param( 'order_ids' ), [ OrderUtil::class, 'current_user_can_manage_order' ] ); $data = [ 'bulk_orders' => $order_ids, 'status' => $requests->get_param( 'status' ), ]; dokan_apply_bulk_order_status_change( $data ); return rest_ensure_response( array( 'success' => true ) ); } /** * Sanitizes order ids. * * @since 3.7.10 * * @param array $order_ids * * @return array */ public function sanitize_order_ids( $order_ids ) { if ( is_array( $order_ids ) ) { return array_map( 'absint', $order_ids ); } else { return []; } } }