PluginProbe
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More / 2.2.0
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More v2.2.0
2.3.0 2.2.0 2.1.1 2.1.0 2.0.0 1.10.0 1.9.1 1.9.0 1.2.1 1.2.2 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 All 59 releases
storeengine / includes / api / taxes.php

taxes.php in StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More 2.2.0, at includes/api/taxes.php

661 lines 20.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php /** @noinspection PhpMissingReturnTypeInspection */
2
3 /**
4 * Tax api endpoints.
5 */
6
7 namespace StoreEngine\API;
8
9 use stdClass;
10 use StoreEngine\Classes\Tax;
11 use StoreEngine\Utils\Helper;
12 use WP_Error;
13 use WP_REST_Request;
14 use WP_REST_Response;
15 use WP_REST_Server;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit;
19 }
20
21 class Taxes extends AbstractRestApiController {
22
23 /**
24 * Route base.
25 *
26 * @var string
27 */
28 protected $rest_base = 'taxes';
29
30 public static function init() {
31 $self = new self();
32 add_action( 'rest_api_init', array( $self, 'register_routes' ) );
33 }
34
35 /**
36 * Register the routes for the objects of the controller.
37 */
38 public function register_routes() {
39 register_rest_route( $this->namespace, '/' . $this->rest_base, [
40 [
41 'methods' => WP_REST_Server::READABLE,
42 'callback' => [ $this, 'get_items' ],
43 'permission_callback' => [ $this, 'permissions_check' ],
44 'args' => $this->get_collection_params(),
45 ],
46 [
47 'methods' => WP_REST_Server::CREATABLE,
48 'callback' => [ $this, 'create_item' ],
49 'permission_callback' => [ $this, 'permissions_check' ],
50 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
51 ],
52 'schema' => [ $this, 'get_public_item_schema' ],
53 ] );
54
55 register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', [
56 'args' => [
57 'id' => [
58 'description' => __( 'Unique identifier for the resource.', 'storeengine' ),
59 'type' => 'integer',
60 ],
61 ],
62 [
63 'methods' => WP_REST_Server::READABLE,
64 'callback' => [ $this, 'get_item' ],
65 'permission_callback' => [ $this, 'permissions_check' ],
66 'args' => [
67 'context' => $this->get_context_param( [ 'default' => 'view' ] ),
68 ],
69 ],
70 [
71 'methods' => WP_REST_Server::EDITABLE,
72 'callback' => [ $this, 'update_item' ],
73 'permission_callback' => [ $this, 'permissions_check' ],
74 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
75 ],
76 [
77 'methods' => WP_REST_Server::DELETABLE,
78 'callback' => [ $this, 'delete_item' ],
79 'permission_callback' => [ $this, 'permissions_check' ],
80 'args' => [
81 'force' => [
82 'default' => false,
83 'type' => 'boolean',
84 'description' => __( 'Required to be true, as resource does not support trashing.', 'storeengine' ),
85 ],
86 ],
87 ],
88 'schema' => [ $this, 'get_public_item_schema' ],
89 ] );
90
91 register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', [
92 [
93 'methods' => WP_REST_Server::EDITABLE,
94 'callback' => [ $this, 'batch_items' ],
95 'permission_callback' => [ $this, 'permissions_check' ],
96 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
97 ],
98 'schema' => [ $this, 'get_public_batch_schema' ],
99 ] );
100 }
101
102 public function permissions_check() {
103 return Helper::check_rest_user_cap( 'manage_options' );
104 }
105
106 /**
107 * Get all taxes.
108 *
109 * @param WP_REST_Request $request Full details about the request.
110 *
111 * @return WP_Error|WP_REST_Response
112 */
113 public function get_items( $request ) {
114 global $wpdb;
115
116 $prepared_args = [];
117 $prepared_args['order'] = $request['order'];
118 $prepared_args['number'] = $request['per_page'];
119 if ( ! empty( $request['offset'] ) ) {
120 $prepared_args['offset'] = $request['offset'];
121 } else {
122 $prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
123 }
124 $orderby_possibles = [
125 'id' => 'tax_rate_id',
126 'order' => 'tax_rate_order',
127 'priority' => 'tax_rate_priority',
128 ];
129
130 $prepared_args['orderby'] = $orderby_possibles[ $request['orderby'] ];
131 $prepared_args['class'] = $request['class'] ?? '';
132
133 /**
134 * Filter arguments, before passing to $wpdb->get_results(), when querying taxes via the REST API.
135 *
136 * @param array $prepared_args Array of arguments for $wpdb->get_results().
137 * @param WP_REST_Request $request The current request.
138 */
139 $prepared_args = apply_filters( 'storeengine/rest_tax_query', $prepared_args, $request );
140
141 $orderby = sanitize_key( $prepared_args['orderby'] ) . ' ' . sanitize_key( $prepared_args['order'] );
142 $query = "
143 SELECT *
144 FROM {$wpdb->prefix}storeengine_tax_rates
145 %s
146 ORDER BY {$orderby}
147 LIMIT %%d, %%d
148 ";
149
150 $wpdb_prepare_args = [ $prepared_args['offset'], $prepared_args['number'] ];
151
152 // Filter by tax class.
153 if ( empty( $prepared_args['class'] ) ) {
154 $query = sprintf( $query, '' );
155 } else {
156 $class = 'standard' !== $prepared_args['class'] ? sanitize_title( $prepared_args['class'] ) : '';
157 array_unshift( $wpdb_prepare_args, $class );
158 $query = sprintf( $query, 'WHERE tax_rate_class = %s' );
159 }
160
161 // Query taxes.
162 // phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- query prepared.
163 $results = $wpdb->get_results( $wpdb->prepare( $query, $wpdb_prepare_args ) );
164 // phpcs:enable PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- query prepared.
165
166 $taxes = [];
167 foreach ( $results as $tax ) {
168 $data = $this->prepare_item_for_response( $tax, $request );
169 $taxes[] = $this->prepare_response_for_collection( $data );
170 }
171
172 $response = rest_ensure_response( $taxes );
173
174 $per_page = (int) $prepared_args['number'];
175 $page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
176
177 // Unset LIMIT args.
178 array_splice( $wpdb_prepare_args, - 2 );
179
180 // Count query.
181 $query = str_replace( [ 'SELECT *', 'LIMIT %d, %d' ], [ 'SELECT COUNT(*)', '' ], $query );
182
183 // phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- query prepared.
184 $total_taxes = (int) $wpdb->get_var( empty( $wpdb_prepare_args ) ? $query : $wpdb->prepare( $query, $wpdb_prepare_args ) );
185 // phpcs:enable PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- query prepared.
186
187 // Calculate totals.
188 $response->header( 'X-WP-Total', $total_taxes );
189 $max_pages = ceil( $total_taxes / $per_page );
190 $response->header( 'X-WP-TotalPages', (int) $max_pages );
191
192 $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
193 if ( $page > 1 ) {
194 $prev_page = $page - 1;
195 if ( $prev_page > $max_pages ) {
196 $prev_page = $max_pages;
197 }
198 $prev_link = add_query_arg( 'page', $prev_page, $base );
199 $response->link_header( 'prev', $prev_link );
200 }
201 if ( $max_pages > $page ) {
202 $next_page = $page + 1;
203 $next_link = add_query_arg( 'page', $next_page, $base );
204 $response->link_header( 'next', $next_link );
205 }
206
207 return $response;
208 }
209
210 /**
211 * Take tax data from the request and return the updated or newly created rate.
212 *
213 * @param WP_REST_Request $request Full details about the request.
214 * @param stdClass|null $current Existing tax object.
215 *
216 * @return object
217 */
218 protected function create_or_update_tax( $request, $current = null ) {
219 $id = absint( isset( $request['id'] ) ? $request['id'] : 0 );
220 $data = array();
221 $fields = array(
222 'tax_rate_country',
223 'tax_rate_state',
224 'tax_rate',
225 'tax_rate_name',
226 'tax_rate_priority',
227 'tax_rate_compound',
228 'tax_rate_shipping',
229 'tax_rate_order',
230 'tax_rate_class',
231 );
232
233 foreach ( $fields as $field ) {
234 // Keys via API differ from the stored names returned by _get_tax_rate.
235 $key = 'tax_rate' === $field ? 'rate' : str_replace( 'tax_rate_', '', $field );
236
237 // Remove data that was not posted.
238 if ( ! isset( $request[ $key ] ) ) {
239 continue;
240 }
241
242 // Test new data against current data.
243 if ( $current && $current->$field === $request[ $key ] ) {
244 continue;
245 }
246
247 // Add to data array.
248 switch ( $key ) {
249 case 'tax_rate_priority':
250 case 'tax_rate_compound':
251 case 'tax_rate_shipping':
252 case 'tax_rate_order':
253 $data[ $field ] = absint( $request[ $key ] );
254 break;
255 case 'tax_rate_class':
256 $data[ $field ] = 'standard' !== $request['tax_rate_class'] ? $request['tax_rate_class'] : '';
257 break;
258 default:
259 $data[ $field ] = sanitize_text_field( $request[ $key ] );
260 break;
261 }
262 }
263
264 if ( ! $id ) {
265 $id = Tax::_insert_tax_rate( $data );
266 } elseif ( $data ) {
267 Tax::_update_tax_rate( $id, $data );
268 }
269
270 // Add locales.
271 if ( ! empty( $request['postcode'] ) ) {
272 Tax::_update_tax_rate_postcodes( $id, sanitize_text_field( $request['postcode'] ) );
273 }
274 if ( ! empty( $request['city'] ) ) {
275 Tax::_update_tax_rate_cities( $id, sanitize_text_field( $request['city'] ) );
276 }
277
278 return Tax::_get_tax_rate( $id, OBJECT );
279 }
280
281 /**
282 * Create a single tax.
283 *
284 * @param WP_REST_Request $request Full details about the request.
285 *
286 * @return WP_Error|WP_REST_Response
287 */
288 public function create_item( $request ) {
289 if ( ! empty( $request['id'] ) ) {
290 return new WP_Error( 'rest_tax_exists', __( 'Cannot create existing resource.', 'storeengine' ), array( 'status' => 400 ) );
291 }
292
293 $tax = $this->create_or_update_tax( $request );
294
295 $this->update_additional_fields_for_object( $tax, $request );
296
297 /**
298 * Fires after a tax is created or updated via the REST API.
299 *
300 * @param stdClass $tax Data used to create the tax.
301 * @param WP_REST_Request $request Request object.
302 * @param boolean $creating True when creating tax, false when updating tax.
303 */
304 do_action( 'storeengine/rest_insert_tax', $tax, $request, true );
305
306 $request->set_param( 'context', 'edit' );
307 $response = $this->prepare_item_for_response( $tax, $request );
308 $response = rest_ensure_response( $response );
309 $response->set_status( 201 );
310 $response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $tax->tax_rate_id ) ) );
311
312 return $response;
313 }
314
315 /**
316 * Get a single tax.
317 *
318 * @param WP_REST_Request $request Full details about the request.
319 *
320 * @return WP_Error|WP_REST_Response
321 */
322 public function get_item( $request ) {
323 $id = (int) $request['id'];
324 $tax_obj = Tax::_get_tax_rate( $id, OBJECT );
325
326 if ( empty( $id ) || empty( $tax_obj ) ) {
327 return new WP_Error( 'rest_invalid_id', __( 'Invalid resource ID.', 'storeengine' ), array( 'status' => 404 ) );
328 }
329
330 $tax = $this->prepare_item_for_response( $tax_obj, $request );
331
332 return rest_ensure_response( $tax );
333 }
334
335 /**
336 * Update a single tax.
337 *
338 * @param WP_REST_Request $request Full details about the request.
339 *
340 * @return WP_Error|WP_REST_Response
341 */
342 public function update_item( $request ) {
343 $id = (int) $request['id'];
344 $tax_obj = Tax::_get_tax_rate( $id, OBJECT );
345
346 if ( empty( $id ) || empty( $tax_obj ) ) {
347 return new WP_Error( 'rest_invalid_id', __( 'Invalid resource ID.', 'storeengine' ), array( 'status' => 404 ) );
348 }
349
350 $tax = $this->create_or_update_tax( $request, $tax_obj );
351
352 $this->update_additional_fields_for_object( $tax, $request );
353
354 /**
355 * Fires after a tax is created or updated via the REST API.
356 *
357 * @param stdClass $tax Data used to create the tax.
358 * @param WP_REST_Request $request Request object.
359 * @param boolean $creating True when creating tax, false when updating tax.
360 */
361 do_action( 'storeengine/rest_insert_tax', $tax, $request, false );
362
363 $request->set_param( 'context', 'edit' );
364 $response = $this->prepare_item_for_response( $tax, $request );
365
366 return rest_ensure_response( $response );
367 }
368
369 /**
370 * Delete a single tax.
371 *
372 * @param WP_REST_Request $request Full details about the request.
373 *
374 * @return WP_Error|WP_REST_Response
375 */
376 public function delete_item( $request ) {
377 global $wpdb;
378
379 $id = (int) $request['id'];
380 $force = isset( $request['force'] ) && (bool) $request['force'];
381
382 // We don't support trashing for this type, error out.
383 if ( ! $force ) {
384 return new WP_Error( 'rest_trash_not_supported', __( 'Taxes do not support trashing.', 'storeengine' ), array( 'status' => 501 ) );
385 }
386
387 $tax = Tax::_get_tax_rate( $id, OBJECT );
388
389 if ( empty( $id ) || empty( $tax ) ) {
390 return new WP_Error( 'rest_invalid_id', __( 'Invalid resource ID.', 'storeengine' ), array( 'status' => 400 ) );
391 }
392
393 $request->set_param( 'context', 'edit' );
394 $response = $this->prepare_item_for_response( $tax, $request );
395
396 Tax::_delete_tax_rate( $id );
397
398 if ( 0 === $wpdb->rows_affected ) {
399 return new WP_Error( 'rest_cannot_delete', __( 'The resource cannot be deleted.', 'storeengine' ), array( 'status' => 500 ) );
400 }
401
402 /**
403 * Fires after a tax is deleted via the REST API.
404 *
405 * @param stdClass $tax The tax data.
406 * @param WP_REST_Response $response The response returned from the API.
407 * @param WP_REST_Request $request The request sent to the API.
408 */
409 do_action( 'storeengine/rest_delete_tax', $tax, $response, $request );
410
411 return $response;
412 }
413
414 /**
415 * Prepare a single tax output for response.
416 *
417 * @param stdClass $item Tax object.
418 * @param WP_REST_Request $request Request object.
419 *
420 * @return WP_REST_Response $response Response data.
421 */
422 public function prepare_item_for_response( $item, $request ) {
423 $data = [
424 'id' => (int) $item->tax_rate_id,
425 'country' => $item->tax_rate_country,
426 'state' => $item->tax_rate_state,
427 'postcode' => '',
428 'city' => '',
429 'rate' => (float) $item->tax_rate,
430 'name' => $item->tax_rate_name,
431 'priority' => (int) $item->tax_rate_priority,
432 'compound' => (bool) $item->tax_rate_compound,
433 'shipping' => (bool) $item->tax_rate_shipping,
434 'order' => (int) $item->tax_rate_order,
435 'class' => $item->tax_rate_class ?: 'standard',
436 ];
437
438 $data = $this->add_tax_rate_locales( $data, $item );
439
440 $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
441 $data = $this->add_additional_fields_to_object( $data, $request );
442 $data = $this->filter_response_by_context( $data, $context );
443
444 // Wrap the data in a response object.
445 $response = rest_ensure_response( $data );
446
447 $response->add_links( $this->prepare_links( $item, $request ) );
448
449 /**
450 * Filter tax object returned from the REST API.
451 *
452 * @param WP_REST_Response $response The response object.
453 * @param stdClass $item Tax object used to create response.
454 * @param WP_REST_Request $request Request object.
455 */
456 return apply_filters( 'storeengine/rest_prepare_tax', $response, $item, $request );
457 }
458
459 /**
460 * Prepare links for the request.
461 *
462 * @param $item
463 * @param WP_REST_Request $request *
464 *
465 * @return array Links for the given tax.
466 */
467 protected function prepare_links( $item, WP_REST_Request $request ): array {
468 return [
469 'self' => [
470 'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $item->tax_rate_id ) ),
471 ],
472 'collection' => [
473 'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
474 ],
475 ];
476 }
477
478 /**
479 * Add tax rate locales to the response array.
480 *
481 * @param array $data Response data.
482 * @param ?stdClass $tax Tax object.
483 *
484 * @return array
485 */
486 protected function add_tax_rate_locales( array $data, ?stdClass $tax ): array {
487 global $wpdb;
488
489 if ( ! is_wp_error( $tax ) && ! is_null( $tax ) ) {
490 // Get locales from a tax rate.
491 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
492 $locales = $wpdb->get_results(
493 $wpdb->prepare(
494 "
495 SELECT location_code, location_type
496 FROM {$wpdb->prefix}storeengine_tax_rate_locations
497 WHERE tax_rate_id = %d
498 ",
499 $tax->tax_rate_id
500 )
501 );
502 // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
503
504 foreach ( $locales as $locale ) {
505 $data[ $locale->location_type ] = $locale->location_code;
506 }
507 }
508
509 return $data;
510 }
511
512 /**
513 * Get the Taxes schema, conforming to JSON Schema.
514 *
515 * @return array
516 */
517 public function get_item_schema() {
518 $schema = [
519 '$schema' => 'http://json-schema.org/draft-04/schema#',
520 'title' => 'tax',
521 'type' => 'object',
522 'properties' => [
523 'id' => [
524 'description' => __( 'Unique identifier for the resource.', 'storeengine' ),
525 'type' => 'integer',
526 'context' => [ 'view', 'edit' ],
527 'readonly' => true,
528 ],
529 'country' => [
530 'description' => __( 'Country ISO 3166 code.', 'storeengine' ),
531 'type' => 'string',
532 'context' => [ 'view', 'edit' ],
533 ],
534 'state' => [
535 'description' => __( 'State code.', 'storeengine' ),
536 'type' => 'string',
537 'context' => [ 'view', 'edit' ],
538 ],
539 'postcode' => [
540 'description' => __( 'Postcode / ZIP.', 'storeengine' ),
541 'type' => 'string',
542 'context' => [ 'view', 'edit' ],
543 ],
544 'city' => [
545 'description' => __( 'City name.', 'storeengine' ),
546 'type' => 'string',
547 'context' => [ 'view', 'edit' ],
548 ],
549 'rate' => [
550 'description' => __( 'Tax rate.', 'storeengine' ),
551 'type' => 'float',
552 'context' => [ 'view', 'edit' ],
553 ],
554 'name' => [
555 'description' => __( 'Tax rate name.', 'storeengine' ),
556 'type' => 'string',
557 'context' => [ 'view', 'edit' ],
558 ],
559 'priority' => [
560 'description' => __( 'Tax priority.', 'storeengine' ),
561 'type' => 'integer',
562 'default' => 1,
563 'context' => [ 'view', 'edit' ],
564 ],
565 'compound' => [
566 'description' => __( 'Whether or not this is a compound rate.', 'storeengine' ),
567 'type' => 'boolean',
568 'default' => false,
569 'context' => [ 'view', 'edit' ],
570 ],
571 'shipping' => [
572 'description' => __( 'Whether or not this tax rate also gets applied to shipping.', 'storeengine' ),
573 'type' => 'boolean',
574 'default' => true,
575 'context' => [ 'view', 'edit' ],
576 ],
577 'order' => [
578 'description' => __( 'Indicates the order that will appear in queries.', 'storeengine' ),
579 'type' => 'integer',
580 'context' => [ 'view', 'edit' ],
581 ],
582 'class' => [ // @TODO remove experimental after implementation is completed.
583 'description' => __( 'Tax class (experimental).', 'storeengine' ),
584 'type' => 'string',
585 'default' => 'standard',
586 'enum' => array_merge( [ 'standard' ], \StoreEngine\Classes\Tax::get_tax_class_slugs() ),
587 'context' => [ 'view', 'edit' ],
588 ],
589 ],
590 ];
591
592 return $this->add_additional_fields_schema( $schema );
593 }
594
595 /**
596 * Get the query params for collections.
597 *
598 * @return array
599 */
600 public function get_collection_params() {
601 $params = [];
602 $params['context'] = $this->get_context_param();
603 $params['context']['default'] = 'view';
604
605 $params['page'] = [
606 'description' => __( 'Current page of the collection.', 'storeengine' ),
607 'type' => 'integer',
608 'default' => 1,
609 'sanitize_callback' => 'absint',
610 'validate_callback' => 'rest_validate_request_arg',
611 'minimum' => 1,
612 ];
613 $params['per_page'] = [
614 'description' => __( 'Maximum number of items to be returned in result set.', 'storeengine' ),
615 'type' => 'integer',
616 'default' => 10,
617 'minimum' => 1,
618 'maximum' => 100,
619 'sanitize_callback' => 'absint',
620 'validate_callback' => 'rest_validate_request_arg',
621 ];
622 $params['offset'] = [
623 'description' => __( 'Offset the result set by a specific number of items.', 'storeengine' ),
624 'type' => 'integer',
625 'sanitize_callback' => 'absint',
626 'validate_callback' => 'rest_validate_request_arg',
627 ];
628 $params['order'] = [
629 'default' => 'asc',
630 'description' => __( 'Order sort attribute ascending or descending.', 'storeengine' ),
631 'enum' => [ 'asc', 'desc' ],
632 'sanitize_callback' => 'sanitize_key',
633 'type' => 'string',
634 'validate_callback' => 'rest_validate_request_arg',
635 ];
636 $params['orderby'] = [
637 'default' => 'order',
638 'description' => __( 'Sort collection by object attribute.', 'storeengine' ),
639 'enum' => [
640 'id',
641 'order',
642 'priority',
643 ],
644 'sanitize_callback' => 'sanitize_key',
645 'type' => 'string',
646 'validate_callback' => 'rest_validate_request_arg',
647 ];
648 $params['class'] = [ // @TODO remove experimental after implementation is completed.
649 'description' => __( 'Sort by tax class (experimental).', 'storeengine' ),
650 'enum' => array_merge( [ 'standard' ], \StoreEngine\Classes\Tax::get_tax_class_slugs() ),
651 'sanitize_callback' => 'sanitize_title',
652 'type' => 'string',
653 'validate_callback' => 'rest_validate_request_arg',
654 ];
655
656 return $params;
657 }
658 }
659
660 // End of file tax.php.
661