Skip to content

Commit

Permalink
Feat: Add REST API endpoint for vendor-specific customer search
Browse files Browse the repository at this point in the history
This commit introduces a new REST API endpoint for searching customers
specific to a vendor's orders. Key changes include:

- Add new route `/dokan/v1/customers/search` in CustomersController
- Implement `search_customers` method with the following features:
  * Search customers by ID or term (name/email)
  * Limit results to customers who have ordered from the current vendor
  * Support excluding specific customer IDs
  * Limit results to 20 when search term is less than 3 characters
  * Apply `dokan_json_search_found_customers` filter for extensibility
- Add permission check to ensure only users with 'edit_shop_orders' capability can access
- Include detailed PHPDoc for the new filter

This feature allows vendors to efficiently search their customer base
through the REST API, mirroring the functionality of the existing AJAX action
while providing a standardized API interface.
  • Loading branch information
mralaminahamed committed Oct 21, 2024
1 parent e42b81f commit 0077f78
Show file tree
Hide file tree
Showing 2 changed files with 261 additions and 0 deletions.
260 changes: 260 additions & 0 deletions includes/REST/CustomersController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
<?php

namespace WeDevs\Dokan\REST;

use WC_Data;
use WC_Customer;
use WC_REST_Customers_Controller;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;

class CustomersController extends WC_REST_Customers_Controller {

/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'dokan/v1';

/**
* Register the routes for customers.
*/
public function register_routes() {
parent::register_routes();

// Add new route for searching customers
register_rest_route( $this->namespace, '/' . $this->rest_base . '/search', array(

Check failure on line 28 in includes/REST/CustomersController.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Opening parenthesis of a multi-line function call must be the last content on the line
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'search_customers' ),
'permission_callback' => array( $this, 'search_customers_permissions_check' ),
'args' => array(
'search' => array(
'description' => __( 'Search string.', 'dokan-lite' ),
'type' => 'string',
'required' => true,
),
'exclude' => array(
'description' => __( 'Comma-separated list of customer IDs to exclude.', 'dokan-lite' ),
'type' => 'string',
),
),
),
) );

Check failure on line 45 in includes/REST/CustomersController.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Closing parenthesis of a multi-line function call must be on a line by itself
}

/**
* Check if a given request has access to perform an action.
*
* @param WP_REST_Request $request Full details about the request.
* @param string $action The action to check (view, create, edit, delete).
*
* @return WP_Error|boolean
*/
protected function check_permission( $request, $action ) {
if ( ! $this->check_vendor_permission() ) {
$messages = [
'view' => __( 'Sorry, you cannot list resources.', 'dokan-lite' ),
'create' => __( 'Sorry, you are not allowed to create resources.', 'dokan-lite' ),
'edit' => __( 'Sorry, you are not allowed to edit this resource.', 'dokan-lite' ),
'delete' => __( 'Sorry, you are not allowed to delete this resource.', 'dokan-lite' ),
];
return new WP_Error( "dokan_rest_cannot_$action", $messages[ $action ], [ 'status' => rest_authorization_required_code() ] );
}
return true;
}

/**
* Check if the current user has vendor permissions.
*
* @return bool
*/
public function check_vendor_permission(): bool {
return dokan_is_user_seller( dokan_get_current_user_id() );
}

