From e81bd6078476c20390d50ab3bb695401b3544a75 Mon Sep 17 00:00:00 2001 From: osman sufy Date: Wed, 29 Jan 2025 17:52:23 +0600 Subject: [PATCH 1/4] update: product categories api --- includes/REST/Manager.php | 2 +- .../ProductCategoriesVendorController.php | 199 ----------- .../VendorProductCategoriesController.php | 333 ++++++++++++++++++ 3 files changed, 334 insertions(+), 200 deletions(-) delete mode 100644 includes/REST/ProductCategoriesVendorController.php create mode 100644 includes/REST/VendorProductCategoriesController.php diff --git a/includes/REST/Manager.php b/includes/REST/Manager.php index ac239a288b..8f62e500b7 100644 --- a/includes/REST/Manager.php +++ b/includes/REST/Manager.php @@ -201,7 +201,7 @@ private function get_rest_api_class_map() { DOKAN_DIR . '/includes/REST/VendorDashboardController.php' => '\WeDevs\Dokan\REST\VendorDashboardController', DOKAN_DIR . '/includes/REST/ProductBlockController.php' => '\WeDevs\Dokan\REST\ProductBlockController', DOKAN_DIR . '/includes/REST/CommissionControllerV1.php' => '\WeDevs\Dokan\REST\CommissionControllerV1', - DOKAN_DIR . '/includes/REST/ProductCategoriesVendorController.php' => '\WeDevs\Dokan\REST\ProductCategoriesVendorController', + DOKAN_DIR . '/includes/REST/VendorProductCategoriesController.php' => '\WeDevs\Dokan\REST\VendorProductCategoriesController', ) ); } diff --git a/includes/REST/ProductCategoriesVendorController.php b/includes/REST/ProductCategoriesVendorController.php deleted file mode 100644 index 202ffddaf9..0000000000 --- a/includes/REST/ProductCategoriesVendorController.php +++ /dev/null @@ -1,199 +0,0 @@ -namespace, '/' . $this->rest_base, [ - [ - 'methods' => 'GET', - 'callback' => array( $this, 'get_product_categories' ), - 'permission_callback' => [ $this, 'check_permission' ], - 'args' => $this->get_collection_params(), - - ], - ] - ); - } - - /** - * Get the product categories - * - * @param WP_REST_Request $request Full details about the request. - * @return \WP_REST_Response | \WP_Error Response object on success, or WP_Error object on failure. - */ - public function get_product_categories( $request ) { - // Get parameters from request - $per_page = $request->get_param( 'per_page' ) ? $request->get_param( 'per_page' ) : 10; - $page = $request->get_param( 'page' ) ? $request->get_param( 'page' ) : 1; - $search = $request->get_param( 'search' ); - $exclude = $request->get_param( 'exclude' ); - $include = $request->get_param( 'include' ); - $order = $request->get_param( 'order' ); - $orderby = $request->get_param( 'orderby' ); - $hide_empty = $request->get_param( 'hide_empty' ); - $parent = $request->get_param( 'parent' ); - $fields = $request->get_param( '_fields' ); - // Set up query arguments - $args = array( - 'taxonomy' => 'product_cat', - 'number' => $per_page, - 'offset' => ( $page - 1 ) * $per_page, - 'hide_empty' => $hide_empty === 'true', - 'orderby' => $orderby ? $orderby : 'name', - 'order' => $order ? $order : 'ASC', - ); - - // Add conditional parameters - if ( $search ) { - $args['search'] = $search; - } - if ( $exclude ) { - $args['exclude'] = array_map( 'absint', explode( ',', $exclude ) ); - } - if ( $include ) { - $args['include'] = array_map( 'absint', explode( ',', $include ) ); - } - if ( $parent ) { - $args['parent'] = absint( $parent ); - } - - // Get categories - $categories = get_terms( $args ); - - if ( is_wp_error( $categories ) ) { - return new WP_Error( - 'rest_category_error', - __( 'Error retrieving product categories.', 'dokan-lite' ), - array( 'status' => 400 ) - ); - } - - // Get total count for pagination - $total_args = $args; - unset( $total_args['number'] ); - unset( $total_args['offset'] ); - $total_categories = wp_count_terms( $args ); - - // Format the response data - $data = array(); - foreach ( $categories as $category ) { - $response = $this->prepare_category_for_response( $category, $request ); - if ( $fields ) { - $response = $this->filter_response_by_fields( $response, $fields ); - } - $data[] = $response; - } - - // Create response with pagination headers - $response = new \WP_REST_Response( $data ); - $response->header( 'X-WP-Total', (int) $total_categories ); - $response->header( 'X-WP-TotalPages', ceil( $total_categories / $per_page ) ); - - return $response; - } - - /** - * Prepare category data for REST response - * - * @param WP_Term $category The category object. - * @param WP_REST_Request $request Request object. - * @return array Formatted category data. - */ - protected function prepare_category_for_response( $category, $request ) { - $thumbnail_id = get_term_meta( $category->term_id, 'thumbnail_id', true ); - $thumbnail_url = $thumbnail_id ? wp_get_attachment_url( $thumbnail_id ) : ''; - - return array( - 'id' => (int) $category->term_id, - 'name' => $category->name, - 'slug' => $category->slug, - 'parent' => (int) $category->parent, - 'description' => $category->description, - 'count' => (int) $category->count, - 'thumbnail' => $thumbnail_url, - 'link' => get_term_link( $category ), - ); - } - - /** - * Filter response data by requested fields - * - * @param array $response Response data. - * @param string $fields Requested fields. - * @return array Filtered response data. - */ - protected function filter_response_by_fields( $response, $fields ) { - $fields = explode( ',', $fields ); - return array_intersect_key( $response, array_flip( $fields ) ); - } - - /** - * Get collection parameters for the REST API - * - * @return array Collection parameters. - */ - public function get_collection_params() { - return array( - 'page' => array( - 'description' => 'Current page of the collection.', - 'type' => 'integer', - 'default' => 1, - 'minimum' => 1, - 'sanitize_callback' => 'absint', - ), - 'per_page' => array( - 'description' => 'Maximum number of items to be returned in result set.', - 'type' => 'integer', - 'default' => 10, - 'minimum' => 1, - 'maximum' => 100, - 'sanitize_callback' => 'absint', - ), - 'search' => array( - 'description' => 'Limit results to those matching a string.', - 'type' => 'string', - ), - 'exclude' => array( - 'description' => 'Ensure result set excludes specific IDs.', - 'type' => 'string', - ), - 'include' => array( - 'description' => 'Limit result set to specific IDs.', - 'type' => 'string', - ), - 'order' => array( - 'description' => 'Order sort attribute ascending or descending.', - 'type' => 'string', - 'default' => 'ASC', - 'enum' => array( 'ASC', 'DESC' ), - ), - 'orderby' => array( - 'description' => 'Sort collection by term attribute.', - 'type' => 'string', - 'default' => 'name', - 'enum' => array( 'name', 'id', 'slug', 'count' ), - ), - 'hide_empty' => array( - 'description' => 'Whether to hide terms not assigned to any posts.', - 'type' => 'boolean', - 'default' => false, - ), - 'parent' => array( - 'description' => 'Limit result set to terms assigned to a specific parent.', - 'type' => 'integer', - ), - '_fields' => array( - 'description' => 'Limit response to specific fields.', - 'type' => 'string', - ), - ); - } -} diff --git a/includes/REST/VendorProductCategoriesController.php b/includes/REST/VendorProductCategoriesController.php new file mode 100644 index 0000000000..32e704e593 --- /dev/null +++ b/includes/REST/VendorProductCategoriesController.php @@ -0,0 +1,333 @@ +namespace, + '/' . $this->rest_base, + [ + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_items' ], + 'permission_callback' => [ $this, 'get_items_permissions_check' ], + 'args' => $this->get_collection_params(), + ], + ] + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\d]+)', + [ + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_item' ], + 'permission_callback' => [ $this, 'get_item_permissions_check' ], + 'args' => [ + 'id' => [ + 'description' => __( 'Unique identifier for the resource.', 'dokan-lite' ), + 'type' => 'integer', + ], + 'context' => $this->get_context_param( [ 'default' => 'view' ] ), + ], + ], + ] + ); + } + + /** + * Check permissions for getting items + * + * @param WP_REST_Request $request Request object + * @return bool|WP_Error + */ + public function get_items_permissions_check( $request ) { + if ( ! is_user_logged_in() ) { + return new WP_Error( + 'dokan_rest_unauthorized', + __( 'You are not logged in.', 'dokan-lite' ), + [ 'status' => rest_authorization_required_code() ] + ); + } + + if ( ! dokan_is_user_seller( get_current_user_id() ) ) { + return new WP_Error( + 'dokan_rest_forbidden', + __( 'You are not authorized to view product categories.', 'dokan-lite' ), + [ 'status' => 403 ] + ); + } + + return true; + } + + /** + * Get a collection of product categories + * + * @param WP_REST_Request $request Request object + * @return WP_REST_Response|WP_Error + */ + public function get_items( $request ) { + $vendor_id = get_current_user_id(); + + // Prepare query arguments + $prepared_args = $this->prepare_vendor_category_args( $request, $vendor_id ); + + if ( is_wp_error( $prepared_args ) ) { + return $prepared_args; + } + + // Get categories + $terms = get_terms( $prepared_args ); + + if ( is_wp_error( $terms ) ) { + return new WP_Error( + 'dokan_rest_category_error', + __( 'Error retrieving product categories.', 'dokan-lite' ), + [ 'status' => 400 ] + ); + } + + $response = []; + foreach ( $terms as $term ) { + $data = $this->prepare_item_for_response( $term, $request ); + $response[] = $this->prepare_response_for_collection( $data ); + } + + // Pagination headers + $total_terms = wp_count_terms( $prepared_args ); + $max_pages = ceil( $total_terms / (int) $prepared_args['number'] ); + + $rest_response = rest_ensure_response( $response ); + $rest_response->header( 'X-WP-Total', (int) $total_terms ); + $rest_response->header( 'X-WP-TotalPages', (int) $max_pages ); + + return $rest_response; + } + + /** + * Prepare vendor category arguments + * + * @param WP_REST_Request $request Request object + * @param int $vendor_id Vendor user ID + * @return array|WP_Error Prepared arguments + */ + protected function prepare_vendor_category_args( $request, $vendor_id ) { + // Start with default Dokan product category arguments + $args = apply_filters( + 'dokan_product_cat_dropdown_args', [ + 'taxonomy' => 'product_cat', + 'number' => false, + 'orderby' => 'name', + 'order' => 'asc', + 'hide_empty' => false, + ] + ); + + // Override with request parameters if provided + $args['number'] = $request['per_page'] ?? $args['number']; + $args['offset'] = $request['offset'] ?? 0; + $args['order'] = $request['order'] ?? $args['order']; + $args['orderby'] = $request['orderby'] ?? $args['orderby']; + $args['hide_empty'] = $request['hide_empty'] === 'true' ?? $args['hide_empty']; + + // Handle search parameter + if ( ! empty( $request['search'] ) ) { + $args['search'] = $request['search']; + } + + // Handle parent parameter + if ( isset( $request['parent'] ) ) { + $args['parent'] = $request['parent']; + } + + // Handle include/exclude parameters + if ( ! empty( $request['include'] ) ) { + $args['include'] = array_map( 'absint', explode( ',', $request['include'] ) ); + } + + if ( ! empty( $request['exclude'] ) ) { + $args['exclude'] = array_map( 'absint', explode( ',', $request['exclude'] ) ); + } + + // Filter categories based on vendor's products if needed + if ( ! empty( $request['vendor_products_only'] ) && $request['vendor_products_only'] === 'true' ) { + $vendor_products = get_posts( + [ + 'post_type' => 'product', + 'author' => $vendor_id, + 'posts_per_page' => -1, + 'fields' => 'ids', + ] + ); + + if ( ! empty( $vendor_products ) ) { + $args['object_ids'] = $vendor_products; + } + } + + return apply_filters( 'dokan_rest_vendor_product_categories_args', $args, $request, $vendor_id ); + } + + /** + * Prepare a single product category for response + * + * @param WP_Term $item Term object + * @param WP_REST_Request $request Request object + * @return WP_REST_Response + */ + public function prepare_item_for_response( $item, $request ) { + $response = parent::prepare_item_for_response( $item, $request ); + $data = $response->get_data(); + + // Add vendor-specific data + $vendor_id = get_current_user_id(); + $data['vendor_product_count'] = $this->get_vendor_product_count( $item->term_id, $vendor_id ); + $data['can_use_category'] = $this->can_vendor_use_category( $item->term_id, $vendor_id ); + $data['children_count'] = $this->get_children_count( $item->term_id ); + + $response->set_data( $data ); + return $response; + } + + /** + * Get product count for specific vendor in a category + * + * @param int $term_id Category term ID + * @param int $vendor_id Vendor user ID + * @return int + */ + protected function get_vendor_product_count( $term_id, $vendor_id ) { + $query = new \WP_Query( + [ + 'post_type' => 'product', + 'author' => $vendor_id, + 'tax_query' => [ + [ + 'taxonomy' => 'product_cat', + 'field' => 'term_id', + 'terms' => $term_id, + ], + ], + 'posts_per_page' => -1, + ] + ); + + return $query->found_posts; + } + + /** + * Get number of child categories + * + * @param int $term_id Category term ID + * @return int + */ + protected function get_children_count( $term_id ) { + $children = get_terms( + [ + 'taxonomy' => 'product_cat', + 'parent' => $term_id, + 'fields' => 'count', + 'hide_empty' => false, + ] + ); + + return is_wp_error( $children ) ? 0 : $children; + } + + /** + * Check if vendor can use this category + * + * @param int $term_id Category term ID + * @param int $vendor_id Vendor user ID + * @return bool + */ + protected function can_vendor_use_category( $term_id, $vendor_id ) { + // Implement your vendor category permission logic here + // For example, check if the category is in allowed categories for the vendor + return apply_filters( 'dokan_vendor_can_use_category', true, $term_id, $vendor_id ); + } + + /** + * Get collection params + * + * @return array + */ + public function get_collection_params() { + $params = parent::get_collection_params(); + + $params['vendor_products_only'] = [ + 'description' => __( 'Limit categories to those containing vendor products.', 'dokan-lite' ), + 'type' => 'boolean', + 'default' => false, + 'sanitize_callback' => 'rest_sanitize_boolean', + ]; + + return $params; + } + + /** + * Get the Product Category schema + * + * @return array + */ + public function get_item_schema() { + $schema = parent::get_item_schema(); + + $schema['properties']['vendor_product_count'] = [ + 'description' => __( 'Number of products in this category by the vendor.', 'dokan-lite' ), + 'type' => 'integer', + 'context' => [ 'view' ], + 'readonly' => true, + ]; + + $schema['properties']['can_use_category'] = [ + 'description' => __( 'Whether the vendor can use this category.', 'dokan-lite' ), + 'type' => 'boolean', + 'context' => [ 'view' ], + 'readonly' => true, + ]; + + $schema['properties']['children_count'] = [ + 'description' => __( 'Number of child categories.', 'dokan-lite' ), + 'type' => 'integer', + 'context' => [ 'view' ], + 'readonly' => true, + ]; + + return $schema; + } +} From abb0efea8f088046f562eac9ec5353a6f01af137 Mon Sep 17 00:00:00 2001 From: osman sufy Date: Thu, 30 Jan 2025 14:39:42 +0600 Subject: [PATCH 2/4] update: dokan vendor categories --- .../VendorProductCategoriesController.php | 351 +++++------------- 1 file changed, 92 insertions(+), 259 deletions(-) diff --git a/includes/REST/VendorProductCategoriesController.php b/includes/REST/VendorProductCategoriesController.php index 32e704e593..0f314eb1bb 100644 --- a/includes/REST/VendorProductCategoriesController.php +++ b/includes/REST/VendorProductCategoriesController.php @@ -2,332 +2,165 @@ namespace WeDevs\Dokan\REST; -use WC_REST_Product_Categories_Controller; use WP_Error; use WP_REST_Server; use WP_REST_Request; use WP_REST_Response; -use WP_Term; - -/** - * Vendor Product Categories REST Controller - * - * Extends WooCommerce Product Categories REST API for vendor-specific functionality - * - * @since 3.0.0 - */ +use WC_REST_Product_Categories_Controller; + class VendorProductCategoriesController extends WC_REST_Product_Categories_Controller { /** - * Endpoint namespace + * Endpoint namespace. * * @var string */ protected $namespace = 'dokan/v1'; /** - * Route base + * Route base. * * @var string */ protected $rest_base = 'products/categories'; /** - * Register routes + * + * @return false */ - public function register_routes() { - register_rest_route( - $this->namespace, - '/' . $this->rest_base, - [ - [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'get_items' ], - 'permission_callback' => [ $this, 'get_items_permissions_check' ], - 'args' => $this->get_collection_params(), - ], - ] - ); - - register_rest_route( - $this->namespace, - '/' . $this->rest_base . '/(?P[\d]+)', - [ - [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'get_item' ], - 'permission_callback' => [ $this, 'get_item_permissions_check' ], - 'args' => [ - 'id' => [ - 'description' => __( 'Unique identifier for the resource.', 'dokan-lite' ), - 'type' => 'integer', - ], - 'context' => $this->get_context_param( [ 'default' => 'view' ] ), - ], - ], - ] - ); + + public function create_item_permissions_check($request) { + return false; } /** - * Check permissions for getting items - * - * @param WP_REST_Request $request Request object - * @return bool|WP_Error + * @return false */ - public function get_items_permissions_check( $request ) { - if ( ! is_user_logged_in() ) { - return new WP_Error( - 'dokan_rest_unauthorized', - __( 'You are not logged in.', 'dokan-lite' ), - [ 'status' => rest_authorization_required_code() ] - ); - } + public function delete_item_permissions_check($request) { + return false; + } - if ( ! dokan_is_user_seller( get_current_user_id() ) ) { - return new WP_Error( - 'dokan_rest_forbidden', - __( 'You are not authorized to view product categories.', 'dokan-lite' ), - [ 'status' => 403 ] - ); - } + /** + * @return false + */ - return true; + public function update_item_permissions_check($request) + { + return false; } - /** - * Get a collection of product categories + * Check if a given request has access to read items. * - * @param WP_REST_Request $request Request object - * @return WP_REST_Response|WP_Error + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|boolean */ - public function get_items( $request ) { - $vendor_id = get_current_user_id(); - - // Prepare query arguments - $prepared_args = $this->prepare_vendor_category_args( $request, $vendor_id ); - - if ( is_wp_error( $prepared_args ) ) { - return $prepared_args; - } - - // Get categories - $terms = get_terms( $prepared_args ); - - if ( is_wp_error( $terms ) ) { - return new WP_Error( - 'dokan_rest_category_error', - __( 'Error retrieving product categories.', 'dokan-lite' ), - [ 'status' => 400 ] - ); - } - - $response = []; - foreach ( $terms as $term ) { - $data = $this->prepare_item_for_response( $term, $request ); - $response[] = $this->prepare_response_for_collection( $data ); - } - - // Pagination headers - $total_terms = wp_count_terms( $prepared_args ); - $max_pages = ceil( $total_terms / (int) $prepared_args['number'] ); - - $rest_response = rest_ensure_response( $response ); - $rest_response->header( 'X-WP-Total', (int) $total_terms ); - $rest_response->header( 'X-WP-TotalPages', (int) $max_pages ); - - return $rest_response; + public function get_items_permissions_check($request) { + return current_user_can( 'dokandar' ); } /** - * Prepare vendor category arguments + * Get all product categories. * - * @param WP_REST_Request $request Request object - * @param int $vendor_id Vendor user ID - * @return array|WP_Error Prepared arguments + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|WP_REST_Response */ - protected function prepare_vendor_category_args( $request, $vendor_id ) { - // Start with default Dokan product category arguments - $args = apply_filters( - 'dokan_product_cat_dropdown_args', [ - 'taxonomy' => 'product_cat', - 'number' => false, - 'orderby' => 'name', - 'order' => 'asc', - 'hide_empty' => false, - ] - ); - - // Override with request parameters if provided - $args['number'] = $request['per_page'] ?? $args['number']; - $args['offset'] = $request['offset'] ?? 0; - $args['order'] = $request['order'] ?? $args['order']; - $args['orderby'] = $request['orderby'] ?? $args['orderby']; - $args['hide_empty'] = $request['hide_empty'] === 'true' ?? $args['hide_empty']; - - // Handle search parameter - if ( ! empty( $request['search'] ) ) { - $args['search'] = $request['search']; - } + public function get_items($request) { + // Get current user ID + $vendor_id = dokan_get_current_user_id(); - // Handle parent parameter - if ( isset( $request['parent'] ) ) { - $args['parent'] = $request['parent']; - } + // Get categories using parent method + $response = parent::get_items($request); - // Handle include/exclude parameters - if ( ! empty( $request['include'] ) ) { - $args['include'] = array_map( 'absint', explode( ',', $request['include'] ) ); + if (is_wp_error($response)) { + return $response; } - if ( ! empty( $request['exclude'] ) ) { - $args['exclude'] = array_map( 'absint', explode( ',', $request['exclude'] ) ); - } + $categories = $response->get_data(); - // Filter categories based on vendor's products if needed - if ( ! empty( $request['vendor_products_only'] ) && $request['vendor_products_only'] === 'true' ) { - $vendor_products = get_posts( - [ - 'post_type' => 'product', - 'author' => $vendor_id, - 'posts_per_page' => -1, - 'fields' => 'ids', - ] - ); - - if ( ! empty( $vendor_products ) ) { - $args['object_ids'] = $vendor_products; - } - } + // Allow other modules to filter the categories + $categories = apply_filters('dokan_rest_get_product_categories', $categories, $vendor_id, $request); - return apply_filters( 'dokan_rest_vendor_product_categories_args', $args, $request, $vendor_id ); - } + // Check if categories are allowed for vendor's subscription - /** - * Prepare a single product category for response - * - * @param WP_Term $item Term object - * @param WP_REST_Request $request Request object - * @return WP_REST_Response - */ - public function prepare_item_for_response( $item, $request ) { - $response = parent::prepare_item_for_response( $item, $request ); - $data = $response->get_data(); + if (dokan_pro()->module->is_active('product_subscription')) { + // call method get_vendor_allowed_categories() from product_subscription module + $allowed_categories = apply_filters('dokan_get_vendor_allowed_categories', [], $vendor_id); - // Add vendor-specific data - $vendor_id = get_current_user_id(); - $data['vendor_product_count'] = $this->get_vendor_product_count( $item->term_id, $vendor_id ); - $data['can_use_category'] = $this->can_vendor_use_category( $item->term_id, $vendor_id ); - $data['children_count'] = $this->get_children_count( $item->term_id ); + if (!empty($allowed_categories)) { + $categories = array_filter($categories, function ($category) use ($allowed_categories) { + return in_array($category['id'], $allowed_categories); + }); + } + } + + $response->set_data($categories); - $response->set_data( $data ); return $response; } - /** - * Get product count for specific vendor in a category - * - * @param int $term_id Category term ID - * @param int $vendor_id Vendor user ID - * @return int - */ - protected function get_vendor_product_count( $term_id, $vendor_id ) { - $query = new \WP_Query( - [ - 'post_type' => 'product', - 'author' => $vendor_id, - 'tax_query' => [ - [ - 'taxonomy' => 'product_cat', - 'field' => 'term_id', - 'terms' => $term_id, - ], - ], - 'posts_per_page' => -1, - ] - ); - - return $query->found_posts; - } /** - * Get number of child categories + * Check if a given request has access to read an item. * - * @param int $term_id Category term ID - * @return int + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|boolean */ - protected function get_children_count( $term_id ) { - $children = get_terms( - [ - 'taxonomy' => 'product_cat', - 'parent' => $term_id, - 'fields' => 'count', - 'hide_empty' => false, - ] - ); - - return is_wp_error( $children ) ? 0 : $children; - } + public function get_item_permissions_check( $request ) { - /** - * Check if vendor can use this category - * - * @param int $term_id Category term ID - * @param int $vendor_id Vendor user ID - * @return bool - */ - protected function can_vendor_use_category( $term_id, $vendor_id ) { - // Implement your vendor category permission logic here - // For example, check if the category is in allowed categories for the vendor - return apply_filters( 'dokan_vendor_can_use_category', true, $term_id, $vendor_id ); + return current_user_can( 'dokandar' ); } /** - * Get collection params + * Get a single product category. * - * @return array + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|WP_REST_Response */ - public function get_collection_params() { - $params = parent::get_collection_params(); + public function get_item($request) { + $response = parent::get_item($request); - $params['vendor_products_only'] = [ - 'description' => __( 'Limit categories to those containing vendor products.', 'dokan-lite' ), - 'type' => 'boolean', - 'default' => false, - 'sanitize_callback' => 'rest_sanitize_boolean', - ]; + if (is_wp_error($response)) { + return $response; + } - return $params; + $vendor_id = dokan_get_current_user_id(); + $category = $response->get_data(); + + // Allow modules to filter single category data + $category = apply_filters('dokan_rest_get_product_category', $category, $vendor_id, $request); + + // Check if category is allowed for vendor's subscription + if (dokan_pro()->module->is_active('product_subscription')) { + $allowed_categories = apply_filters('dokan_get_vendor_allowed_categories', [], $vendor_id); + + if (!empty($allowed_categories) && !in_array($category['id'], $allowed_categories)) { + return new WP_Error( + 'dokan_rest_category_not_allowed', + __('This category is not allowed in your subscription plan.', 'dokan-lite'), + ['status' => 403] + ); + } + } + + $response->set_data($category); + + return $response; } /** - * Get the Product Category schema + * Get the query params for collections of product categories. * * @return array */ - public function get_item_schema() { - $schema = parent::get_item_schema(); - - $schema['properties']['vendor_product_count'] = [ - 'description' => __( 'Number of products in this category by the vendor.', 'dokan-lite' ), - 'type' => 'integer', - 'context' => [ 'view' ], - 'readonly' => true, - ]; + public function get_collection_params() { + $params = parent::get_collection_params(); - $schema['properties']['can_use_category'] = [ - 'description' => __( 'Whether the vendor can use this category.', 'dokan-lite' ), + // Add additional query parameters for Dokan-specific filtering + $params['vendor_allowed'] = [ + 'description' => __('Limit result set to categories allowed for vendor.', 'dokan-lite'), 'type' => 'boolean', - 'context' => [ 'view' ], - 'readonly' => true, + 'default' => false, ]; - $schema['properties']['children_count'] = [ - 'description' => __( 'Number of child categories.', 'dokan-lite' ), - 'type' => 'integer', - 'context' => [ 'view' ], - 'readonly' => true, - ]; - - return $schema; + return $params; } } From d576070a91a3ef98ef24ca1ab1a8cec2d0069fd5 Mon Sep 17 00:00:00 2001 From: osman sufy Date: Thu, 30 Jan 2025 15:01:48 +0600 Subject: [PATCH 3/4] update: product categories rest api extending WC_REST_Product_Categories_Controller --- .../VendorProductCategoriesController.php | 125 ++++-------------- 1 file changed, 24 insertions(+), 101 deletions(-) diff --git a/includes/REST/VendorProductCategoriesController.php b/includes/REST/VendorProductCategoriesController.php index 0f314eb1bb..8e4930df40 100644 --- a/includes/REST/VendorProductCategoriesController.php +++ b/includes/REST/VendorProductCategoriesController.php @@ -17,84 +17,62 @@ class VendorProductCategoriesController extends WC_REST_Product_Categories_Contr protected $namespace = 'dokan/v1'; /** - * Route base. - * - * @var string - */ - protected $rest_base = 'products/categories'; - - /** - * + * Override the create_item_permissions_check method * @return false */ - public function create_item_permissions_check($request) { + public function create_item_permissions_check( $request ): bool { return false; } /** + * Override the delete_item_permissions_check method * @return false */ - public function delete_item_permissions_check($request) { + public function delete_item_permissions_check( $request ): bool { return false; } /** + * Override the update_item_permissions_check method * @return false */ - public function update_item_permissions_check($request) - { + public function update_item_permissions_check( $request ): bool { return false; } /** * Check if a given request has access to read items. * - * @param WP_REST_Request $request Full details about the request. - * @return WP_Error|boolean + * Override the get_items_permissions_check method + * @return boolean */ - public function get_items_permissions_check($request) { + public function get_items_permissions_check( $request ): bool { return current_user_can( 'dokandar' ); } + /** + * Check if a given request has access batch create, update and delete items. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return boolean + */ + public function batch_items_permissions_check( $request ) { + return false; + } + /** * Get all product categories. * * @param WP_REST_Request $request Full details about the request. * @return WP_Error|WP_REST_Response */ - public function get_items($request) { - // Get current user ID - $vendor_id = dokan_get_current_user_id(); + public function get_items( $request ) { + $request = apply_filters( 'dokan_rest_product_categories_query', $request ); // Get categories using parent method - $response = parent::get_items($request); - - if (is_wp_error($response)) { - return $response; - } - - $categories = $response->get_data(); - - // Allow other modules to filter the categories - $categories = apply_filters('dokan_rest_get_product_categories', $categories, $vendor_id, $request); - - // Check if categories are allowed for vendor's subscription - - if (dokan_pro()->module->is_active('product_subscription')) { - // call method get_vendor_allowed_categories() from product_subscription module - $allowed_categories = apply_filters('dokan_get_vendor_allowed_categories', [], $vendor_id); - - if (!empty($allowed_categories)) { - $categories = array_filter($categories, function ($category) use ($allowed_categories) { - return in_array($category['id'], $allowed_categories); - }); - } - } - - $response->set_data($categories); - - return $response; + return parent::get_items( $request ); } @@ -102,65 +80,10 @@ public function get_items($request) { * Check if a given request has access to read an item. * * @param WP_REST_Request $request Full details about the request. - * @return WP_Error|boolean + * @return boolean */ public function get_item_permissions_check( $request ) { return current_user_can( 'dokandar' ); } - - /** - * Get a single product category. - * - * @param WP_REST_Request $request Full details about the request. - * @return WP_Error|WP_REST_Response - */ - public function get_item($request) { - $response = parent::get_item($request); - - if (is_wp_error($response)) { - return $response; - } - - $vendor_id = dokan_get_current_user_id(); - $category = $response->get_data(); - - // Allow modules to filter single category data - $category = apply_filters('dokan_rest_get_product_category', $category, $vendor_id, $request); - - // Check if category is allowed for vendor's subscription - if (dokan_pro()->module->is_active('product_subscription')) { - $allowed_categories = apply_filters('dokan_get_vendor_allowed_categories', [], $vendor_id); - - if (!empty($allowed_categories) && !in_array($category['id'], $allowed_categories)) { - return new WP_Error( - 'dokan_rest_category_not_allowed', - __('This category is not allowed in your subscription plan.', 'dokan-lite'), - ['status' => 403] - ); - } - } - - $response->set_data($category); - - return $response; - } - - /** - * Get the query params for collections of product categories. - * - * @return array - */ - public function get_collection_params() { - $params = parent::get_collection_params(); - - // Add additional query parameters for Dokan-specific filtering - $params['vendor_allowed'] = [ - 'description' => __('Limit result set to categories allowed for vendor.', 'dokan-lite'), - 'type' => 'boolean', - 'default' => false, - ]; - - return $params; - } } From 094b3c8e2b2c15c8570a37e16f646b0b85643710 Mon Sep 17 00:00:00 2001 From: osman sufy Date: Fri, 31 Jan 2025 12:33:55 +0600 Subject: [PATCH 4/4] update: extends api from wc products categories --- .../VendorProductCategoriesController.php | 83 +++--- .../ProductCategoryApiTest.php | 269 ------------------ .../VendorProductCategoriesApiTest.php | 249 ++++++++++++++++ 3 files changed, 299 insertions(+), 302 deletions(-) delete mode 100644 tests/php/src/ProductCategory/ProductCategoryApiTest.php create mode 100644 tests/php/src/ProductCategory/VendorProductCategoriesApiTest.php diff --git a/includes/REST/VendorProductCategoriesController.php b/includes/REST/VendorProductCategoriesController.php index 8e4930df40..1adeb0a99c 100644 --- a/includes/REST/VendorProductCategoriesController.php +++ b/includes/REST/VendorProductCategoriesController.php @@ -3,10 +3,10 @@ namespace WeDevs\Dokan\REST; use WP_Error; -use WP_REST_Server; use WP_REST_Request; use WP_REST_Response; use WC_REST_Product_Categories_Controller; +use WP_REST_Server; class VendorProductCategoriesController extends WC_REST_Product_Categories_Controller { /** @@ -17,29 +17,44 @@ class VendorProductCategoriesController extends WC_REST_Product_Categories_Contr protected $namespace = 'dokan/v1'; /** - * Override the create_item_permissions_check method - * @return false - */ - - public function create_item_permissions_check( $request ): bool { - return false; - } - - /** - * Override the delete_item_permissions_check method - * @return false - */ - public function delete_item_permissions_check( $request ): bool { - return false; - } - - /** - * Override the update_item_permissions_check method - * @return false + * Register the routes for terms. */ + public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'args' => $this->get_collection_params(), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); - public function update_item_permissions_check( $request ): bool { - return false; + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\d]+)', + array( + 'args' => array( + 'id' => array( + 'description' => __( 'Unique identifier for the resource.', 'dokan-lite' ), + 'type' => 'integer', + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); } /** * Check if a given request has access to read items. @@ -52,16 +67,15 @@ public function get_items_permissions_check( $request ): bool { } /** - * Check if a given request has access batch create, update and delete items. - * - * @param WP_REST_Request $request Full details about the request. + * Check if a given request has access to read a single item. * + * Override the get_item_permissions_check method * @return boolean */ - public function batch_items_permissions_check( $request ) { - return false; - } + public function get_item_permissions_check( $request ): bool { + return current_user_can( 'dokandar' ); + } /** * Get all product categories. * @@ -75,15 +89,18 @@ public function get_items( $request ) { return parent::get_items( $request ); } - /** - * Check if a given request has access to read an item. + * Get a single product category. * * @param WP_REST_Request $request Full details about the request. - * @return boolean + * @return WP_Error|WP_REST_Response */ - public function get_item_permissions_check( $request ) { - return current_user_can( 'dokandar' ); + public function get_item( $request ) { + $request = apply_filters( 'dokan_rest_product_category_query', $request ); + + // Get category using parent method + return parent::get_item( $request ); } + } diff --git a/tests/php/src/ProductCategory/ProductCategoryApiTest.php b/tests/php/src/ProductCategory/ProductCategoryApiTest.php deleted file mode 100644 index f5e96f26f1..0000000000 --- a/tests/php/src/ProductCategory/ProductCategoryApiTest.php +++ /dev/null @@ -1,269 +0,0 @@ -categories = $this->create_test_categories(); - } - - /** - * Create test categories with parent-child relationships - */ - protected function create_test_categories(): array { - $categories = []; - - // Create parent categories - $categories['parent1'] = $this->factory()->term->create( - [ - 'taxonomy' => 'product_cat', - 'name' => 'Parent Category 1', - ] - ); - - $categories['parent2'] = $this->factory()->term->create( - [ - 'taxonomy' => 'product_cat', - 'name' => 'Parent Category 2', - ] - ); - - // Create child categories - $categories['child1'] = $this->factory()->term->create( - [ - 'taxonomy' => 'product_cat', - 'name' => 'Child Category 1', - 'parent' => $categories['parent1'], - ] - ); - - $categories['child2'] = $this->factory()->term->create( - [ - 'taxonomy' => 'product_cat', - 'name' => 'Child Category 2', - 'parent' => $categories['parent1'], - ] - ); - - // Create additional categories for pagination tests - $categories['others'] = $this->factory()->term->create_many( - 6, [ - 'taxonomy' => 'product_cat', - ] - ); - - return $categories; - } - - /** - * Helper method to make API requests - */ - protected function make_request( array $params = [] ): \WP_REST_Response { - $route = $this->get_route( $this->rest_base . '/' ); - $request = new \WP_REST_Request( 'GET', $route ); - - foreach ( $params as $key => $value ) { - $request->set_param( $key, $value ); - } - - return $this->server->dispatch( $request ); - } - - // make single category request - protected function make_single_category_request( int $category_id ): \WP_REST_Response { - $route = $this->get_route( $this->rest_base . '/' . $category_id ); - $request = new \WP_REST_Request( 'GET', $route ); - - return $this->server->dispatch( $request ); - - } - - /** - * Helper method to authenticate as vendor - */ - protected function authenticate_as_vendor(): void { - wp_set_current_user( $this->seller_id1 ); - } - - /** - * @group endpoint - */ - public function test_endpoint_exists(): void { - $routes = $this->server->get_routes( $this->namespace ); - $full_route = $this->get_route( $this->rest_base . '/' ); - $this->assertArrayHasKey( $full_route, $routes ); - } - - /** - * @group authentication - */ - public function test_requires_authentication(): void { - $response = $this->make_request(); - $this->assertEquals( 401, $response->get_status() ); - } - - /** - * @group authentication - */ - public function test_vendor_can_access(): void { - $this->authenticate_as_vendor(); - $response = $this->make_request(); - $this->assertEquals( 200, $response->get_status() ); - } - - /** - * @group pagination - */ - public function test_pagination(): void { - $this->authenticate_as_vendor(); - - // Test first page - $response = $this->make_request( - [ - 'per_page' => 5, - 'page' => 1, - ] - ); - $data = $response->get_data(); - $this->assertCount( 5, $data ); - - // Test second page - $response = $this->make_request( - [ - 'per_page' => 5, - 'page' => 2, - ] - ); - $data = $response->get_data(); - $this->assertNotEmpty( $data ); - - // Test pagination headers - $headers = $response->get_headers(); - $this->assertArrayHasKey( 'X-WP-Total', $headers ); - $this->assertArrayHasKey( 'X-WP-TotalPages', $headers ); - } - - /** - * @group filtering - */ - public function test_search_filter(): void { - $this->authenticate_as_vendor(); - - $response = $this->make_request( [ 'search' => 'Parent' ] ); - $data = $response->get_data(); - - $this->assertNotEmpty( $data ); - foreach ( $data as $category ) { - $this->assertStringContainsStringIgnoringCase( 'Parent', $category['name'] ); - } - } - - /** - * @group filtering - */ - public function test_exclude_filter(): void { - $this->authenticate_as_vendor(); - - $exclude_id = $this->categories['parent1']; - $response = $this->make_request( [ 'exclude' => (string) $exclude_id ] ); - $data = $response->get_data(); - - $category_ids = wp_list_pluck( $data, 'id' ); - $this->assertNotContains( $exclude_id, $category_ids ); - } - - /** - * @group filtering - */ - public function test_include_filter(): void { - $this->authenticate_as_vendor(); - - $include_id = $this->categories['parent1']; - $response = $this->make_request( [ 'include' => (string) $include_id ] ); - $data = $response->get_data(); - - $this->assertCount( 1, $data ); - $this->assertEquals( $include_id, $data[0]['id'] ); - } - - /** - * @group fields - */ - public function test_fields_parameter(): void { - $this->authenticate_as_vendor(); - - $response = $this->make_request( [ '_fields' => 'id,name' ] ); - $data = $response->get_data(); - - $this->assertNotEmpty( $data ); - foreach ( $data as $category ) { - $this->assertArrayHasKey( 'id', $category ); - $this->assertArrayHasKey( 'name', $category ); - $this->assertArrayNotHasKey( 'description', $category ); - $this->assertArrayNotHasKey( 'parent', $category ); - } - } - - /** - * @group parent-child - */ - public function test_parent_parameter(): void { - $this->authenticate_as_vendor(); - - $parent_id = $this->categories['parent1']; - $response = $this->make_request( [ 'parent' => $parent_id ] ); - $data = $response->get_data(); - - $this->assertNotEmpty( $data ); - foreach ( $data as $category ) { - $this->assertEquals( $parent_id, $category['parent'] ); - } - } - - // test single category response - public function test_single_category_response(): void { - $this->authenticate_as_vendor(); - - $category_id = $this->categories['parent1']; - $response = $this->make_single_category_request( $category_id ); - $data = $response->get_data(); - - $this->assertEquals( $category_id, $data['id'] ); - $this->assertEquals( 'Parent Category 1', $data['name'] ); - } - /** - * Clean up after tests - */ - public function tearDown(): void { - // Delete test categories - foreach ( $this->categories as $category_id ) { - if ( is_array( $category_id ) ) { - foreach ( $category_id as $id ) { - wp_delete_term( $id, 'product_cat' ); - } - } else { - wp_delete_term( $category_id, 'product_cat' ); - } - } - - parent::tearDown(); - } -} diff --git a/tests/php/src/ProductCategory/VendorProductCategoriesApiTest.php b/tests/php/src/ProductCategory/VendorProductCategoriesApiTest.php new file mode 100644 index 0000000000..13df01c159 --- /dev/null +++ b/tests/php/src/ProductCategory/VendorProductCategoriesApiTest.php @@ -0,0 +1,249 @@ +categories = $this->create_test_categories(); + } + + /** + * Create test categories with parent-child relationships + */ + protected function create_test_categories(): array { + $categories = []; + + // Create parent categories + $categories['parent1'] = $this->factory()->term->create( + [ + 'taxonomy' => 'product_cat', + 'name' => 'Parent Category 1', + ] + ); + + $categories['parent2'] = $this->factory()->term->create( + [ + 'taxonomy' => 'product_cat', + 'name' => 'Parent Category 2', + ] + ); + + // Create child categories + $categories['child1'] = $this->factory()->term->create( + [ + 'taxonomy' => 'product_cat', + 'name' => 'Child Category 1', + 'parent' => $categories['parent1'], + ] + ); + + return $categories; + } + + /** + * Test that endpoint exists + */ + public function test_endpoint_exists(): void { + $routes = $this->server->get_routes(); + $this->assertArrayHasKey( $this->get_route( $this->base ), $routes ); + } + + /** + * Test get_items permissions + */ + public function test_get_items_permissions(): void { + // Test as guest + wp_set_current_user( 0 ); + $response = $this->get_request( $this->base ); + $this->assertEquals( 401, $response->get_status() ); + + // Test as customer + wp_set_current_user( $this->customer_id ); + $response = $this->get_request( $this->base ); + $this->assertEquals( 403, $response->get_status() ); + + // Test as vendor + wp_set_current_user( $this->seller_id1 ); + $response = $this->get_request( $this->base ); + $this->assertEquals( 200, $response->get_status() ); + } + + /** + * Test get_item permissions + */ + public function test_get_item_permissions(): void { + $category_id = $this->categories['parent1']; + + // Test as guest + wp_set_current_user( 0 ); + $response = $this->get_request( "{$this->base}/{$category_id}" ); + $this->assertEquals( 401, $response->get_status() ); + + // Test as customer + wp_set_current_user( $this->customer_id ); + $response = $this->get_request( "{$this->base}/{$category_id}" ); + $this->assertEquals( 403, $response->get_status() ); + + // Test as vendor + wp_set_current_user( $this->seller_id1 ); + $response = $this->get_request( "{$this->base}/{$category_id}" ); + $this->assertEquals( 200, $response->get_status() ); + } + + /** + * Test getting product categories list + */ + public function test_get_product_categories(): void { + wp_set_current_user( $this->seller_id1 ); + + $response = $this->get_request( $this->base ); + $this->assertEquals( 200, $response->get_status() ); + + $data = $response->get_data(); + $this->assertIsArray( $data ); + $this->assertNotEmpty( $data ); + + // Check structure of first category + $first_category = $data[0]; + $this->assertArrayHasKey( 'id', $first_category ); + $this->assertArrayHasKey( 'name', $first_category ); + $this->assertArrayHasKey( 'slug', $first_category ); + $this->assertArrayHasKey( 'parent', $first_category ); + $this->assertArrayHasKey( 'count', $first_category ); + } + + /** + * Test getting single product category + */ + public function test_get_single_product_category(): void { + wp_set_current_user( $this->seller_id1 ); + + $category_id = $this->categories['parent1']; + $response = $this->get_request( "{$this->base}/{$category_id}" ); + + $this->assertEquals( 200, $response->get_status() ); + $data = $response->get_data(); + + $this->assertEquals( $category_id, $data['id'] ); + $this->assertEquals( 'Parent Category 1', $data['name'] ); + } + + /** + * Test filtering categories by parent + */ + public function test_filter_categories_by_parent(): void { + wp_set_current_user( $this->seller_id1 ); + + $parent_id = $this->categories['parent1']; + $response = $this->get_request( $this->base, [ 'parent' => $parent_id ] ); + + $this->assertEquals( 200, $response->get_status() ); + $data = $response->get_data(); + + foreach ( $data as $category ) { + $this->assertEquals( $parent_id, $category['parent'] ); + } + } + + /** + * Test category creation is disabled + */ + public function test_create_category_is_disabled(): void { + wp_set_current_user( $this->seller_id1 ); + + $response = $this->post_request( + $this->base, [ + 'name' => 'Test Category', + ] + ); + + $this->assertEquals( 404, $response->get_status() ); + } + + /** + * Test category update is disabled + */ + public function test_update_category_is_disabled(): void { + wp_set_current_user( $this->seller_id1 ); + + $category_id = $this->categories['parent1']; + $response = $this->put_request( + "{$this->base}/{$category_id}", [ + 'name' => 'Updated Name', + ] + ); + + $this->assertEquals( 404, $response->get_status() ); + } + + /** + * Test category deletion is disabled + */ + public function test_delete_category_is_disabled(): void { + wp_set_current_user( $this->seller_id1 ); + + $category_id = $this->categories['parent1']; + $response = $this->delete_request( "{$this->base}/{$category_id}" ); + + $this->assertEquals( 404, $response->get_status() ); + } + + /** + * Test batch operations are disabled + */ + public function test_batch_operations_are_disabled(): void { + wp_set_current_user( $this->seller_id1 ); + + $response = $this->post_request( + "{$this->base}/batch", [ + 'create' => [ + [ 'name' => 'New Category' ], + ], + 'update' => [ + [ + 'id' => $this->categories['parent1'], + 'name' => 'Updated Name', + ], + ], + 'delete' => [ + $this->categories['child1'], + ], + ] + ); + + $this->assertEquals( 404, $response->get_status() ); + } + + /** + * Clean up after tests + */ + protected function tearDown(): void { + // Delete test categories + foreach ( $this->categories as $category_id ) { + wp_delete_term( $category_id, 'product_cat' ); + } + + parent::tearDown(); + } +}