Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance/product category rest api #2510

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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 includes/REST/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
*
* @return void
*/
public function prepeare_product_response( $response, $object, $request ) {

Check warning on line 77 in includes/REST/Manager.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

The method parameter $object is never used

Check warning on line 77 in includes/REST/Manager.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

The method parameter $request is never used

Check warning on line 77 in includes/REST/Manager.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

It is recommended not to use reserved keyword "object" as function parameter name. Found: $object
$data = $response->get_data();
$author_id = get_post_field( 'post_author', $data['id'] );

Expand Down Expand Up @@ -136,7 +136,7 @@
*
* @return void
*/
public function on_dokan_rest_insert_product( $object, $request, $creating ) {

Check warning on line 139 in includes/REST/Manager.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

It is recommended not to use reserved keyword "object" as function parameter name. Found: $object
// if not creating, meaning product is updating. So return early
if ( ! $creating ) {
return;
Expand Down Expand Up @@ -201,6 +201,7 @@
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',
)
);
}
Expand Down
208 changes: 208 additions & 0 deletions includes/REST/ProductCategoriesVendorController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
<?php
namespace WeDevs\Dokan\REST;

use WeDevs\Dokan\Abstracts\DokanRESTVendorController;
use WP_Error;
use WP_REST_Request;
use WP_REST_Server;
use WP_Term;

class ProductCategoriesVendorController extends DokanRESTVendorController {

protected $rest_base = 'product-categories';

public function register_routes() {
register_rest_route(
$this->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;
Comment on lines +36 to +37
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Validate maximum value for per_page parameter

The per_page parameter should be capped at 100 as defined in get_collection_params().

-        $per_page = $request->get_param( 'per_page' ) ? $request->get_param( 'per_page' ) : 10;
+        $per_page = min( $request->get_param( 'per_page' ) ? (int) $request->get_param( 'per_page' ) : 10, 100 );
📝 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
$per_page = $request->get_param( 'per_page' ) ? $request->get_param( 'per_page' ) : 10;
$page = $request->get_param( 'page' ) ? $request->get_param( 'page' ) : 1;
$per_page = min( $request->get_param( 'per_page' ) ? (int) $request->get_param( 'per_page' ) : 10, 100 );
$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' );
Comment on lines +36 to +45
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Sanitize and validate request parameters

The request parameters such as 'order', 'orderby', 'parent', and others are not sanitized or validated. This could lead to unexpected behavior or security issues if invalid data is passed. It's important to add 'sanitize_callback' and 'validate_callback' functions to these parameters in your get_collection_params() method.

For example, update the 'order' parameter:

'order' => array(
    'description'       => 'Order sort attribute ascending or descending.',
    'type'              => 'string',
    'default'           => 'ASC',
    'enum'              => array( 'ASC', 'DESC' ),
+   'sanitize_callback' => 'sanitize_text_field',
+   'validate_callback' => 'rest_validate_request_arg',
),

Similarly, add appropriate callbacks for 'orderby', 'parent', and other parameters to ensure they're properly sanitized and validated.

Committable suggestion skipped: line range outside the PR's diff.

// 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
unset( $args['number'] );
unset( $args['offset'] );
$total_categories = wp_count_terms( $args );
osmansufy marked this conversation as resolved.
Show resolved Hide resolved

// 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 ): array {
$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 ),
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for term link generation

The get_term_link() function can return WP_Error, which should be handled.

-            'link' => get_term_link( $category ),
+            'link' => is_wp_error( $term_link = get_term_link( $category ) ) ? '' : $term_link,
📝 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
'link' => get_term_link( $category ),
'link' => is_wp_error( $term_link = get_term_link( $category ) ) ? '' : $term_link,

);
}

/**
* 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',
'validate_callback' => 'rest_validate_request_arg',
),
'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',
'sanitize_callback' => 'sanitize_text_field',
),
'exclude' => array(
'description' => 'Ensure result set excludes specific IDs.',
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
),
'include' => array(
'description' => 'Limit result set to specific IDs.',
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
),
'order' => array(
'description' => 'Order sort attribute ascending or descending.',
'type' => 'string',
'default' => 'ASC',
'enum' => array( 'ASC', 'DESC' ),
'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => 'rest_validate_request_arg',
),
'orderby' => array(
'description' => 'Sort collection by term attribute.',
'type' => 'string',
'default' => 'name',
'enum' => array( 'name', 'id', 'slug', 'count' ),
'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => 'rest_validate_request_arg',
),
'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',
),
Comment on lines +198 to +201
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation for parent parameter

The parent parameter should be validated to ensure it's a positive integer.

             'parent' => array(
                 'description' => 'Limit result set to terms assigned to a specific parent.',
                 'type' => 'integer',
+                'minimum' => 0,
+                'sanitize_callback' => 'absint',
+                'validate_callback' => 'rest_validate_request_arg',
             ),
📝 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
'parent' => array(
'description' => 'Limit result set to terms assigned to a specific parent.',
'type' => 'integer',
),
'parent' => array(
'description' => 'Limit result set to terms assigned to a specific parent.',
'type' => 'integer',
'minimum' => 0,
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
),

'_fields' => array(
'description' => 'Limit response to specific fields.',
'type' => 'string',
),
Comment on lines +202 to +205
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation for _fields parameter

The _fields parameter should validate that only existing fields are requested.

             '_fields' => array(
                 'description' => 'Limit response to specific fields.',
                 'type' => 'string',
+                'validate_callback' => array( $this, 'validate_fields' ),
             ),

Add this validation method to your class:

/**
 * Validate requested fields
 *
 * @param string $value Comma-separated list of fields
 * @return bool|WP_Error
 */
protected function validate_fields( $value ) {
    $valid_fields = array( 'id', 'name', 'slug', 'parent', 'description', 'count', 'thumbnail', 'link' );
    $requested_fields = explode( ',', $value );
    $invalid_fields = array_diff( $requested_fields, $valid_fields );
    
    if ( ! empty( $invalid_fields ) ) {
        return new WP_Error(
            'rest_invalid_fields',
            sprintf(
                __( 'Invalid field(s): %s. Valid fields are: %s', 'dokan-lite' ),
                implode( ', ', $invalid_fields ),
                implode( ', ', $valid_fields )
            )
        );
    }
    return true;
}

);
}
}
Loading
Loading