/**
* Get all customers.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
return $this->perform_vendor_action(
function () use ( $request ) {
return parent::get_items( $request );
}
);
}

/**
* Get a single customer.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_item( $request ) {
return $this->perform_vendor_action(
function () use ( $request ) {
return parent::get_item( $request );
}
);
}

/**
* Search customers for the current vendor.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function search_customers( $request ) {
if ( ! current_user_can( 'edit_shop_orders' ) ) {
return new WP_Error( 'dokan_rest_cannot_search', __( 'You do not have permission to search customers.', 'dokan-lite' ), [ 'status' => rest_authorization_required_code() ] );
}

$term = $request->get_param( 'search' );
$exclude = $request->get_param( 'exclude' ) ? explode( ',', $request->get_param( 'exclude' ) ) : [];
$limit = '';

if ( empty( $term ) ) {
return new WP_Error( 'dokan_rest_empty_search', __( 'Search term is required.', 'dokan-lite' ), [ 'status' => 400 ] );
}

$ids = [];
// Search by ID.
if ( is_numeric( $term ) ) {
$customer = new WC_Customer( intval( $term ) );

// Customer exists.
if ( 0 !== $customer->get_id() ) {
$ids = [ $customer->get_id() ];
}
}

// Usernames can be numeric so we first check that no users was found by ID before searching for numeric username, this prevents performance issues with ID lookups.
if ( empty( $ids ) ) {
$data_store = WC_Data_Store::load( 'customer' );

// If search is smaller than 3 characters, limit result set to avoid
// too many rows being returned.
if ( 3 > strlen( $term ) ) {
$limit = 20;
}
$ids = $data_store->search_customers( $term, $limit );
}

$found_customers = [];

$ids = array_diff( $ids, $exclude );

foreach ( $ids as $id ) {
if ( ! dokan_customer_has_order_from_this_seller( $id ) ) {
continue;
}

$customer = new WC_Customer( $id );
$found_customers[ $id ] = [
'id' => $id,
'name' => sprintf(
'%s',
$customer->get_first_name() . ' ' . $customer->get_last_name()
),
'email' => $customer->get_email(),
];
}

/**
* Filter the found customers for Dokan REST API search.
*
* This filter allows you to modify the list of customers found during a search
* before it is returned by the REST API.
*
* @since DOKAN_SINCE
*
* @param array $found_customers An array of found customers. Each customer is an array containing:
* 'id' => (int) The customer's ID.
* 'name' => (string) The customer's full name.
* 'email' => (string) The customer's email address.
* @param string $term The search term used to find customers.
* @param array $exclude An array of customer IDs to exclude from the search results.
* @param int $limit The maximum number of results to return (if any).
*
* @return array The filtered array of found customers.
*/
$found_customers = apply_filters( 'dokan_json_search_found_customers', $found_customers, $term, $exclude, $limit );

return rest_ensure_response( array_values( $found_customers ) );
}

/**
* Prepare a single customer for create or update.
*
* @param WP_REST_Request $request Request object.
* @param bool $creating If is creating a new object.
*
* @return WP_Error|WC_Data
*/
protected function prepare_object_for_database( $request, $creating = false ) {
$customer = parent::prepare_object_for_database( $request, $creating );

if ( is_wp_error( $customer ) ) {
return $customer;
}

if ( ! $customer instanceof WC_Customer ) {
return new WP_Error( 'dokan_rest_invalid_customer', __( 'Invalid customer.', 'dokan-lite' ), [ 'status' => 400 ] );
}

// Add any Dokan-specific customer preparation here

return apply_filters( "dokan_rest_pre_insert_{$this->post_type}_object", $customer, $request, $creating );
}

/**
* Perform an action with vendor permission check.
*
* @param callable $action The action to perform.
*
* @return mixed The result of the action.
*/
private function perform_vendor_action( callable $action ) {
add_filter( 'woocommerce_rest_check_permissions', [ $this, 'check_vendor_permission' ] );
$result = $action();
remove_filter( 'woocommerce_rest_check_permissions', [ $this, 'check_vendor_permission' ] );
return $result;
}

/**
* Check if a given request has access to get items.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
return $this->check_permission( $request, 'view' );
}

/**
* Check if a given request has access to get a specific item.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
return $this->check_permission( $request, 'view' );
}

/**
* Check if a given request has access to search customers.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function search_customers_permissions_check( $request ) {
return $this->check_permission( $request, 'view' );
}
}
1 change: 1 addition & 0 deletions includes/REST/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ private function get_rest_api_class_map() {
DOKAN_DIR . '/includes/REST/StoreSettingControllerV2.php' => '\WeDevs\Dokan\REST\StoreSettingControllerV2',
DOKAN_DIR . '/includes/REST/VendorDashboardController.php' => '\WeDevs\Dokan\REST\VendorDashboardController',
DOKAN_DIR . '/includes/REST/ProductBlockController.php' => '\WeDevs\Dokan\REST\ProductBlockController',
DOKAN_DIR . '/includes/REST/CustomersController.php' => '\WeDevs\Dokan\REST\CustomersController',
)
);
}
Expand Down

0 comments on commit 0077f78

Please sign in to comment.