File manager - Edit - /home/monara/public_html/test.athavaneng.com/Routes.tar
Back
RouteInterface.php 0000644 00000000520 15073235735 0010203 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes; /** * RouteInterface. */ interface RouteInterface { /** * Get the path of this REST route. * * @return string */ public function get_path(); /** * Get arguments for this REST route. * * @return array An array of endpoints. */ public function get_args(); } V1/CartUpdateItem.php 0000644 00000003204 15073235735 0010427 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; /** * CartUpdateItem class. */ class CartUpdateItem extends AbstractCartRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'cart-update-item'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/update-item'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'key' => [ 'description' => __( 'Unique identifier (key) for the cart item to update.', 'woocommerce' ), 'type' => 'string', ], 'quantity' => [ 'description' => __( 'New quantity of the item in the cart.', 'woocommerce' ), 'type' => 'integer', ], ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Handle the request and return a valid response for this endpoint. * . * * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { $cart = $this->cart_controller->get_cart_instance(); if ( isset( $request['quantity'] ) ) { $this->cart_controller->set_cart_item_quantity( $request['key'], $request['quantity'] ); } return rest_ensure_response( $this->schema->get_item_response( $cart ) ); } } V1/CartItems.php 0000644 00000007255 15073235735 0007461 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; /** * CartItems class. */ class CartItems extends AbstractCartRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'cart-items'; /** * The routes schema. * * @var string */ const SCHEMA_TYPE = 'cart-item'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/items'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'context' => $this->get_context_param( [ 'default' => 'view' ] ), ], ], [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => array( $this, 'get_response' ), 'permission_callback' => '__return_true', 'args' => $this->schema->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ), ], [ 'methods' => \WP_REST_Server::DELETABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Get a collection of cart items. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $cart_items = $this->cart_controller->get_cart_items(); $items = []; foreach ( $cart_items as $cart_item ) { $data = $this->prepare_item_for_response( $cart_item, $request ); $items[] = $this->prepare_response_for_collection( $data ); } $response = rest_ensure_response( $items ); return $response; } /** * Creates one item from the collection. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { // Do not allow key to be specified during creation. if ( ! empty( $request['key'] ) ) { throw new RouteException( 'woocommerce_rest_cart_item_exists', __( 'Cannot create an existing cart item.', 'woocommerce' ), 400 ); } $result = $this->cart_controller->add_to_cart( [ 'id' => $request['id'], 'quantity' => $request['quantity'], 'variation' => $request['variation'], ] ); $response = rest_ensure_response( $this->prepare_item_for_response( $this->cart_controller->get_cart_item( $result ), $request ) ); $response->set_status( 201 ); return $response; } /** * Deletes all items in the cart. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_delete_response( \WP_REST_Request $request ) { $this->cart_controller->empty_cart(); return new \WP_REST_Response( [], 200 ); } /** * Prepare links for the request. * * @param array $cart_item Object to prepare. * @param \WP_REST_Request $request Request object. * @return array */ protected function prepare_links( $cart_item, $request ) { $base = $this->get_namespace() . $this->get_path(); $links = array( 'self' => array( 'href' => rest_url( trailingslashit( $base ) . $cart_item['key'] ), ), 'collection' => array( 'href' => rest_url( $base ), ), ); return $links; } } V1/Batch.php 0000644 00000006522 15073235735 0006603 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Routes\RouteInterface; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; use WP_REST_Request; use WP_REST_Response; /** * Batch Route class. */ class Batch extends AbstractRoute implements RouteInterface { /** * The route identifier. * * @var string */ const IDENTIFIER = 'batch'; /** * The schema item identifier. * * @var string */ const SCHEMA_TYPE = 'batch'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/batch'; } /** * Get arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return array( 'callback' => [ $this, 'get_response' ], 'methods' => 'POST', 'permission_callback' => '__return_true', 'args' => array( 'validation' => array( 'type' => 'string', 'enum' => array( 'require-all-validate', 'normal' ), 'default' => 'normal', ), 'requests' => array( 'required' => true, 'type' => 'array', 'maxItems' => 25, 'items' => array( 'type' => 'object', 'properties' => array( 'method' => array( 'type' => 'string', 'enum' => array( 'POST', 'PUT', 'PATCH', 'DELETE' ), 'default' => 'POST', ), 'path' => array( 'type' => 'string', 'required' => true, ), 'body' => array( 'type' => 'object', 'properties' => array(), 'additionalProperties' => true, ), 'headers' => array( 'type' => 'object', 'properties' => array(), 'additionalProperties' => array( 'type' => array( 'string', 'array' ), 'items' => array( 'type' => 'string', ), ), ), ), ), ), ), ); } /** * Get the route response. * * @see WP_REST_Server::serve_batch_request_v1 * https://developer.wordpress.org/reference/classes/wp_rest_server/serve_batch_request_v1/ * * @throws RouteException On error. * * @param WP_REST_Request $request Request object. * @return WP_REST_Response */ public function get_response( WP_REST_Request $request ) { try { foreach ( $request['requests'] as $args ) { if ( ! stristr( $args['path'], 'wc/store' ) ) { throw new RouteException( 'woocommerce_rest_invalid_path', __( 'Invalid path provided.', 'woocommerce' ), 400 ); } } $response = rest_get_server()->serve_batch_request_v1( $request ); } catch ( RouteException $error ) { $response = $this->get_route_error_response( $error->getErrorCode(), $error->getMessage(), $error->getCode(), $error->getAdditionalData() ); } catch ( \Exception $error ) { $response = $this->get_route_error_response( 'woocommerce_rest_unknown_server_error', $error->getMessage(), 500 ); } if ( is_wp_error( $response ) ) { $response = $this->error_to_response( $response ); } $nonce = wp_create_nonce( 'wc_store_api' ); $response->header( 'Nonce', $nonce ); $response->header( 'X-WC-Store-API-Nonce', $nonce ); $response->header( 'Nonce-Timestamp', time() ); $response->header( 'User-ID', get_current_user_id() ); return $response; } } V1/CartRemoveItem.php 0000644 00000004146 15073235735 0010450 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Utilities\DraftOrderTrait; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; /** * CartRemoveItem class. */ class CartRemoveItem extends AbstractCartRoute { use DraftOrderTrait; /** * The route identifier. * * @var string */ const IDENTIFIER = 'cart-remove-item'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/remove-item'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'key' => [ 'description' => __( 'Unique identifier (key) for the cart item.', 'woocommerce' ), 'type' => 'string', ], ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Handle the request and return a valid response for this endpoint. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { $cart = $this->cart_controller->get_cart_instance(); $cart_item = $this->cart_controller->get_cart_item( $request['key'] ); if ( empty( $cart_item ) ) { throw new RouteException( 'woocommerce_rest_cart_invalid_key', __( 'Cart item no longer exists or is invalid.', 'woocommerce' ), 409 ); } $cart->remove_cart_item( $request['key'] ); $this->maybe_release_stock(); return rest_ensure_response( $this->schema->get_item_response( $cart ) ); } /** * If there is a draft order, releases stock. * * @return void */ protected function maybe_release_stock() { $draft_order_id = $this->get_draft_order_id(); if ( ! $draft_order_id ) { return; } wc_release_stock_for_order( $draft_order_id ); } } V1/CartApplyCoupon.php 0000644 00000003565 15073235735 0010651 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; /** * CartApplyCoupon class. */ class CartApplyCoupon extends AbstractCartRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'cart-apply-coupon'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/apply-coupon'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'code' => [ 'description' => __( 'Unique identifier for the coupon within the cart.', 'woocommerce' ), 'type' => 'string', ], ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Handle the request and return a valid response for this endpoint. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { if ( ! wc_coupons_enabled() ) { throw new RouteException( 'woocommerce_rest_cart_coupon_disabled', __( 'Coupons are disabled.', 'woocommerce' ), 404 ); } $cart = $this->cart_controller->get_cart_instance(); $coupon_code = wc_format_coupon_code( wp_unslash( $request['code'] ) ); try { $this->cart_controller->apply_coupon( $coupon_code ); } catch ( \WC_REST_Exception $e ) { throw new RouteException( $e->getErrorCode(), $e->getMessage(), $e->getCode() ); } return rest_ensure_response( $this->schema->get_item_response( $cart ) ); } } V1/ProductCollectionData.php 0000644 00000011650 15073235735 0012006 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Utilities\ProductQueryFilters; /** * ProductCollectionData route. * Get aggregate data from a collection of products. * * Supports the same parameters as /products, but returns a different response. */ class ProductCollectionData extends AbstractRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'product-collection-data'; /** * The routes schema. * * @var string */ const SCHEMA_TYPE = 'product-collection-data'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products/collection-data'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => $this->get_collection_params(), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get a collection of posts and add the post title filter option to \WP_Query. * * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $data = [ 'min_price' => null, 'max_price' => null, 'attribute_counts' => null, 'stock_status_counts' => null, 'rating_counts' => null, ]; $filters = new ProductQueryFilters(); if ( ! empty( $request['calculate_price_range'] ) ) { $filter_request = clone $request; $filter_request->set_param( 'min_price', null ); $filter_request->set_param( 'max_price', null ); $price_results = $filters->get_filtered_price( $filter_request ); $data['min_price'] = $price_results->min_price; $data['max_price'] = $price_results->max_price; } if ( ! empty( $request['calculate_stock_status_counts'] ) ) { $filter_request = clone $request; $counts = $filters->get_stock_status_counts( $filter_request ); $data['stock_status_counts'] = []; foreach ( $counts as $key => $value ) { $data['stock_status_counts'][] = (object) [ 'status' => $key, 'count' => $value, ]; } } if ( ! empty( $request['calculate_attribute_counts'] ) ) { foreach ( $request['calculate_attribute_counts'] as $attributes_to_count ) { if ( ! isset( $attributes_to_count['taxonomy'] ) ) { continue; } $counts = $filters->get_attribute_counts( $request, $attributes_to_count['taxonomy'] ); foreach ( $counts as $key => $value ) { $data['attribute_counts'][] = (object) [ 'term' => $key, 'count' => $value, ]; } } } if ( ! empty( $request['calculate_rating_counts'] ) ) { $filter_request = clone $request; $counts = $filters->get_rating_counts( $filter_request ); $data['rating_counts'] = []; foreach ( $counts as $key => $value ) { $data['rating_counts'][] = (object) [ 'rating' => $key, 'count' => $value, ]; } } return rest_ensure_response( $this->schema->get_item_response( $data ) ); } /** * Get the query params for collections of products. * * @return array */ public function get_collection_params() { $params = ( new Products( $this->schema_controller, $this->schema ) )->get_collection_params(); $params['calculate_price_range'] = [ 'description' => __( 'If true, calculates the minimum and maximum product prices for the collection.', 'woocommerce' ), 'type' => 'boolean', 'default' => false, ]; $params['calculate_stock_status_counts'] = [ 'description' => __( 'If true, calculates stock counts for products in the collection.', 'woocommerce' ), 'type' => 'boolean', 'default' => false, ]; $params['calculate_attribute_counts'] = [ 'description' => __( 'If requested, calculates attribute term counts for products in the collection.', 'woocommerce' ), 'type' => 'array', 'items' => [ 'type' => 'object', 'properties' => [ 'taxonomy' => [ 'description' => __( 'Taxonomy name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'query_type' => [ 'description' => __( 'Filter condition being performed which may affect counts. Valid values include "and" and "or".', 'woocommerce' ), 'type' => 'string', 'enum' => [ 'and', 'or' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], 'default' => [], ]; $params['calculate_rating_counts'] = [ 'description' => __( 'If true, calculates rating counts for products in the collection.', 'woocommerce' ), 'type' => 'boolean', 'default' => false, ]; return $params; } } V1/CartItemsByKey.php 0000644 00000007666 15073235735 0010433 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; /** * CartItemsByKey class. */ class CartItemsByKey extends AbstractCartRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'cart-items-by-key'; /** * The routes schema. * * @var string */ const SCHEMA_TYPE = 'cart-item'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/items/(?P<key>[\w-]{32})'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ 'args' => [ 'key' => [ 'description' => __( 'Unique identifier for the item within the cart.', 'woocommerce' ), 'type' => 'string', ], ], [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'context' => $this->get_context_param( [ 'default' => 'view' ] ), ], ], [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => array( $this, 'get_response' ), 'permission_callback' => '__return_true', 'args' => $this->schema->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ), ], [ 'methods' => \WP_REST_Server::DELETABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Get a single cart items. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $cart_item = $this->cart_controller->get_cart_item( $request['key'] ); if ( empty( $cart_item ) ) { throw new RouteException( 'woocommerce_rest_cart_invalid_key', __( 'Cart item does not exist.', 'woocommerce' ), 409 ); } $data = $this->prepare_item_for_response( $cart_item, $request ); $response = rest_ensure_response( $data ); return $response; } /** * Update a single cart item. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_update_response( \WP_REST_Request $request ) { $cart = $this->cart_controller->get_cart_instance(); if ( isset( $request['quantity'] ) ) { $this->cart_controller->set_cart_item_quantity( $request['key'], $request['quantity'] ); } return rest_ensure_response( $this->prepare_item_for_response( $this->cart_controller->get_cart_item( $request['key'] ), $request ) ); } /** * Delete a single cart item. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_delete_response( \WP_REST_Request $request ) { $cart = $this->cart_controller->get_cart_instance(); $cart_item = $this->cart_controller->get_cart_item( $request['key'] ); if ( empty( $cart_item ) ) { throw new RouteException( 'woocommerce_rest_cart_invalid_key', __( 'Cart item does not exist.', 'woocommerce' ), 409 ); } $cart->remove_cart_item( $request['key'] ); return new \WP_REST_Response( null, 204 ); } /** * Prepare links for the request. * * @param array $cart_item Object to prepare. * @param \WP_REST_Request $request Request object. * @return array */ protected function prepare_links( $cart_item, $request ) { $base = $this->get_namespace() . $this->get_path(); $links = array( 'self' => array( 'href' => rest_url( trailingslashit( $base ) . $cart_item['key'] ), ), 'collection' => array( 'href' => rest_url( $base ), ), ); return $links; } } V1/ProductsById.php 0000644 00000003323 15073235735 0010131 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; /** * ProductsById class. */ class ProductsById extends AbstractRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'products-by-id'; /** * The routes schema. * * @var string */ const SCHEMA_TYPE = 'product'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products/(?P<id>[\d]+)'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ 'args' => array( 'id' => array( 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), 'type' => 'integer', ), ), [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view', ) ), ), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get a single item. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $object = wc_get_product( (int) $request['id'] ); if ( ! $object || 0 === $object->get_id() ) { throw new RouteException( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce' ), 404 ); } return rest_ensure_response( $this->schema->get_item_response( $object ) ); } } V1/Products.php 0000644 00000034433 15073235735 0007367 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Utilities\Pagination; use Automattic\WooCommerce\StoreApi\Utilities\ProductQuery; /** * Products class. */ class Products extends AbstractRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'products'; /** * The routes schema. * * @var string */ const SCHEMA_TYPE = 'product'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => $this->get_collection_params(), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get a collection of posts and add the post title filter option to \WP_Query. * * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $response = new \WP_REST_Response(); $product_query = new ProductQuery(); // Only get objects during GET requests. if ( \WP_REST_Server::READABLE === $request->get_method() ) { $query_results = $product_query->get_objects( $request ); $response_objects = []; foreach ( $query_results['objects'] as $object ) { $data = rest_ensure_response( $this->schema->get_item_response( $object ) ); $response_objects[] = $this->prepare_response_for_collection( $data ); } $response->set_data( $response_objects ); } else { $query_results = $product_query->get_results( $request ); } $response = ( new Pagination() )->add_headers( $response, $request, $query_results['total'], $query_results['pages'] ); $response->header( 'Last-Modified', $product_query->get_last_modified() ); return $response; } /** * Prepare links for the request. * * @param \WC_Product $item Product object. * @param \WP_REST_Request $request Request object. * @return array */ protected function prepare_links( $item, $request ) { $links = array( 'self' => array( 'href' => rest_url( $this->get_namespace() . $this->get_path() . '/' . $item->get_id() ), ), 'collection' => array( 'href' => rest_url( $this->get_namespace() . $this->get_path() ), ), ); if ( $item->get_parent_id() ) { $links['up'] = array( 'href' => rest_url( $this->get_namespace() . $this->get_path() . '/' . $item->get_parent_id() ), ); } return $links; } /** * Get the query params for collections of products. * * @return array */ public function get_collection_params() { $params = []; $params['context'] = $this->get_context_param(); $params['context']['default'] = 'view'; $params['page'] = array( 'description' => __( 'Current page of the collection.', 'woocommerce' ), 'type' => 'integer', 'default' => 1, 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', 'minimum' => 1, ); $params['per_page'] = array( 'description' => __( 'Maximum number of items to be returned in result set. Defaults to no limit if left blank.', 'woocommerce' ), 'type' => 'integer', 'default' => 10, 'minimum' => 0, 'maximum' => 100, 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); $params['search'] = array( 'description' => __( 'Limit results to those matching a string.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => 'rest_validate_request_arg', ); $params['slug'] = array( 'description' => __( 'Limit result set to products with specific slug(s). Use commas to separate.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => 'rest_validate_request_arg', ); $params['after'] = array( 'description' => __( 'Limit response to resources created after a given ISO8601 compliant date.', 'woocommerce' ), 'type' => 'string', 'format' => 'date-time', 'validate_callback' => 'rest_validate_request_arg', ); $params['before'] = array( 'description' => __( 'Limit response to resources created before a given ISO8601 compliant date.', 'woocommerce' ), 'type' => 'string', 'format' => 'date-time', 'validate_callback' => 'rest_validate_request_arg', ); $params['date_column'] = array( 'description' => __( 'When limiting response using after/before, which date column to compare against.', 'woocommerce' ), 'type' => 'string', 'default' => 'date', 'enum' => array( 'date', 'date_gmt', 'modified', 'modified_gmt', ), 'validate_callback' => 'rest_validate_request_arg', ); $params['exclude'] = array( 'description' => __( 'Ensure result set excludes specific IDs.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => [], 'sanitize_callback' => 'wp_parse_id_list', ); $params['include'] = array( 'description' => __( 'Limit result set to specific ids.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => [], 'sanitize_callback' => 'wp_parse_id_list', ); $params['offset'] = array( 'description' => __( 'Offset the result set by a specific number of items.', 'woocommerce' ), 'type' => 'integer', 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); $params['order'] = array( 'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ), 'type' => 'string', 'default' => 'desc', 'enum' => array( 'asc', 'desc' ), 'validate_callback' => 'rest_validate_request_arg', ); $params['orderby'] = array( 'description' => __( 'Sort collection by object attribute.', 'woocommerce' ), 'type' => 'string', 'default' => 'date', 'enum' => array( 'date', 'modified', 'id', 'include', 'title', 'slug', 'price', 'popularity', 'rating', 'menu_order', 'comment_count', ), 'validate_callback' => 'rest_validate_request_arg', ); $params['parent'] = array( 'description' => __( 'Limit result set to those of particular parent IDs.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => [], 'sanitize_callback' => 'wp_parse_id_list', ); $params['parent_exclude'] = array( 'description' => __( 'Limit result set to all items except those of a particular parent ID.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'sanitize_callback' => 'wp_parse_id_list', 'default' => [], ); $params['type'] = array( 'description' => __( 'Limit result set to products assigned a specific type.', 'woocommerce' ), 'type' => 'string', 'enum' => array_merge( array_keys( wc_get_product_types() ), [ 'variation' ] ), 'sanitize_callback' => 'sanitize_key', 'validate_callback' => 'rest_validate_request_arg', ); $params['sku'] = array( 'description' => __( 'Limit result set to products with specific SKU(s). Use commas to separate.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => 'rest_validate_request_arg', ); $params['featured'] = array( 'description' => __( 'Limit result set to featured products.', 'woocommerce' ), 'type' => 'boolean', 'sanitize_callback' => 'wc_string_to_bool', 'validate_callback' => 'rest_validate_request_arg', ); $params['category'] = array( 'description' => __( 'Limit result set to products assigned a specific category ID.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'wp_parse_id_list', 'validate_callback' => 'rest_validate_request_arg', ); $params['category_operator'] = array( 'description' => __( 'Operator to compare product category terms.', 'woocommerce' ), 'type' => 'string', 'enum' => [ 'in', 'not_in', 'and' ], 'default' => 'in', 'sanitize_callback' => 'sanitize_key', 'validate_callback' => 'rest_validate_request_arg', ); // If the $_REQUEST contains a taxonomy query, add it to the params and sanitize it. foreach ( $_REQUEST as $param => $value ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( str_starts_with( $param, '_unstable_tax_' ) && ! str_ends_with( $param, '_operator' ) ) { $params[ $param ] = array( 'description' => __( 'Limit result set to products assigned a specific category ID.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'wp_parse_id_list', 'validate_callback' => 'rest_validate_request_arg', ); } if ( str_starts_with( $param, '_unstable_tax_' ) && str_ends_with( $param, '_operator' ) ) { $params[ $param ] = array( 'description' => __( 'Operator to compare product category terms.', 'woocommerce' ), 'type' => 'string', 'enum' => [ 'in', 'not_in', 'and' ], 'default' => 'in', 'sanitize_callback' => 'sanitize_key', 'validate_callback' => 'rest_validate_request_arg', ); } } $params['tag'] = array( 'description' => __( 'Limit result set to products assigned a specific tag ID.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'wp_parse_id_list', 'validate_callback' => 'rest_validate_request_arg', ); $params['tag_operator'] = array( 'description' => __( 'Operator to compare product tags.', 'woocommerce' ), 'type' => 'string', 'enum' => [ 'in', 'not_in', 'and' ], 'default' => 'in', 'sanitize_callback' => 'sanitize_key', 'validate_callback' => 'rest_validate_request_arg', ); $params['on_sale'] = array( 'description' => __( 'Limit result set to products on sale.', 'woocommerce' ), 'type' => 'boolean', 'sanitize_callback' => 'wc_string_to_bool', 'validate_callback' => 'rest_validate_request_arg', ); $params['min_price'] = array( 'description' => __( 'Limit result set to products based on a minimum price, provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => 'rest_validate_request_arg', ); $params['max_price'] = array( 'description' => __( 'Limit result set to products based on a maximum price, provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => 'rest_validate_request_arg', ); $params['stock_status'] = array( 'description' => __( 'Limit result set to products with specified stock status.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'string', 'enum' => array_keys( wc_get_product_stock_status_options() ), 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => 'rest_validate_request_arg', ), 'default' => [], ); $params['attributes'] = array( 'description' => __( 'Limit result set to products with selected global attributes.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'object', 'properties' => array( 'attribute' => array( 'description' => __( 'Attribute taxonomy name.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'wc_sanitize_taxonomy_name', ), 'term_id' => array( 'description' => __( 'List of attribute term IDs.', 'woocommerce' ), 'type' => 'array', 'items' => [ 'type' => 'integer', ], 'sanitize_callback' => 'wp_parse_id_list', ), 'slug' => array( 'description' => __( 'List of attribute slug(s). If a term ID is provided, this will be ignored.', 'woocommerce' ), 'type' => 'array', 'items' => [ 'type' => 'string', ], 'sanitize_callback' => 'wp_parse_slug_list', ), 'operator' => array( 'description' => __( 'Operator to compare product attribute terms.', 'woocommerce' ), 'type' => 'string', 'enum' => [ 'in', 'not_in', 'and' ], ), ), ), 'default' => [], ); $params['attribute_relation'] = array( 'description' => __( 'The logical relationship between attributes when filtering across multiple at once.', 'woocommerce' ), 'type' => 'string', 'enum' => [ 'in', 'and' ], 'default' => 'and', 'sanitize_callback' => 'sanitize_key', 'validate_callback' => 'rest_validate_request_arg', ); $params['catalog_visibility'] = array( 'description' => __( 'Determines if hidden or visible catalog products are shown.', 'woocommerce' ), 'type' => 'string', 'enum' => array( 'any', 'visible', 'catalog', 'search', 'hidden' ), 'sanitize_callback' => 'sanitize_key', 'validate_callback' => 'rest_validate_request_arg', ); $params['rating'] = array( 'description' => __( 'Limit result set to products with a certain average rating.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', 'enum' => range( 1, 5 ), ), 'default' => [], 'sanitize_callback' => 'wp_parse_id_list', ); return $params; } } V1/CheckoutOrder.php 0000644 00000017015 15073235735 0010322 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Payments\PaymentResult; use Automattic\WooCommerce\StoreApi\Exceptions\InvalidStockLevelsInCartException; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; use Automattic\WooCommerce\StoreApi\Utilities\OrderAuthorizationTrait; use Automattic\WooCommerce\StoreApi\Utilities\CheckoutTrait; /** * CheckoutOrder class. */ class CheckoutOrder extends AbstractCartRoute { use OrderAuthorizationTrait; use CheckoutTrait; /** * The route identifier. * * @var string */ const IDENTIFIER = 'checkout-order'; /** * The routes schema. * * @var string */ const SCHEMA_TYPE = 'checkout-order'; /** * Holds the current order being processed. * * @var \WC_Order */ private $order = null; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/checkout/(?P<id>[\d]+)'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => [ $this, 'is_authorized' ], 'args' => array_merge( [ 'payment_data' => [ 'description' => __( 'Data to pass through to the payment method when processing payment.', 'woocommerce' ), 'type' => 'array', 'items' => [ 'type' => 'object', 'properties' => [ 'key' => [ 'type' => 'string', ], 'value' => [ 'type' => [ 'string', 'boolean' ], ], ], ], ], ], $this->schema->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ) ), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Process an order. * * 1. Process Request * 2. Process Customer * 3. Validate Order * 4. Process Payment * * @throws RouteException On error. * @throws InvalidStockLevelsInCartException On error. * * @param \WP_REST_Request $request Request object. * * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { $order_id = absint( $request['id'] ); $this->order = wc_get_order( $order_id ); if ( ! $this->order || ! $this->order->needs_payment() ) { return new \WP_Error( 'invalid_order_update_status', __( 'This order cannot be paid for.', 'woocommerce' ) ); } /** * Process request data. * * Note: Customer data is persisted from the request first so that OrderController::update_addresses_from_cart * uses the up to date customer address. */ $this->update_billing_address( $request ); $this->update_order_from_request( $request ); /** * Process customer data. * * Update order with customer details, and sign up a user account as necessary. */ $this->process_customer( $request ); /** * Validate order. * * This logic ensures the order is valid before payment is attempted. */ $this->order_controller->validate_order_before_payment( $this->order ); /** * Fires before an order is processed by the Checkout Block/Store API. * * This hook informs extensions that $order has completed processing and is ready for payment. * * This is similar to existing core hook woocommerce_checkout_order_processed. We're using a new action: * - To keep the interface focused (only pass $order, not passing request data). * - This also explicitly indicates these orders are from checkout block/StoreAPI. * * @since 7.2.0 * * @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3238 * @example See docs/examples/checkout-order-processed.md * @param \WC_Order $order Order object. */ do_action( 'woocommerce_store_api_checkout_order_processed', $this->order ); /** * Process the payment and return the results. */ $payment_result = new PaymentResult(); if ( $this->order->needs_payment() ) { $this->process_payment( $request, $payment_result ); } else { $this->process_without_payment( $request, $payment_result ); } return $this->prepare_item_for_response( (object) [ 'order' => wc_get_order( $this->order ), 'payment_result' => $payment_result, ], $request ); } /** * Updates the current customer session using data from the request (e.g. address data). * * Address session data is synced to the order itself later on by OrderController::update_order_from_cart() * * @param \WP_REST_Request $request Full details about the request. */ private function update_billing_address( \WP_REST_Request $request ) { $customer = wc()->customer; $billing = $request['billing_address']; $shipping = $request['shipping_address']; // Billing address is a required field. foreach ( $billing as $key => $value ) { if ( is_callable( [ $customer, "set_billing_$key" ] ) ) { $customer->{"set_billing_$key"}( $value ); } } // If shipping address (optional field) was not provided, set it to the given billing address (required field). $shipping_address_values = $shipping ?? $billing; foreach ( $shipping_address_values as $key => $value ) { if ( is_callable( [ $customer, "set_shipping_$key" ] ) ) { $customer->{"set_shipping_$key"}( $value ); } elseif ( 'phone' === $key ) { $customer->update_meta_data( 'shipping_phone', $value ); } } /** * Fires when the Checkout Block/Store API updates a customer from the API request data. * * @since 8.2.0 * * @param \WC_Customer $customer Customer object. * @param \WP_REST_Request $request Full details about the request. */ do_action( 'woocommerce_store_api_checkout_update_customer_from_request', $customer, $request ); $customer->save(); $this->order->set_billing_address( $billing ); $this->order->set_shipping_address( $shipping ); $this->order->save(); $this->order->calculate_totals(); } /** * Gets the chosen payment method from the request. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WC_Payment_Gateway|null */ private function get_request_payment_method( \WP_REST_Request $request ) { $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); $request_payment_method = wc_clean( wp_unslash( $request['payment_method'] ?? '' ) ); $requires_payment_method = $this->order->needs_payment(); if ( empty( $request_payment_method ) ) { if ( $requires_payment_method ) { throw new RouteException( 'woocommerce_rest_checkout_missing_payment_method', __( 'No payment method provided.', 'woocommerce' ), 400 ); } return null; } if ( ! isset( $available_gateways[ $request_payment_method ] ) ) { throw new RouteException( 'woocommerce_rest_checkout_payment_method_disabled', sprintf( // Translators: %s Payment method ID. __( 'The %s payment gateway is not available.', 'woocommerce' ), esc_html( $request_payment_method ) ), 400 ); } return $available_gateways[ $request_payment_method ]; } /** * Updates the order with user details (e.g. address). * * @throws RouteException API error object with error details. * @param \WP_REST_Request $request Request object. */ private function process_customer( \WP_REST_Request $request ) { $this->order_controller->sync_customer_data_with_order( $this->order ); } } V1/Cart.php 0000644 00000002226 15073235735 0006450 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; /** * Cart class. */ class Cart extends AbstractCartRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'cart'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'context' => $this->get_context_param( [ 'default' => 'view' ] ), ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Handle the request and return a valid response for this endpoint. * * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { return rest_ensure_response( $this->schema->get_item_response( $this->cart_controller->get_cart_instance() ) ); } } V1/ProductTags.php 0000644 00000002054 15073235735 0010015 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; /** * ProductTags class. */ class ProductTags extends AbstractTermsRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'product-tags'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products/tags'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => $this->get_collection_params(), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get a collection of terms. * * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { return $this->get_terms_response( 'product_tag', $request ); } } V1/ProductAttributesById.php 0000644 00000003475 15073235735 0012025 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; /** * ProductAttributesById class. */ class ProductAttributesById extends AbstractRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'product-attributes-by-id'; /** * The routes schema. * * @var string */ const SCHEMA_TYPE = 'product-attribute'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products/attributes/(?P<id>[\d]+)'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ 'args' => array( 'id' => array( 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), 'type' => 'integer', ), ), [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view', ) ), ), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get a single item. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $object = wc_get_attribute( (int) $request['id'] ); if ( ! $object || 0 === $object->id ) { throw new RouteException( 'woocommerce_rest_attribute_invalid_id', __( 'Invalid attribute ID.', 'woocommerce' ), 404 ); } $data = $this->prepare_item_for_response( $object, $request ); $response = rest_ensure_response( $data ); return $response; } } V1/ProductReviews.php 0000644 00000015061 15073235735 0010545 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use WP_Comment_Query; use Automattic\WooCommerce\StoreApi\Utilities\Pagination; /** * ProductReviews class. */ class ProductReviews extends AbstractRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'product-reviews'; /** * The routes schema. * * @var string */ const SCHEMA_TYPE = 'product-review'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products/reviews'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => $this->get_collection_params(), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get a collection of reviews. * * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $prepared_args = array( 'type' => 'review', 'status' => 'approve', 'no_found_rows' => false, 'offset' => $request['offset'], 'order' => $request['order'], 'number' => $request['per_page'], 'post__in' => $request['product_id'], ); /** * Map category id to list of product ids. */ if ( ! empty( $request['category_id'] ) ) { $category_ids = $request['category_id']; $child_ids = []; foreach ( $category_ids as $category_id ) { $child_ids = array_merge( $child_ids, get_term_children( $category_id, 'product_cat' ) ); } $category_ids = array_unique( array_merge( $category_ids, $child_ids ) ); $product_ids = get_objects_in_term( $category_ids, 'product_cat' ); $prepared_args['post__in'] = isset( $prepared_args['post__in'] ) ? array_merge( $prepared_args['post__in'], $product_ids ) : $product_ids; } if ( 'rating' === $request['orderby'] ) { $prepared_args['meta_query'] = array( // phpcs:ignore 'relation' => 'OR', array( 'key' => 'rating', 'compare' => 'EXISTS', ), array( 'key' => 'rating', 'compare' => 'NOT EXISTS', ), ); } $prepared_args['orderby'] = $this->normalize_query_param( $request['orderby'] ); if ( empty( $request['offset'] ) ) { $prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 ); } $query = new WP_Comment_Query(); $query_result = $query->query( $prepared_args ); $response_objects = array(); foreach ( $query_result as $review ) { $data = $this->prepare_item_for_response( $review, $request ); $response_objects[] = $this->prepare_response_for_collection( $data ); } $total_reviews = (int) $query->found_comments; $max_pages = (int) $query->max_num_pages; if ( $total_reviews < 1 ) { // Out-of-bounds, run the query again without LIMIT for total count. unset( $prepared_args['number'], $prepared_args['offset'] ); $query = new WP_Comment_Query(); $prepared_args['count'] = true; $total_reviews = $query->query( $prepared_args ); $max_pages = $request['per_page'] ? ceil( $total_reviews / $request['per_page'] ) : 1; } $response = rest_ensure_response( $response_objects ); $response = ( new Pagination() )->add_headers( $response, $request, $total_reviews, $max_pages ); return $response; } /** * Prepends internal property prefix to query parameters to match our response fields. * * @param string $query_param Query parameter. * @return string */ protected function normalize_query_param( $query_param ) { $prefix = 'comment_'; switch ( $query_param ) { case 'id': $normalized = $prefix . 'ID'; break; case 'product': $normalized = $prefix . 'post_ID'; break; case 'rating': $normalized = 'meta_value_num'; break; default: $normalized = $prefix . $query_param; break; } return $normalized; } /** * Get the query params for collections of products. * * @return array */ public function get_collection_params() { $params = array(); $params['context'] = $this->get_context_param(); $params['context']['default'] = 'view'; $params['page'] = array( 'description' => __( 'Current page of the collection.', 'woocommerce' ), 'type' => 'integer', 'default' => 1, 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', 'minimum' => 1, ); $params['per_page'] = array( 'description' => __( 'Maximum number of items to be returned in result set. Defaults to no limit if left blank.', 'woocommerce' ), 'type' => 'integer', 'default' => 10, 'minimum' => 0, 'maximum' => 100, 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); $params['offset'] = array( 'description' => __( 'Offset the result set by a specific number of items.', 'woocommerce' ), 'type' => 'integer', 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); $params['order'] = array( 'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ), 'type' => 'string', 'default' => 'desc', 'enum' => array( 'asc', 'desc' ), 'validate_callback' => 'rest_validate_request_arg', ); $params['orderby'] = array( 'description' => __( 'Sort collection by object attribute.', 'woocommerce' ), 'type' => 'string', 'default' => 'date', 'enum' => array( 'date', 'date_gmt', 'id', 'rating', 'product', ), 'validate_callback' => 'rest_validate_request_arg', ); $params['category_id'] = array( 'description' => __( 'Limit result set to reviews from specific category IDs.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'wp_parse_id_list', 'validate_callback' => 'rest_validate_request_arg', ); $params['product_id'] = array( 'description' => __( 'Limit result set to reviews from specific product IDs.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'wp_parse_id_list', 'validate_callback' => 'rest_validate_request_arg', ); return $params; } } V1/ProductCategoriesById.php 0000644 00000003443 15073235735 0011757 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; /** * ProductCategoriesById class. */ class ProductCategoriesById extends AbstractRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'product-categories-by-id'; /** * The routes schema. * * @var string */ const SCHEMA_TYPE = 'product-category'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products/categories/(?P<id>[\d]+)'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ 'args' => array( 'id' => array( 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), 'type' => 'integer', ), ), [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view', ) ), ), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get a single item. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $object = get_term( (int) $request['id'], 'product_cat' ); if ( ! $object || 0 === $object->id ) { throw new RouteException( 'woocommerce_rest_category_invalid_id', __( 'Invalid category ID.', 'woocommerce' ), 404 ); } $data = $this->prepare_item_for_response( $object, $request ); return rest_ensure_response( $data ); } } V1/AbstractRoute.php 0000644 00000023435 15073235735 0010346 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\SchemaController; use Automattic\WooCommerce\StoreApi\Routes\RouteInterface; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; use Automattic\WooCommerce\StoreApi\Exceptions\InvalidCartException; use Automattic\WooCommerce\StoreApi\Schemas\v1\AbstractSchema; use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields; use Automattic\WooCommerce\Blocks\Package; use WP_Error; /** * AbstractRoute class. */ abstract class AbstractRoute implements RouteInterface { /** * Schema class instance. * * @var AbstractSchema */ protected $schema; /** * Route namespace. * * @var string */ protected $namespace = 'wc/store/v1'; /** * Schema Controller instance. * * @var SchemaController */ protected $schema_controller; /** * The routes schema. * * @var string */ const SCHEMA_TYPE = ''; /** * The routes schema version. * * @var integer */ const SCHEMA_VERSION = 1; /** * Constructor. * * @param SchemaController $schema_controller Schema Controller instance. * @param AbstractSchema $schema Schema class for this route. */ public function __construct( SchemaController $schema_controller, AbstractSchema $schema ) { $this->schema_controller = $schema_controller; $this->schema = $schema; } /** * Get the namespace for this route. * * @return string */ public function get_namespace() { return $this->namespace; } /** * Set the namespace for this route. * * @param string $namespace Given namespace. */ public function set_namespace( $namespace ) { $this->namespace = $namespace; } /** * Get item schema properties. * * @return array */ public function get_item_schema() { return $this->schema->get_item_schema(); } /** * Get the route response based on the type of request. * * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ public function get_response( \WP_REST_Request $request ) { $response = null; try { $response = $this->get_response_by_request_method( $request ); } catch ( RouteException $error ) { $response = $this->get_route_error_response( $error->getErrorCode(), $error->getMessage(), $error->getCode(), $error->getAdditionalData() ); } catch ( InvalidCartException $error ) { $response = $this->get_route_error_response_from_object( $error->getError(), $error->getCode(), $error->getAdditionalData() ); } catch ( \Exception $error ) { $response = $this->get_route_error_response( 'woocommerce_rest_unknown_server_error', $error->getMessage(), 500 ); } return is_wp_error( $response ) ? $this->error_to_response( $response ) : $response; } /** * Get the route response based on the type of request. * * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_response_by_request_method( \WP_REST_Request $request ) { switch ( $request->get_method() ) { case 'POST': return $this->get_route_post_response( $request ); case 'PUT': case 'PATCH': return $this->get_route_update_response( $request ); case 'DELETE': return $this->get_route_delete_response( $request ); } return $this->get_route_response( $request ); } /** * Converts an error to a response object. Based on \WP_REST_Server. * * @param \WP_Error $error WP_Error instance. * @return \WP_REST_Response List of associative arrays with code and message keys. */ protected function error_to_response( $error ) { $error_data = $error->get_error_data(); $status = isset( $error_data, $error_data['status'] ) ? $error_data['status'] : 500; $errors = []; foreach ( (array) $error->errors as $code => $messages ) { foreach ( (array) $messages as $message ) { $errors[] = array( 'code' => $code, 'message' => $message, 'data' => $error->get_error_data( $code ), ); } } $data = array_shift( $errors ); if ( count( $errors ) ) { $data['additional_errors'] = $errors; } return new \WP_REST_Response( $data, $status ); } /** * Get route response for GET requests. * * When implemented, should return a \WP_REST_Response. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { return $this->get_route_error_response( 'woocommerce_rest_invalid_endpoint', __( 'Method not implemented', 'woocommerce' ), 404 ); } /** * Get route response for POST requests. * * When implemented, should return a \WP_REST_Response. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { return $this->get_route_error_response( 'woocommerce_rest_invalid_endpoint', __( 'Method not implemented', 'woocommerce' ), 404 ); } /** * Get route response for PUT requests. * * When implemented, should return a \WP_REST_Response. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_update_response( \WP_REST_Request $request ) { return $this->get_route_error_response( 'woocommerce_rest_invalid_endpoint', __( 'Method not implemented', 'woocommerce' ), 404 ); } /** * Get route response for DELETE requests. * * When implemented, should return a \WP_REST_Response. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_delete_response( \WP_REST_Request $request ) { return $this->get_route_error_response( 'woocommerce_rest_invalid_endpoint', __( 'Method not implemented', 'woocommerce' ), 404 ); } /** * Get route response when something went wrong. * * @param string $error_code String based error code. * @param string $error_message User facing error message. * @param int $http_status_code HTTP status. Defaults to 500. * @param array $additional_data Extra data (key value pairs) to expose in the error response. * @return \WP_Error WP Error object. */ protected function get_route_error_response( $error_code, $error_message, $http_status_code = 500, $additional_data = [] ) { return new \WP_Error( $error_code, $error_message, array_merge( $additional_data, [ 'status' => $http_status_code ] ) ); } /** * Get route response when something went wrong and the supplied error is a WP_Error. This currently only happens * when an item in the cart is out of stock, partially out of stock, can only be bought individually, or when the * item is not purchasable. * * @param WP_Error $error_object The WP_Error object containing the error. * @param int $http_status_code HTTP status. Defaults to 500. * @param array $additional_data Extra data (key value pairs) to expose in the error response. * @return WP_Error WP Error object. */ protected function get_route_error_response_from_object( $error_object, $http_status_code = 500, $additional_data = [] ) { $error_object->add_data( array_merge( $additional_data, [ 'status' => $http_status_code ] ) ); return $error_object; } /** * Prepare a single item for response. * * @param mixed $item Item to format to schema. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response $response Response data. */ public function prepare_item_for_response( $item, \WP_REST_Request $request ) { $response = rest_ensure_response( $this->schema->get_item_response( $item ) ); $response->add_links( $this->prepare_links( $item, $request ) ); return $response; } /** * Retrieves the context param. * * Ensures consistent descriptions between endpoints, and populates enum from schema. * * @param array $args Optional. Additional arguments for context parameter. Default empty array. * @return array Context parameter details. */ protected function get_context_param( $args = array() ) { $param_details = array( 'description' => __( 'Scope under which the request is made; determines fields present in response.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_key', 'validate_callback' => 'rest_validate_request_arg', ); $schema = $this->get_item_schema(); if ( empty( $schema['properties'] ) ) { return array_merge( $param_details, $args ); } $contexts = array(); foreach ( $schema['properties'] as $attributes ) { if ( ! empty( $attributes['context'] ) ) { $contexts = array_merge( $contexts, $attributes['context'] ); } } if ( ! empty( $contexts ) ) { $param_details['enum'] = array_unique( $contexts ); rsort( $param_details['enum'] ); } return array_merge( $param_details, $args ); } /** * Prepares a response for insertion into a collection. * * @param \WP_REST_Response $response Response object. * @return array|mixed Response data, ready for insertion into collection data. */ protected function prepare_response_for_collection( \WP_REST_Response $response ) { $data = (array) $response->get_data(); $server = rest_get_server(); $links = $server::get_compact_response_links( $response ); if ( ! empty( $links ) ) { $data['_links'] = $links; } return $data; } /** * Prepare links for the request. * * @param mixed $item Item to prepare. * @param \WP_REST_Request $request Request object. * @return array */ protected function prepare_links( $item, $request ) { return []; } /** * Retrieves the query params for the collections. * * @return array Query parameters for the collection. */ public function get_collection_params() { return array( 'context' => $this->get_context_param(), ); } } V1/ProductAttributes.php 0000644 00000002634 15073235735 0011251 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; /** * ProductAttributes class. */ class ProductAttributes extends AbstractRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'product-attributes'; /** * The routes schema. * * @var string */ const SCHEMA_TYPE = 'product-attribute'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products/attributes'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => $this->get_collection_params(), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get a collection of attributes. * * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $ids = wc_get_attribute_taxonomy_ids(); $return = []; foreach ( $ids as $id ) { $object = wc_get_attribute( $id ); $data = $this->prepare_item_for_response( $object, $request ); $return[] = $this->prepare_response_for_collection( $data ); } return rest_ensure_response( $return ); } } V1/CartRemoveCoupon.php 0000644 00000004424 15073235735 0011014 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; /** * CartRemoveCoupon class. */ class CartRemoveCoupon extends AbstractCartRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'cart-remove-coupon'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/remove-coupon'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'code' => [ 'description' => __( 'Unique identifier for the coupon within the cart.', 'woocommerce' ), 'type' => 'string', ], ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Handle the request and return a valid response for this endpoint. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { if ( ! wc_coupons_enabled() ) { throw new RouteException( 'woocommerce_rest_cart_coupon_disabled', __( 'Coupons are disabled.', 'woocommerce' ), 404 ); } $cart = $this->cart_controller->get_cart_instance(); $coupon_code = wc_format_coupon_code( $request['code'] ); $coupon = new \WC_Coupon( $coupon_code ); if ( $coupon->get_code() !== $coupon_code || ! $coupon->is_valid() ) { throw new RouteException( 'woocommerce_rest_cart_coupon_error', __( 'Invalid coupon code.', 'woocommerce' ), 400 ); } if ( ! $this->cart_controller->has_coupon( $coupon_code ) ) { throw new RouteException( 'woocommerce_rest_cart_coupon_invalid_code', __( 'Coupon cannot be removed because it is not already applied to the cart.', 'woocommerce' ), 409 ); } $cart = $this->cart_controller->get_cart_instance(); $cart->remove_coupon( $coupon_code ); $cart->calculate_totals(); return rest_ensure_response( $this->schema->get_item_response( $cart ) ); } } V1/CartSelectShippingRate.php 0000644 00000006567 15073235735 0012142 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; /** * CartSelectShippingRate class. */ class CartSelectShippingRate extends AbstractCartRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'cart-select-shipping-rate'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/select-shipping-rate'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'package_id' => array( 'description' => __( 'The ID of the package being shipped. Leave blank to apply to all packages.', 'woocommerce' ), 'type' => [ 'integer', 'string', 'null' ], 'required' => false, ), 'rate_id' => [ 'description' => __( 'The chosen rate ID for the package.', 'woocommerce' ), 'type' => 'string', 'required' => true, ], ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Handle the request and return a valid response for this endpoint. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { if ( ! wc_shipping_enabled() ) { throw new RouteException( 'woocommerce_rest_shipping_disabled', __( 'Shipping is disabled.', 'woocommerce' ), 404 ); } if ( ! isset( $request['rate_id'] ) ) { throw new RouteException( 'woocommerce_rest_cart_missing_rate_id', __( 'Invalid Rate ID.', 'woocommerce' ), 400 ); } $cart = $this->cart_controller->get_cart_instance(); $package_id = isset( $request['package_id'] ) ? sanitize_text_field( wp_unslash( $request['package_id'] ) ) : null; $rate_id = sanitize_text_field( wp_unslash( $request['rate_id'] ) ); try { if ( ! is_null( $package_id ) ) { $this->cart_controller->select_shipping_rate( $package_id, $rate_id ); } else { foreach ( $this->cart_controller->get_shipping_packages() as $package ) { $this->cart_controller->select_shipping_rate( $package['package_id'], $rate_id ); } } } catch ( \WC_Rest_Exception $e ) { throw new RouteException( $e->getErrorCode(), $e->getMessage(), $e->getCode() ); } /** * Fires an action after a shipping method has been chosen for package(s) via the Store API. * * This allows extensions to perform addition actions after a shipping method has been chosen, but before the * cart totals are recalculated. * * @since 9.0.0 * * @param string|null $package_id The sanitized ID of the package being updated. Null if all packages are being updated. * @param string $rate_id The sanitized chosen rate ID for the package. * @param \WP_REST_Request $request Full details about the request. */ do_action( 'woocommerce_store_api_cart_select_shipping_rate', $package_id, $rate_id, $request ); $cart->calculate_shipping(); $cart->calculate_totals(); return rest_ensure_response( $this->cart_schema->get_item_response( $cart ) ); } } V1/AbstractCartRoute.php 0000644 00000024442 15073235735 0011157 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; use Automattic\WooCommerce\StoreApi\SchemaController; use Automattic\WooCommerce\StoreApi\Schemas\V1\AbstractSchema; use Automattic\WooCommerce\StoreApi\Schemas\V1\CartItemSchema; use Automattic\WooCommerce\StoreApi\Schemas\V1\CartSchema; use Automattic\WooCommerce\StoreApi\SessionHandler; use Automattic\WooCommerce\StoreApi\Utilities\CartController; use Automattic\WooCommerce\StoreApi\Utilities\DraftOrderTrait; use Automattic\WooCommerce\StoreApi\Utilities\JsonWebToken; use Automattic\WooCommerce\StoreApi\Utilities\OrderController; /** * Abstract Cart Route */ abstract class AbstractCartRoute extends AbstractRoute { use DraftOrderTrait; /** * The route's schema. * * @var string */ const SCHEMA_TYPE = 'cart'; /** * Schema class instance. * * @var CartSchema */ protected $schema; /** * Schema class for the cart. * * @var CartSchema */ protected $cart_schema; /** * Schema class for the cart item. * * @var CartItemSchema */ protected $cart_item_schema; /** * Cart controller class instance. * * @var CartController */ protected $cart_controller; /** * Order controller class instance. * * @var OrderController */ protected $order_controller; /** * Additional fields controller class instance. * * @var CheckoutFields */ protected $additional_fields_controller; /** * Constructor. * * @param SchemaController $schema_controller Schema Controller instance. * @param AbstractSchema $schema Schema class for this route. */ public function __construct( SchemaController $schema_controller, AbstractSchema $schema ) { parent::__construct( $schema_controller, $schema ); $this->cart_schema = $this->schema_controller->get( CartSchema::IDENTIFIER ); $this->cart_item_schema = $this->schema_controller->get( CartItemSchema::IDENTIFIER ); $this->cart_controller = new CartController(); $this->additional_fields_controller = Package::container()->get( CheckoutFields::class ); $this->order_controller = new OrderController(); } /** * Are we updating data or getting data? * * @param \WP_REST_Request $request Request object. * @return boolean */ protected function is_update_request( \WP_REST_Request $request ) { return in_array( $request->get_method(), [ 'POST', 'PUT', 'PATCH', 'DELETE' ], true ); } /** * Get the route response based on the type of request. * * @param \WP_REST_Request $request Request object. * * @return \WP_REST_Response */ public function get_response( \WP_REST_Request $request ) { $this->load_cart_session( $request ); $this->cart_controller->calculate_totals(); $response = null; $nonce_check = $this->requires_nonce( $request ) ? $this->check_nonce( $request ) : null; if ( is_wp_error( $nonce_check ) ) { $response = $nonce_check; } if ( ! $response ) { try { $response = $this->get_response_by_request_method( $request ); } catch ( RouteException $error ) { $response = $this->get_route_error_response( $error->getErrorCode(), $error->getMessage(), $error->getCode(), $error->getAdditionalData() ); } catch ( \Exception $error ) { $response = $this->get_route_error_response( 'woocommerce_rest_unknown_server_error', $error->getMessage(), 500 ); } } // For update requests, this will recalculate cart totals and sync draft orders with the current cart. if ( $this->is_update_request( $request ) ) { $this->cart_updated( $request ); } // Format error responses. if ( is_wp_error( $response ) ) { $response = $this->error_to_response( $response ); } return $this->add_response_headers( rest_ensure_response( $response ) ); } /** * Add nonce headers to a response object. * * @param \WP_REST_Response $response The response object. * * @return \WP_REST_Response */ protected function add_response_headers( \WP_REST_Response $response ) { $nonce = wp_create_nonce( 'wc_store_api' ); $response->header( 'Nonce', $nonce ); $response->header( 'Nonce-Timestamp', time() ); $response->header( 'User-ID', get_current_user_id() ); $response->header( 'Cart-Token', $this->get_cart_token() ); // The following headers are deprecated and should be removed in a future version. $response->header( 'X-WC-Store-API-Nonce', $nonce ); return $response; } /** * Load the cart session before handling responses. * * @param \WP_REST_Request $request Request object. */ protected function load_cart_session( \WP_REST_Request $request ) { $cart_token = $request->get_header( 'Cart-Token' ); if ( $cart_token && JsonWebToken::validate( $cart_token, $this->get_cart_token_secret() ) ) { // Overrides the core session class. add_filter( 'woocommerce_session_handler', function () { return SessionHandler::class; } ); } $this->cart_controller->load_cart(); } /** * Generates a cart token for the response headers. * * Current namespace is used as the token Issuer. * * * * @return string */ protected function get_cart_token() { return JsonWebToken::create( [ 'user_id' => wc()->session->get_customer_id(), 'exp' => $this->get_cart_token_expiration(), 'iss' => $this->namespace, ], $this->get_cart_token_secret() ); } /** * Gets the secret for the cart token using wp_salt. * * @return string */ protected function get_cart_token_secret() { return '@' . wp_salt(); } /** * Gets the expiration of the cart token. Defaults to 48h. * * @return int */ protected function get_cart_token_expiration() { /** * Filters the session expiration. * * @since 8.7.0 * * @param int $expiration Expiration in seconds. */ return time() + intval( apply_filters( 'wc_session_expiration', DAY_IN_SECONDS * 2 ) ); } /** * Checks if a nonce is required for the route. * * @param \WP_REST_Request $request Request. * * @return bool */ protected function requires_nonce( \WP_REST_Request $request ) { return $this->is_update_request( $request ); } /** * Triggered after an update to cart data. Re-calculates totals and updates draft orders (if they already exist) to * keep all data in sync. * * @param \WP_REST_Request $request Request object. */ protected function cart_updated( \WP_REST_Request $request ) { $draft_order = $this->get_draft_order(); if ( $draft_order ) { // This does not trigger a recalculation of the cart--endpoints should have already done so before returning // the cart response. $this->order_controller->update_order_from_cart( $draft_order, false ); wc_do_deprecated_action( 'woocommerce_blocks_cart_update_order_from_request', array( $draft_order, $request, ), '7.2.0', 'woocommerce_store_api_cart_update_order_from_request', 'This action was deprecated in WooCommerce Blocks version 7.2.0. Please use woocommerce_store_api_cart_update_order_from_request instead.' ); /** * Fires when the order is synced with cart data from a cart route. * * @since 7.2.0 * * @param \WC_Order $draft_order Order object. * @param \WC_Customer $customer Customer object. * @param \WP_REST_Request $request Full details about the request. */ do_action( 'woocommerce_store_api_cart_update_order_from_request', $draft_order, $request ); } } /** * For non-GET endpoints, require and validate a nonce to prevent CSRF attacks. * * Nonces will mismatch if the logged in session cookie is different! If using a client to test, set this cookie * to match the logged in cookie in your browser. * * @param \WP_REST_Request $request Request object. * * @return \WP_Error|boolean */ protected function check_nonce( \WP_REST_Request $request ) { $nonce = null; if ( $request->get_header( 'Nonce' ) ) { $nonce = $request->get_header( 'Nonce' ); } elseif ( $request->get_header( 'X-WC-Store-API-Nonce' ) ) { $nonce = $request->get_header( 'X-WC-Store-API-Nonce' ); // @todo Remove handling and sending of deprecated X-WC-Store-API-Nonce Header (Blocks 7.5.0) wc_deprecated_argument( 'X-WC-Store-API-Nonce', '7.2.0', 'Use the "Nonce" Header instead. This header will be removed after Blocks release 7.5' ); rest_handle_deprecated_argument( 'X-WC-Store-API-Nonce', 'Use the "Nonce" Header instead. This header will be removed after Blocks release 7.5', '7.2.0' ); } /** * Filters the Store API nonce check. * * This can be used to disable the nonce check when testing API endpoints via a REST API client. * * @since 4.5.0 * * @param boolean $disable_nonce_check If true, nonce checks will be disabled. * * @return boolean */ if ( apply_filters( 'woocommerce_store_api_disable_nonce_check', false ) ) { return true; } if ( null === $nonce ) { return $this->get_route_error_response( 'woocommerce_rest_missing_nonce', __( 'Missing the Nonce header. This endpoint requires a valid nonce.', 'woocommerce' ), 401 ); } if ( ! wp_verify_nonce( $nonce, 'wc_store_api' ) ) { return $this->get_route_error_response( 'woocommerce_rest_invalid_nonce', __( 'Nonce is invalid.', 'woocommerce' ), 403 ); } return true; } /** * Get route response when something went wrong. * * @param string $error_code String based error code. * @param string $error_message User facing error message. * @param int $http_status_code HTTP status. Defaults to 500. * @param array $additional_data Extra data (key value pairs) to expose in the error response. * * @return \WP_Error WP Error object. */ protected function get_route_error_response( $error_code, $error_message, $http_status_code = 500, $additional_data = [] ) { switch ( $http_status_code ) { case 409: // If there was a conflict, return the cart so the client can resolve it. $cart = $this->cart_controller->get_cart_instance(); return new \WP_Error( $error_code, $error_message, array_merge( $additional_data, [ 'status' => $http_status_code, 'cart' => $this->cart_schema->get_item_response( $cart ), ] ) ); } return new \WP_Error( $error_code, $error_message, [ 'status' => $http_status_code ] ); } } V1/CartCouponsByCode.php 0000644 00000005040 15073235735 0011102 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; /** * CartCouponsByCode class. */ class CartCouponsByCode extends AbstractCartRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'cart-coupons-by-code'; /** * The routes schema. * * @var string */ const SCHEMA_TYPE = 'cart-coupon'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/coupons/(?P<code>[\w-]+)'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ 'args' => [ 'code' => [ 'description' => __( 'Unique identifier for the coupon within the cart.', 'woocommerce' ), 'type' => 'string', ], ], [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'context' => $this->get_context_param( [ 'default' => 'view' ] ), ], ], [ 'methods' => \WP_REST_Server::DELETABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Get a single cart coupon. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { if ( ! $this->cart_controller->has_coupon( $request['code'] ) ) { throw new RouteException( 'woocommerce_rest_cart_coupon_invalid_code', __( 'Coupon does not exist in the cart.', 'woocommerce' ), 404 ); } return $this->prepare_item_for_response( $request['code'], $request ); } /** * Delete a single cart coupon. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_delete_response( \WP_REST_Request $request ) { if ( ! $this->cart_controller->has_coupon( $request['code'] ) ) { throw new RouteException( 'woocommerce_rest_cart_coupon_invalid_code', __( 'Coupon does not exist in the cart.', 'woocommerce' ), 404 ); } $cart = $this->cart_controller->get_cart_instance(); $cart->remove_coupon( $request['code'] ); $cart->calculate_totals(); return new \WP_REST_Response( null, 204 ); } } V1/CartAddItem.php 0000644 00000007477 15073235735 0007715 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; /** * CartAddItem class. */ class CartAddItem extends AbstractCartRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'cart-add-item'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/add-item'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'id' => [ 'description' => __( 'The cart item product or variation ID.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'sanitize_callback' => 'absint', ], 'quantity' => [ 'description' => __( 'Quantity of this item to add to the cart.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'arg_options' => [ 'sanitize_callback' => 'wc_stock_amount', ], ], 'variation' => [ 'description' => __( 'Chosen attributes (for variations).', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'items' => [ 'type' => 'object', 'properties' => [ 'attribute' => [ 'description' => __( 'Variation attribute name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'value' => [ 'description' => __( 'Variation attribute value.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], ], ], ], ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Handle the request and return a valid response for this endpoint. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { // Do not allow key to be specified during creation. if ( ! empty( $request['key'] ) ) { throw new RouteException( 'woocommerce_rest_cart_item_exists', __( 'Cannot create an existing cart item.', 'woocommerce' ), 400 ); } $cart = $this->cart_controller->get_cart_instance(); /** * Filters cart item data sent via the API before it is passed to the cart controller. * * This hook filters cart items. It allows the request data to be changed, for example, quantity, or * supplemental cart item data, before it is passed into CartController::add_to_cart and stored to session. * * CartController::add_to_cart only expects the keys id, quantity, variation, and cart_item_data, so other values * may be ignored. CartController::add_to_cart (and core) do already have a filter hook called * woocommerce_add_cart_item, but this does not have access to the original Store API request like this hook does. * * @since 8.8.0 * * @param array $add_to_cart_data An array of cart item data. * @return array */ $add_to_cart_data = apply_filters( 'woocommerce_store_api_add_to_cart_data', array( 'id' => $request['id'], 'quantity' => $request['quantity'], 'variation' => $request['variation'], 'cart_item_data' => [], ), $request ); $this->cart_controller->add_to_cart( $add_to_cart_data ); $response = rest_ensure_response( $this->schema->get_item_response( $cart ) ); $response->set_status( 201 ); return $response; } } V1/Checkout.php 0000644 00000057705 15073235735 0007340 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields; use Automattic\WooCommerce\StoreApi\Payments\PaymentResult; use Automattic\WooCommerce\StoreApi\Exceptions\InvalidStockLevelsInCartException; use Automattic\WooCommerce\StoreApi\Exceptions\InvalidCartException; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; use Automattic\WooCommerce\StoreApi\Utilities\DraftOrderTrait; use Automattic\WooCommerce\Checkout\Helpers\ReserveStock; use Automattic\WooCommerce\Checkout\Helpers\ReserveStockException; use Automattic\WooCommerce\StoreApi\Utilities\CheckoutTrait; use Automattic\WooCommerce\StoreApi\SchemaController; use Automattic\WooCommerce\StoreApi\Schemas\V1\AbstractSchema; use Automattic\WooCommerce\Blocks\Package; /** * Checkout class. */ class Checkout extends AbstractCartRoute { use DraftOrderTrait; use CheckoutTrait; /** * The route identifier. * * @var string */ const IDENTIFIER = 'checkout'; /** * The routes schema. * * @var string */ const SCHEMA_TYPE = 'checkout'; /** * Holds the current order being processed. * * @var \WC_Order */ private $order = null; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/checkout'; } /** * Checks if a nonce is required for the route. * * @param \WP_REST_Request $request Request. * @return bool */ protected function requires_nonce( \WP_REST_Request $request ) { return true; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'context' => $this->get_context_param( [ 'default' => 'view' ] ), ], ], [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => array_merge( [ 'payment_data' => [ 'description' => __( 'Data to pass through to the payment method when processing payment.', 'woocommerce' ), 'type' => 'array', 'items' => [ 'type' => 'object', 'properties' => [ 'key' => [ 'type' => 'string', ], 'value' => [ 'type' => [ 'string', 'boolean' ], ], ], ], ], ], $this->schema->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ) ), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Get the route response based on the type of request. * * @param \WP_REST_Request $request Request object. * * @return \WP_REST_Response */ public function get_response( \WP_REST_Request $request ) { $this->load_cart_session( $request ); $this->cart_controller->calculate_totals(); $response = null; $nonce_check = $this->requires_nonce( $request ) ? $this->check_nonce( $request ) : null; if ( is_wp_error( $nonce_check ) ) { $response = $nonce_check; } if ( ! $response ) { try { $response = $this->get_response_by_request_method( $request ); } catch ( InvalidCartException $error ) { $response = $this->get_route_error_response_from_object( $error->getError(), $error->getCode(), $error->getAdditionalData() ); } catch ( RouteException $error ) { $response = $this->get_route_error_response( $error->getErrorCode(), $error->getMessage(), $error->getCode(), $error->getAdditionalData() ); } catch ( \Exception $error ) { $response = $this->get_route_error_response( 'woocommerce_rest_unknown_server_error', $error->getMessage(), 500 ); } } if ( is_wp_error( $response ) ) { $response = $this->error_to_response( $response ); } return $this->add_response_headers( $response ); } /** * Convert the cart into a new draft order, or update an existing draft order, and return an updated cart response. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $this->create_or_update_draft_order( $request ); return $this->prepare_item_for_response( (object) [ 'order' => $this->order, 'payment_result' => new PaymentResult(), ], $request ); } /** * Process an order. * * 1. Obtain Draft Order * 2. Process Request * 3. Process Customer * 4. Validate Order * 5. Process Payment * * @throws RouteException On error. * @throws InvalidStockLevelsInCartException On error. * * @param \WP_REST_Request $request Request object. * * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { /** * Validate items etc are allowed in the order before the order is processed. This will fix violations and tell * the customer. */ $this->cart_controller->validate_cart(); /** * Obtain Draft Order and process request data. * * Note: Customer data is persisted from the request first so that OrderController::update_addresses_from_cart * uses the up to date customer address. */ $this->update_customer_from_request( $request ); $this->create_or_update_draft_order( $request ); $this->update_order_from_request( $request ); /** * Process customer data. * * Update order with customer details, and sign up a user account as necessary. */ $this->process_customer( $request ); /** * Validate order. * * This logic ensures the order is valid before payment is attempted. */ $this->order_controller->validate_order_before_payment( $this->order ); wc_do_deprecated_action( '__experimental_woocommerce_blocks_checkout_order_processed', array( $this->order, ), '6.3.0', 'woocommerce_store_api_checkout_order_processed', 'This action was deprecated in WooCommerce Blocks version 6.3.0. Please use woocommerce_store_api_checkout_order_processed instead.' ); wc_do_deprecated_action( 'woocommerce_blocks_checkout_order_processed', array( $this->order, ), '7.2.0', 'woocommerce_store_api_checkout_order_processed', 'This action was deprecated in WooCommerce Blocks version 7.2.0. Please use woocommerce_store_api_checkout_order_processed instead.' ); /** * Fires before an order is processed by the Checkout Block/Store API. * * This hook informs extensions that $order has completed processing and is ready for payment. * * This is similar to existing core hook woocommerce_checkout_order_processed. We're using a new action: * - To keep the interface focused (only pass $order, not passing request data). * - This also explicitly indicates these orders are from checkout block/StoreAPI. * * @since 7.2.0 * * @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3238 * @example See docs/examples/checkout-order-processed.md * @param \WC_Order $order Order object. */ do_action( 'woocommerce_store_api_checkout_order_processed', $this->order ); /** * Process the payment and return the results. */ $payment_result = new PaymentResult(); if ( $this->order->needs_payment() ) { $this->process_payment( $request, $payment_result ); } else { $this->process_without_payment( $request, $payment_result ); } return $this->prepare_item_for_response( (object) [ 'order' => wc_get_order( $this->order ), 'payment_result' => $payment_result, ], $request ); } /** * Get route response when something went wrong. * * @param string $error_code String based error code. * @param string $error_message User facing error message. * @param int $http_status_code HTTP status. Defaults to 500. * @param array $additional_data Extra data (key value pairs) to expose in the error response. * @return \WP_Error WP Error object. */ protected function get_route_error_response( $error_code, $error_message, $http_status_code = 500, $additional_data = [] ) { $error_from_message = new \WP_Error( $error_code, $error_message ); // 409 is when there was a conflict, so we return the cart so the client can resolve it. if ( 409 === $http_status_code ) { return $this->add_data_to_error_object( $error_from_message, $additional_data, $http_status_code, true ); } return $this->add_data_to_error_object( $error_from_message, $additional_data, $http_status_code ); } /** * Get route response when something went wrong. * * @param \WP_Error $error_object User facing error message. * @param int $http_status_code HTTP status. Defaults to 500. * @param array $additional_data Extra data (key value pairs) to expose in the error response. * @return \WP_Error WP Error object. */ protected function get_route_error_response_from_object( $error_object, $http_status_code = 500, $additional_data = [] ) { // 409 is when there was a conflict, so we return the cart so the client can resolve it. if ( 409 === $http_status_code ) { return $this->add_data_to_error_object( $error_object, $additional_data, $http_status_code, true ); } return $this->add_data_to_error_object( $error_object, $additional_data, $http_status_code ); } /** * Adds additional data to the \WP_Error object. * * @param \WP_Error $error The error object to add the cart to. * @param array $data The data to add to the error object. * @param int $http_status_code The HTTP status code this error should return. * @param bool $include_cart Whether the cart should be included in the error data. * @returns \WP_Error The \WP_Error with the cart added. */ private function add_data_to_error_object( $error, $data, $http_status_code, bool $include_cart = false ) { $data = array_merge( $data, [ 'status' => $http_status_code ] ); if ( $include_cart ) { $data = array_merge( $data, [ 'cart' => wc()->api->get_endpoint_data( '/wc/store/v1/cart' ) ] ); } $error->add_data( $data ); return $error; } /** * Create or update a draft order based on the cart. * * @param \WP_REST_Request $request Full details about the request. * @throws RouteException On error. */ private function create_or_update_draft_order( \WP_REST_Request $request ) { $this->order = $this->get_draft_order(); if ( ! $this->order ) { $this->order = $this->order_controller->create_order_from_cart(); } else { $this->order_controller->update_order_from_cart( $this->order, true ); } wc_do_deprecated_action( '__experimental_woocommerce_blocks_checkout_update_order_meta', array( $this->order, ), '6.3.0', 'woocommerce_store_api_checkout_update_order_meta', 'This action was deprecated in WooCommerce Blocks version 6.3.0. Please use woocommerce_store_api_checkout_update_order_meta instead.' ); wc_do_deprecated_action( 'woocommerce_blocks_checkout_update_order_meta', array( $this->order, ), '7.2.0', 'woocommerce_store_api_checkout_update_order_meta', 'This action was deprecated in WooCommerce Blocks version 7.2.0. Please use woocommerce_store_api_checkout_update_order_meta instead.' ); /** * Fires when the Checkout Block/Store API updates an order's meta data. * * This hook gives extensions the chance to add or update meta data on the $order. * Throwing an exception from a callback attached to this action will make the Checkout Block render in a warning state, effectively preventing checkout. * * This is similar to existing core hook woocommerce_checkout_update_order_meta. * We're using a new action: * - To keep the interface focused (only pass $order, not passing request data). * - This also explicitly indicates these orders are from checkout block/StoreAPI. * * @since 7.2.0 * * @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3686 * * @param \WC_Order $order Order object. */ do_action( 'woocommerce_store_api_checkout_update_order_meta', $this->order ); // Confirm order is valid before proceeding further. if ( ! $this->order instanceof \WC_Order ) { throw new RouteException( 'woocommerce_rest_checkout_missing_order', __( 'Unable to create order', 'woocommerce' ), 500 ); } // Store order ID to session. $this->set_draft_order_id( $this->order->get_id() ); /** * Try to reserve stock for the order. * * If creating a draft order on checkout entry, set the timeout to 10 mins. * If POSTing to the checkout (attempting to pay), set the timeout to 60 mins (using the woocommerce_hold_stock_minutes option). */ try { $reserve_stock = new ReserveStock(); $duration = $request->get_method() === 'POST' ? (int) get_option( 'woocommerce_hold_stock_minutes', 60 ) : 10; $reserve_stock->reserve_stock_for_order( $this->order, $duration ); } catch ( ReserveStockException $e ) { throw new RouteException( $e->getErrorCode(), $e->getMessage(), $e->getCode() ); } } /** * Updates the current customer session using data from the request (e.g. address data). * * Address session data is synced to the order itself later on by OrderController::update_order_from_cart() * * @param \WP_REST_Request $request Full details about the request. */ private function update_customer_from_request( \WP_REST_Request $request ) { $customer = wc()->customer; // Billing address is a required field. foreach ( $request['billing_address'] as $key => $value ) { if ( is_callable( [ $customer, "set_billing_$key" ] ) ) { $customer->{"set_billing_$key"}( $value ); } elseif ( $this->additional_fields_controller->is_field( $key, 'address' ) ) { $this->additional_fields_controller->persist_field_for_customer( "/billing/$key", $value, $customer ); } } // If shipping address (optional field) was not provided, set it to the given billing address (required field). $shipping_address_values = $request['shipping_address'] ?? $request['billing_address']; foreach ( $shipping_address_values as $key => $value ) { if ( is_callable( [ $customer, "set_shipping_$key" ] ) ) { $customer->{"set_shipping_$key"}( $value ); } elseif ( 'phone' === $key ) { $customer->update_meta_data( 'shipping_phone', $value ); } elseif ( $this->additional_fields_controller->is_field( $key, 'address' ) ) { $this->additional_fields_controller->persist_field_for_customer( "/shipping/$key", $value, $customer ); } } /** * Fires when the Checkout Block/Store API updates a customer from the API request data. * * @since 8.2.0 * * @param \WC_Customer $customer Customer object. * @param \WP_REST_Request $request Full details about the request. */ do_action( 'woocommerce_store_api_checkout_update_customer_from_request', $customer, $request ); $customer->save(); } /** * Gets the chosen payment method from the request. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WC_Payment_Gateway|null */ private function get_request_payment_method( \WP_REST_Request $request ) { $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); $request_payment_method = wc_clean( wp_unslash( $request['payment_method'] ?? '' ) ); $requires_payment_method = $this->order->needs_payment(); if ( empty( $request_payment_method ) ) { if ( $requires_payment_method ) { throw new RouteException( 'woocommerce_rest_checkout_missing_payment_method', __( 'No payment method provided.', 'woocommerce' ), 400 ); } return null; } if ( ! isset( $available_gateways[ $request_payment_method ] ) ) { $all_payment_gateways = WC()->payment_gateways->payment_gateways(); $gateway_title = isset( $all_payment_gateways[ $request_payment_method ] ) ? $all_payment_gateways[ $request_payment_method ]->get_title() : $request_payment_method; throw new RouteException( 'woocommerce_rest_checkout_payment_method_disabled', sprintf( // Translators: %s Payment method ID. __( '%s is not available for this order—please choose a different payment method', 'woocommerce' ), esc_html( $gateway_title ) ), 400 ); } return $available_gateways[ $request_payment_method ]; } /** * Order processing relating to customer account. * * Creates a customer account as needed (based on request & store settings) and updates the order with the new customer ID. * Updates the order with user details (e.g. address). * * @throws RouteException API error object with error details. * @param \WP_REST_Request $request Request object. */ private function process_customer( \WP_REST_Request $request ) { try { if ( $this->should_create_customer_account( $request ) ) { $customer_id = $this->create_customer_account( $request['billing_address']['email'], $request['billing_address']['first_name'], $request['billing_address']['last_name'] ); // Associate customer with the order. This is done before login to ensure the order is associated with // the correct customer if login fails. $this->order->set_customer_id( $customer_id ); $this->order->save(); // Log the customer in to WordPress. Doing this inline instead of using `wc_set_customer_auth_cookie` // because wc_set_customer_auth_cookie forces the use of session cookie. wp_set_current_user( $customer_id ); wp_set_auth_cookie( $customer_id, true ); // Init session cookie if the session cookie handler exists. if ( is_callable( [ WC()->session, 'init_session_cookie' ] ) ) { WC()->session->init_session_cookie(); } } } catch ( \Exception $error ) { switch ( $error->getMessage() ) { case 'registration-error-invalid-email': throw new RouteException( 'registration-error-invalid-email', __( 'Please provide a valid email address.', 'woocommerce' ), 400 ); case 'registration-error-email-exists': throw new RouteException( 'registration-error-email-exists', __( 'An account is already registered with your email address. Please log in before proceeding.', 'woocommerce' ), 400 ); } } // Persist customer address data to account. $this->order_controller->sync_customer_data_with_order( $this->order ); } /** * Check request options and store (shop) config to determine if a user account should be created as part of order * processing. * * @param \WP_REST_Request $request The current request object being handled. * @return boolean True if a new user account should be created. */ private function should_create_customer_account( \WP_REST_Request $request ) { if ( is_user_logged_in() ) { return false; } // Return false if registration is not enabled for the store. if ( false === filter_var( wc()->checkout()->is_registration_enabled(), FILTER_VALIDATE_BOOLEAN ) ) { return false; } // Return true if the store requires an account for all purchases. Note - checkbox is not displayed to shopper in this case. if ( true === filter_var( wc()->checkout()->is_registration_required(), FILTER_VALIDATE_BOOLEAN ) ) { return true; } // Create an account if requested via the endpoint. if ( true === filter_var( $request['create_account'], FILTER_VALIDATE_BOOLEAN ) ) { // User has requested an account as part of checkout processing. return true; } return false; } /** * Create a new account for a customer. * * The account is created with a generated username. The customer is sent * an email notifying them about the account and containing a link to set * their (initial) password. * * Intended as a replacement for wc_create_new_customer in WC core. * * @throws \Exception If an error is encountered when creating the user account. * * @param string $user_email The email address to use for the new account. * @param string $first_name The first name to use for the new account. * @param string $last_name The last name to use for the new account. * * @return int User id if successful */ private function create_customer_account( $user_email, $first_name, $last_name ) { if ( empty( $user_email ) || ! is_email( $user_email ) ) { throw new \Exception( 'registration-error-invalid-email' ); } if ( email_exists( $user_email ) ) { throw new \Exception( 'registration-error-email-exists' ); } $username = wc_create_new_customer_username( $user_email ); // Handle password creation. $password = wp_generate_password(); $password_generated = true; // Use WP_Error to handle registration errors. $errors = new \WP_Error(); /** * Fires before a customer account is registered. * * This hook fires before customer accounts are created and passes the form data (username, email) and an array * of errors. * * This could be used to add extra validation logic and append errors to the array. * * @since 7.2.0 * * @internal Matches filter name in WooCommerce core. * * @param string $username Customer username. * @param string $user_email Customer email address. * @param \WP_Error $errors Error object. */ do_action( 'woocommerce_register_post', $username, $user_email, $errors ); /** * Filters registration errors before a customer account is registered. * * This hook filters registration errors. This can be used to manipulate the array of errors before * they are displayed. * * @since 7.2.0 * * @internal Matches filter name in WooCommerce core. * * @param \WP_Error $errors Error object. * @param string $username Customer username. * @param string $user_email Customer email address. * @return \WP_Error */ $errors = apply_filters( 'woocommerce_registration_errors', $errors, $username, $user_email ); if ( is_wp_error( $errors ) && $errors->get_error_code() ) { throw new \Exception( $errors->get_error_code() ); } /** * Filters customer data before a customer account is registered. * * This hook filters customer data. It allows user data to be changed, for example, username, password, email, * first name, last name, and role. * * @since 7.2.0 * * @param array $customer_data An array of customer (user) data. * @return array */ $new_customer_data = apply_filters( 'woocommerce_new_customer_data', array( 'user_login' => $username, 'user_pass' => $password, 'user_email' => $user_email, 'first_name' => $first_name, 'last_name' => $last_name, 'role' => 'customer', 'source' => 'store-api', ) ); $customer_id = wp_insert_user( $new_customer_data ); if ( is_wp_error( $customer_id ) ) { throw $this->map_create_account_error( $customer_id ); } // Set account flag to remind customer to update generated password. update_user_option( $customer_id, 'default_password_nag', true, true ); /** * Fires after a customer account has been registered. * * This hook fires after customer accounts are created and passes the customer data. * * @since 7.2.0 * * @internal Matches filter name in WooCommerce core. * * @param integer $customer_id New customer (user) ID. * @param array $new_customer_data Array of customer (user) data. * @param string $password_generated The generated password for the account. */ do_action( 'woocommerce_created_customer', $customer_id, $new_customer_data, $password_generated ); return $customer_id; } /** * Convert an account creation error to an exception. * * @param \WP_Error $error An error object. * @return \Exception. */ private function map_create_account_error( \WP_Error $error ) { switch ( $error->get_error_code() ) { // WordPress core error codes. case 'empty_username': case 'invalid_username': case 'empty_email': case 'invalid_email': case 'email_exists': case 'registerfail': return new \Exception( 'woocommerce_rest_checkout_create_account_failure' ); } return new \Exception( 'woocommerce_rest_checkout_create_account_failure' ); } } V1/AI/Patterns.php 0000644 00000005561 15073235735 0007655 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1\AI; use Automattic\WooCommerce\Blocks\AI\Connection; use Automattic\WooCommerce\Blocks\Patterns\PatternsHelper; use Automattic\WooCommerce\Blocks\Patterns\PatternUpdater; use Automattic\WooCommerce\StoreApi\Routes\V1\AbstractRoute; /** * Patterns class. * * @internal */ class Patterns extends AbstractRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'ai/patterns'; /** * The schema item identifier. * * @var string */ const SCHEMA_TYPE = 'ai/patterns'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/ai/patterns'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => [ Middleware::class, 'is_authorized' ], 'args' => [ 'business_description' => [ 'description' => __( 'The business description for a given store.', 'woocommerce' ), 'type' => 'string', ], 'images' => [ 'description' => __( 'The images for a given store.', 'woocommerce' ), 'type' => 'object', ], ], ], [ 'methods' => \WP_REST_Server::DELETABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => [ Middleware::class, 'is_authorized' ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Update patterns with the content and images powered by AI. * * @param \WP_REST_Request $request Request object. * * @return bool|string|\WP_Error|\WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { $business_description = sanitize_text_field( wp_unslash( $request['business_description'] ) ); $ai_connection = new Connection(); $site_id = $ai_connection->get_site_id(); if ( is_wp_error( $site_id ) ) { return $this->error_to_response( $site_id ); } $token = $ai_connection->get_jwt_token( $site_id ); $images = $request['images']; try { ( new PatternUpdater() )->generate_content( $ai_connection, $token, $images, $business_description ); return rest_ensure_response( array( 'ai_content_generated' => true ) ); } catch ( \WP_Error $e ) { return $this->error_to_response( $e ); } } /** * Remove patterns generated by AI. * * @param \WP_REST_Request $request Request object. * * @return bool|string|\WP_Error|\WP_REST_Response */ protected function get_route_delete_response( \WP_REST_Request $request ) { PatternsHelper::delete_patterns_ai_data_post(); return rest_ensure_response( array( 'removed' => true ) ); } } V1/AI/Images.php 0000644 00000005265 15073235735 0007263 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1\AI; use Automattic\WooCommerce\Blocks\AI\Connection; use Automattic\WooCommerce\Blocks\Images\Pexels; use Automattic\WooCommerce\StoreApi\Routes\V1\AbstractRoute; /** * Patterns class. */ class Images extends AbstractRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'ai/images'; /** * The schema item identifier. * * @var string */ const SCHEMA_TYPE = 'ai/images'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/ai/images'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => [ Middleware::class, 'is_authorized' ], 'args' => [ 'business_description' => [ 'description' => __( 'The business description for a given store.', 'woocommerce' ), 'type' => 'string', ], ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Generate Images from Pexels * * @param \WP_REST_Request $request Request object. * * @return bool|string|\WP_Error|\WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { $business_description = sanitize_text_field( wp_unslash( $request['business_description'] ) ); if ( empty( $business_description ) ) { $business_description = get_option( 'woo_ai_describe_store_description' ); } $last_business_description = get_option( 'last_business_description_with_ai_content_generated' ); if ( $last_business_description === $business_description ) { return rest_ensure_response( $this->prepare_item_for_response( [ 'ai_content_generated' => true, 'images' => array(), ], $request ) ); } $ai_connection = new Connection(); $site_id = $ai_connection->get_site_id(); if ( is_wp_error( $site_id ) ) { return $this->error_to_response( $site_id ); } $token = $ai_connection->get_jwt_token( $site_id ); if ( is_wp_error( $token ) ) { return $this->error_to_response( $token ); } $images = ( new Pexels() )->get_images( $ai_connection, $token, $business_description ); if ( is_wp_error( $images ) ) { return $this->error_to_response( $images ); } return rest_ensure_response( $this->prepare_item_for_response( [ 'ai_content_generated' => true, 'images' => $images, ], $request ) ); } } V1/AI/Products.php 0000644 00000007401 15073235735 0007653 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1\AI; use Automattic\WooCommerce\Blocks\AI\Connection; use Automattic\WooCommerce\Blocks\Patterns\ProductUpdater; use Automattic\WooCommerce\StoreApi\Routes\V1\AbstractRoute; /** * Products class. * * @internal */ class Products extends AbstractRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'ai/products'; /** * The schema item identifier. * * @var string */ const SCHEMA_TYPE = 'ai/products'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/ai/products'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => [ Middleware::class, 'is_authorized' ], 'args' => [ 'business_description' => [ 'description' => __( 'The business description for a given store.', 'woocommerce' ), 'type' => 'string', ], 'images' => [ 'description' => __( 'The images for a given store.', 'woocommerce' ), 'type' => 'object', ], ], ], [ 'methods' => \WP_REST_Server::DELETABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => [ Middleware::class, 'is_authorized' ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Generate the content for the products. * * @param \WP_REST_Request $request Request object. * * @return bool|string|\WP_Error|\WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { $allow_ai_connection = get_option( 'woocommerce_blocks_allow_ai_connection' ); if ( ! $allow_ai_connection ) { return rest_ensure_response( $this->error_to_response( new \WP_Error( 'ai_connection_not_allowed', __( 'AI content generation is not allowed on this store. Update your store settings if you wish to enable this feature.', 'woocommerce' ) ) ) ); } $business_description = sanitize_text_field( wp_unslash( $request['business_description'] ) ); if ( empty( $business_description ) ) { $business_description = get_option( 'woo_ai_describe_store_description' ); } $ai_connection = new Connection(); $site_id = $ai_connection->get_site_id(); if ( is_wp_error( $site_id ) ) { return $this->error_to_response( $site_id ); } $token = $ai_connection->get_jwt_token( $site_id ); if ( is_wp_error( $token ) ) { return $this->error_to_response( $token ); } $images = $request['images']; $populate_products = ( new ProductUpdater() )->generate_content( $ai_connection, $token, $images, $business_description ); if ( is_wp_error( $populate_products ) ) { return $this->error_to_response( $populate_products ); } if ( ! isset( $populate_products['product_content'] ) ) { return $this->error_to_response( new \WP_Error( 'product_content_not_found', __( 'Product content not found.', 'woocommerce' ) ) ); } $product_content = $populate_products['product_content']; $item = array( 'ai_content_generated' => true, 'product_content' => $product_content, ); return rest_ensure_response( $item ); } /** * Remove products generated by AI. * * @param \WP_REST_Request $request Request object. * * @return bool|string|\WP_Error|\WP_REST_Response */ protected function get_route_delete_response( \WP_REST_Request $request ) { ( new ProductUpdater() )->reset_products_content(); return rest_ensure_response( array( 'removed' => true ) ); } } V1/AI/BusinessDescription.php 0000644 00000003631 15073235735 0012050 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1\AI; use Automattic\WooCommerce\StoreApi\Routes\V1\AbstractRoute; /** * BusinessDescription class. * * @internal */ class BusinessDescription extends AbstractRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'ai/business-description'; /** * The schema item identifier. * * @var string */ const SCHEMA_TYPE = 'ai/business-description'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/ai/business-description'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => [ Middleware::class, 'is_authorized' ], 'args' => [ 'business_description' => [ 'description' => __( 'The business description for a given store.', 'woocommerce' ), 'type' => 'string', ], ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Update the last business description. * * @param \WP_REST_Request $request Request object. * * @return bool|string|\WP_Error|\WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { $business_description = $request->get_param( 'business_description' ); if ( ! $business_description ) { return $this->error_to_response( new \WP_Error( 'invalid_business_description', __( 'Invalid business description.', 'woocommerce' ) ) ); } update_option( 'last_business_description_with_ai_content_generated', $business_description ); return rest_ensure_response( array( 'ai_content_generated' => true, ) ); } } V1/AI/Product.php 0000644 00000004266 15073235735 0007476 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1\AI; use Automattic\WooCommerce\Blocks\Patterns\ProductUpdater; use Automattic\WooCommerce\StoreApi\Routes\V1\AbstractRoute; /** * Product class. * * @internal */ class Product extends AbstractRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'ai/product'; /** * The schema item identifier. * * @var string */ const SCHEMA_TYPE = 'ai/product'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/ai/product'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => [ Middleware::class, 'is_authorized' ], 'args' => [ 'products_information' => [ 'description' => __( 'Data generated by AI for updating dummy products.', 'woocommerce' ), 'type' => 'object', ], 'last_product' => [ 'description' => __( 'Whether the product being updated is the last one in the loop', 'woocommerce' ), 'type' => 'boolean', ], ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Update product with the content and image powered by AI. * * @param \WP_REST_Request $request Request object. * * @return bool|string|\WP_Error|\WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { $product_updater = new ProductUpdater(); $product_information = $request['products_information'] ?? array(); if ( empty( $product_information ) ) { return rest_ensure_response( array( 'ai_content_generated' => true, ) ); } $product_updater->update_product_content( $product_information ); $last_product_to_update = $request['last_product'] ?? false; if ( $last_product_to_update ) { flush_rewrite_rules(); } return rest_ensure_response( array( 'ai_content_generated' => true, ) ); } } V1/AI/Middleware.php 0000644 00000002477 15073235735 0010135 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1\AI; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; /** * Middleware class. * * @internal */ class Middleware { /** * Ensure that the user is allowed to make this request. * * @throws RouteException If the user is not allowed to make this request. * @return boolean */ public static function is_authorized() { try { if ( ! current_user_can( 'manage_options' ) ) { throw new RouteException( 'woocommerce_rest_invalid_user', __( 'You are not allowed to make this request. Please make sure you are logged in.', 'woocommerce' ), 403 ); } } catch ( RouteException $error ) { return new \WP_Error( $error->getErrorCode(), $error->getMessage(), array( 'status' => $error->getCode() ) ); } $allow_ai_connection = get_option( 'woocommerce_blocks_allow_ai_connection' ); if ( ! $allow_ai_connection ) { try { throw new RouteException( 'ai_connection_not_allowed', __( 'AI content generation is not allowed on this store. Update your store settings if you wish to enable this feature.', 'woocommerce' ), 403 ); } catch ( RouteException $error ) { return new \WP_Error( $error->getErrorCode(), $error->getMessage(), array( 'status' => $error->getCode() ) ); } } return true; } } V1/AI/StoreTitle.php 0000644 00000007074 15073235735 0010154 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1\AI; use Automattic\WooCommerce\Blocks\AI\Connection; use Automattic\WooCommerce\StoreApi\Routes\V1\AbstractRoute; /** * StoreTitle class. * * @internal */ class StoreTitle extends AbstractRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'ai/store-title'; /** * The schema item identifier. * * @var string */ const SCHEMA_TYPE = 'ai/store-title'; /** * The store title option name. * * @var string */ const STORE_TITLE_OPTION_NAME = 'blogname'; /** * The default store title. * * @var string */ const DEFAULT_TITLE = 'Site Title'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/ai/store-title'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => [ Middleware::class, 'is_authorized' ], 'args' => [ 'business_description' => [ 'description' => __( 'The business description for a given store.', 'woocommerce' ), 'type' => 'string', ], ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Update the store title powered by AI. * * @param \WP_REST_Request $request Request object. * * @return bool|string|\WP_Error|\WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { $business_description = $request->get_param( 'business_description' ); if ( ! $business_description ) { return $this->error_to_response( new \WP_Error( 'invalid_business_description', __( 'Invalid business description.', 'woocommerce' ) ) ); } $store_title = get_option( 'blogname' ); if ( ! ( empty( $store_title ) || self::DEFAULT_TITLE === $store_title ) ) { return rest_ensure_response( array( 'ai_content_generated' => false ) ); } $ai_generated_title = $this->generate_ai_title( $business_description ); if ( is_wp_error( $ai_generated_title ) ) { return $this->error_to_response( $ai_generated_title ); } update_option( self::STORE_TITLE_OPTION_NAME, $ai_generated_title ); return rest_ensure_response( array( 'ai_content_generated' => true, ) ); } /** * Generate the store title powered by AI. * * @param string $business_description The business description for a given store. * * @return string|\WP_Error|\WP_REST_Response The store title generated by AI. */ private function generate_ai_title( $business_description ) { $ai_connection = new Connection(); $site_id = $ai_connection->get_site_id(); if ( is_wp_error( $site_id ) ) { return $this->error_to_response( $site_id ); } $token = $ai_connection->get_jwt_token( $site_id ); if ( is_wp_error( $token ) ) { return $this->error_to_response( $token ); } $prompt = "Generate a store title for a store that has the following: '$business_description'. The length of the title should be 1 and 3 words. The result should include only the store title without any other explanation, number or punctuation marks"; $ai_response = $ai_connection->fetch_ai_response( $token, $prompt ); if ( is_wp_error( $ai_response ) ) { return $this->error_to_response( $ai_response ); } if ( ! isset( $ai_response['completion'] ) ) { return ''; } return $ai_response['completion']; } } V1/AI/StoreInfo.php 0000644 00000003322 15073235735 0007756 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1\AI; use Automattic\WooCommerce\Blocks\Patterns\PatternsHelper; use Automattic\WooCommerce\Blocks\Patterns\ProductUpdater; use Automattic\WooCommerce\StoreApi\Routes\V1\AbstractRoute; /** * StoreInfo class. * * @internal */ class StoreInfo extends AbstractRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'ai/store-info'; /** * The schema item identifier. * * @var string */ const SCHEMA_TYPE = 'ai/store-info'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/ai/store-info'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => [ Middleware::class, 'is_authorized' ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Update the store title powered by AI. * * @param \WP_REST_Request $request Request object. * * @return bool|string|\WP_Error|\WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $product_updater = new ProductUpdater(); $patterns = PatternsHelper::get_patterns_ai_data_post(); $products = $product_updater->fetch_product_ids( 'dummy' ); if ( empty( $products ) && ! isset( $patterns ) ) { return rest_ensure_response( array( 'is_ai_generated' => false, ) ); } return rest_ensure_response( array( 'is_ai_generated' => true, ) ); } } V1/ProductsBySlug.php 0000644 00000005021 15073235735 0010504 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; /** * ProductsBySlug class. */ class ProductsBySlug extends AbstractRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'products-by-slug'; /** * The routes schema. * * @var string */ const SCHEMA_TYPE = 'product'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products/(?P<slug>[\S]+)'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ 'args' => array( 'slug' => array( 'description' => __( 'Slug of the resource.', 'woocommerce' ), 'type' => 'string', ), ), [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view', ) ), ), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get a single item. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $slug = sanitize_title( $request['slug'] ); $object = $this->get_product_by_slug( $slug ); if ( ! $object ) { $object = $this->get_product_variation_by_slug( $slug ); } if ( ! $object || 0 === $object->get_id() ) { throw new RouteException( 'woocommerce_rest_product_invalid_slug', __( 'Invalid product slug.', 'woocommerce' ), 404 ); } return rest_ensure_response( $this->schema->get_item_response( $object ) ); } /** * Get a product by slug. * * @param string $slug The slug of the product. */ public function get_product_by_slug( $slug ) { return wc_get_product( get_page_by_path( $slug, OBJECT, 'product' ) ); } /** * Get a product variation by slug. * * @param string $slug The slug of the product variation. */ private function get_product_variation_by_slug( $slug ) { global $wpdb; $result = $wpdb->get_results( $wpdb->prepare( "SELECT ID, post_name, post_parent, post_type FROM $wpdb->posts WHERE post_name = %s AND post_type = 'product_variation'", $slug ) ); if ( ! $result ) { return null; } return wc_get_product( $result[0]->ID ); } } V1/CartCoupons.php 0000644 00000007430 15073235735 0010021 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; /** * CartCoupons class. */ class CartCoupons extends AbstractCartRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'cart-coupons'; /** * The routes schema. * * @var string */ const SCHEMA_TYPE = 'cart-coupon'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/coupons'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'context' => $this->get_context_param( [ 'default' => 'view' ] ), ], ], [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => $this->schema->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ), ], [ 'methods' => \WP_REST_Server::DELETABLE, 'permission_callback' => '__return_true', 'callback' => [ $this, 'get_response' ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Get a collection of cart coupons. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $cart_coupons = $this->cart_controller->get_cart_coupons(); $items = []; foreach ( $cart_coupons as $coupon_code ) { $response = rest_ensure_response( $this->schema->get_item_response( $coupon_code ) ); $response->add_links( $this->prepare_links( $coupon_code, $request ) ); $response = $this->prepare_response_for_collection( $response ); $items[] = $response; } $response = rest_ensure_response( $items ); return $response; } /** * Add a coupon to the cart and return the result. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { if ( ! wc_coupons_enabled() ) { throw new RouteException( 'woocommerce_rest_cart_coupon_disabled', __( 'Coupons are disabled.', 'woocommerce' ), 404 ); } try { $this->cart_controller->apply_coupon( $request['code'] ); } catch ( \WC_REST_Exception $e ) { throw new RouteException( $e->getErrorCode(), $e->getMessage(), $e->getCode() ); } $response = $this->prepare_item_for_response( $request['code'], $request ); $response->set_status( 201 ); return $response; } /** * Deletes all coupons in the cart. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_delete_response( \WP_REST_Request $request ) { $cart = $this->cart_controller->get_cart_instance(); $cart->remove_coupons(); $cart->calculate_totals(); return new \WP_REST_Response( [], 200 ); } /** * Prepare links for the request. * * @param string $coupon_code Coupon code. * @param \WP_REST_Request $request Request object. * @return array */ protected function prepare_links( $coupon_code, $request ) { $base = $this->get_namespace() . $this->get_path(); $links = array( 'self' => array( 'href' => rest_url( trailingslashit( $base ) . $coupon_code ), ), 'collection' => array( 'href' => rest_url( $base ), ), ); return $links; } } V1/CartUpdateCustomer.php 0000644 00000026415 15073235735 0011343 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Utilities\DraftOrderTrait; use Automattic\WooCommerce\StoreApi\Utilities\ValidationUtils; /** * CartUpdateCustomer class. * * Updates the customer billing and shipping addresses, recalculates the cart totals, and returns an updated cart. */ class CartUpdateCustomer extends AbstractCartRoute { use DraftOrderTrait; /** * The route identifier. * * @var string */ const IDENTIFIER = 'cart-update-customer'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/update-customer'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'billing_address' => [ 'description' => __( 'Billing address.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'properties' => $this->schema->billing_address_schema->get_properties(), 'sanitize_callback' => null, ], 'shipping_address' => [ 'description' => __( 'Shipping address.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'properties' => $this->schema->shipping_address_schema->get_properties(), 'sanitize_callback' => null, ], ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Validate address params now they are populated. * * @param \WP_REST_Request $request Request object. * @param array $billing Billing address. * @param array $shipping Shipping address. * @return \WP_Error|true */ protected function validate_address_params( $request, $billing, $shipping ) { $posted_billing = isset( $request['billing_address'] ); $posted_shipping = isset( $request['shipping_address'] ); $invalid_params = array(); $invalid_details = array(); if ( $posted_billing ) { $billing_validation_check = $this->schema->billing_address_schema->validate_callback( $billing, $request, 'billing_address' ); if ( false === $billing_validation_check ) { $invalid_params['billing_address'] = __( 'Invalid parameter.', 'woocommerce' ); } elseif ( is_wp_error( $billing_validation_check ) ) { $invalid_params['billing_address'] = implode( ' ', $billing_validation_check->get_error_messages() ); $invalid_details['billing_address'] = \rest_convert_error_to_response( $billing_validation_check )->get_data(); } } if ( $posted_shipping ) { $shipping_validation_check = $this->schema->shipping_address_schema->validate_callback( $shipping, $request, 'shipping_address' ); if ( false === $shipping_validation_check ) { $invalid_params['shipping_address'] = __( 'Invalid parameter.', 'woocommerce' ); } elseif ( is_wp_error( $shipping_validation_check ) ) { $invalid_params['shipping_address'] = implode( ' ', $shipping_validation_check->get_error_messages() ); $invalid_details['shipping_address'] = \rest_convert_error_to_response( $shipping_validation_check )->get_data(); } } if ( $invalid_params ) { return new \WP_Error( 'rest_invalid_param', /* translators: %s: List of invalid parameters. */ sprintf( __( 'Invalid parameter(s): %s', 'woocommerce' ), implode( ', ', array_keys( $invalid_params ) ) ), [ 'status' => 400, 'params' => $invalid_params, 'details' => $invalid_details, ] ); } return true; } /** * Handle the request and return a valid response for this endpoint. * * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { $cart = $this->cart_controller->get_cart_instance(); $customer = wc()->customer; // Get data from request object and merge with customer object, then sanitize. $billing = $this->schema->billing_address_schema->sanitize_callback( wp_parse_args( $request['billing_address'] ?? [], $this->get_customer_billing_address( $customer ) ), $request, 'billing_address' ); $shipping = $this->schema->billing_address_schema->sanitize_callback( wp_parse_args( $request['shipping_address'] ?? [], $this->get_customer_shipping_address( $customer ) ), $request, 'shipping_address' ); // If the cart does not need shipping, shipping address is forced to match billing address unless defined. if ( ! $cart->needs_shipping() && ! isset( $request['shipping_address'] ) ) { $shipping = $billing; } // Run validation and sanitization now that the cart and customer data is loaded. $billing = $this->schema->billing_address_schema->sanitize_callback( $billing, $request, 'billing_address' ); $shipping = $this->schema->shipping_address_schema->sanitize_callback( $shipping, $request, 'shipping_address' ); // Validate data now everything is clean.. $validation_check = $this->validate_address_params( $request, $billing, $shipping ); if ( is_wp_error( $validation_check ) ) { return rest_ensure_response( $validation_check ); } $customer->set_props( array( 'billing_first_name' => $billing['first_name'] ?? null, 'billing_last_name' => $billing['last_name'] ?? null, 'billing_company' => $billing['company'] ?? null, 'billing_address_1' => $billing['address_1'] ?? null, 'billing_address_2' => $billing['address_2'] ?? null, 'billing_city' => $billing['city'] ?? null, 'billing_state' => $billing['state'] ?? null, 'billing_postcode' => $billing['postcode'] ?? null, 'billing_country' => $billing['country'] ?? null, 'billing_phone' => $billing['phone'] ?? null, 'billing_email' => $billing['email'] ?? null, 'shipping_first_name' => $shipping['first_name'] ?? null, 'shipping_last_name' => $shipping['last_name'] ?? null, 'shipping_company' => $shipping['company'] ?? null, 'shipping_address_1' => $shipping['address_1'] ?? null, 'shipping_address_2' => $shipping['address_2'] ?? null, 'shipping_city' => $shipping['city'] ?? null, 'shipping_state' => $shipping['state'] ?? null, 'shipping_postcode' => $shipping['postcode'] ?? null, 'shipping_country' => $shipping['country'] ?? null, 'shipping_phone' => $shipping['phone'] ?? null, ) ); // We want to only get additional fields passed, since core ones are already saved. $core_fields = array_keys( $this->additional_fields_controller->get_core_fields() ); $additional_shipping_values = array_diff_key( $shipping, array_flip( $core_fields ) ); $additional_billing_values = array_diff_key( $billing, array_flip( $core_fields ) ); // We save them one by one, and we add the group prefix. foreach ( $additional_shipping_values as $key => $value ) { $this->additional_fields_controller->persist_field_for_customer( "/shipping/{$key}", $value, $customer ); } foreach ( $additional_billing_values as $key => $value ) { $this->additional_fields_controller->persist_field_for_customer( "/billing/{$key}", $value, $customer ); } wc_do_deprecated_action( 'woocommerce_blocks_cart_update_customer_from_request', array( $customer, $request, ), '7.2.0', 'woocommerce_store_api_cart_update_customer_from_request', 'This action was deprecated in WooCommerce Blocks version 7.2.0. Please use woocommerce_store_api_cart_update_customer_from_request instead.' ); /** * Fires when the Checkout Block/Store API updates a customer from the API request data. * * @since 7.2.0 * * @param \WC_Customer $customer Customer object. * @param \WP_REST_Request $request Full details about the request. */ do_action( 'woocommerce_store_api_cart_update_customer_from_request', $customer, $request ); $customer->save(); $this->cart_controller->calculate_totals(); return rest_ensure_response( $this->schema->get_item_response( $cart ) ); } /** * Get full customer billing address. * * @param \WC_Customer $customer Customer object. * @return array */ protected function get_customer_billing_address( \WC_Customer $customer ) { $validation_util = new ValidationUtils(); $billing_country = $customer->get_billing_country(); $billing_state = $customer->get_billing_state(); $additional_fields = $this->additional_fields_controller->get_all_fields_from_customer( $customer ); $additional_fields = array_reduce( array_keys( $additional_fields ), function( $carry, $key ) use ( $additional_fields ) { if ( 0 === strpos( $key, '/billing/' ) ) { $value = $additional_fields[ $key ]; $key = str_replace( '/billing/', '', $key ); $carry[ $key ] = $value; } return $carry; }, array() ); /** * There's a bug in WooCommerce core in which not having a state ("") would result in us validating against the store's state. * This resets the state to an empty string if it doesn't match the country. * * @todo Removing this handling once we fix the issue with the state value always being the store one. */ if ( ! $validation_util->validate_state( $billing_state, $billing_country ) ) { $billing_state = ''; } return array_merge( [ 'first_name' => $customer->get_billing_first_name(), 'last_name' => $customer->get_billing_last_name(), 'company' => $customer->get_billing_company(), 'address_1' => $customer->get_billing_address_1(), 'address_2' => $customer->get_billing_address_2(), 'city' => $customer->get_billing_city(), 'state' => $billing_state, 'postcode' => $customer->get_billing_postcode(), 'country' => $billing_country, 'phone' => $customer->get_billing_phone(), 'email' => $customer->get_billing_email(), ], $additional_fields ); } /** * Get full customer shipping address. * * @param \WC_Customer $customer Customer object. * @return array */ protected function get_customer_shipping_address( \WC_Customer $customer ) { $additional_fields = $this->additional_fields_controller->get_all_fields_from_customer( $customer ); $additional_fields = array_reduce( array_keys( $additional_fields ), function( $carry, $key ) use ( $additional_fields ) { if ( 0 === strpos( $key, '/shipping/' ) ) { $value = $additional_fields[ $key ]; $key = str_replace( '/shipping/', '', $key ); $carry[ $key ] = $value; } return $carry; }, array() ); return array_merge( [ 'first_name' => $customer->get_shipping_first_name(), 'last_name' => $customer->get_shipping_last_name(), 'company' => $customer->get_shipping_company(), 'address_1' => $customer->get_shipping_address_1(), 'address_2' => $customer->get_shipping_address_2(), 'city' => $customer->get_shipping_city(), 'state' => $customer->get_shipping_state(), 'postcode' => $customer->get_shipping_postcode(), 'country' => $customer->get_shipping_country(), 'phone' => $customer->get_shipping_phone(), ], $additional_fields ); } } V1/AbstractTermsRoute.php 0000644 00000011306 15073235735 0011353 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Utilities\Pagination; use WP_Term_Query; /** * AbstractTermsRoute class. */ abstract class AbstractTermsRoute extends AbstractRoute { /** * The routes schema. * * @var string */ const SCHEMA_TYPE = 'term'; /** * Get the query params for collections of attributes. * * @return array */ public function get_collection_params() { $params = array(); $params['context'] = $this->get_context_param(); $params['context']['default'] = 'view'; $params['page'] = array( 'description' => __( 'Current page of the collection.', 'woocommerce' ), 'type' => 'integer', 'default' => 1, 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', 'minimum' => 1, ); $params['per_page'] = array( 'description' => __( 'Maximum number of items to be returned in result set. Defaults to no limit if left blank.', 'woocommerce' ), 'type' => 'integer', 'minimum' => 0, 'maximum' => 100, 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); $params['search'] = array( 'description' => __( 'Limit results to those matching a string.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => 'rest_validate_request_arg', ); $params['exclude'] = array( 'description' => __( 'Ensure result set excludes specific IDs.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), 'sanitize_callback' => 'wp_parse_id_list', ); $params['include'] = array( 'description' => __( 'Limit result set to specific ids.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), 'sanitize_callback' => 'wp_parse_id_list', ); $params['order'] = array( 'description' => __( 'Sort ascending or descending.', 'woocommerce' ), 'type' => 'string', 'default' => 'asc', 'enum' => array( 'asc', 'desc' ), 'validate_callback' => 'rest_validate_request_arg', ); $params['orderby'] = array( 'description' => __( 'Sort by term property.', 'woocommerce' ), 'type' => 'string', 'default' => 'name', 'enum' => array( 'name', 'slug', 'count', ), 'validate_callback' => 'rest_validate_request_arg', ); $params['hide_empty'] = array( 'description' => __( 'If true, empty terms will not be returned.', 'woocommerce' ), 'type' => 'boolean', 'default' => true, ); return $params; } /** * Get terms matching passed in args. * * @param string $taxonomy Taxonomy to get terms from. * @param \WP_REST_Request $request Request object. * * @return \WP_REST_Response */ protected function get_terms_response( $taxonomy, $request ) { $page = (int) $request['page']; $per_page = $request['per_page'] ? (int) $request['per_page'] : 0; $prepared_args = array( 'taxonomy' => $taxonomy, 'exclude' => $request['exclude'], 'include' => $request['include'], 'order' => $request['order'], 'orderby' => $request['orderby'], 'hide_empty' => (bool) $request['hide_empty'], 'number' => $per_page, 'offset' => $per_page > 0 ? ( $page - 1 ) * $per_page : 0, 'search' => $request['search'], ); $term_query = new WP_Term_Query(); $objects = $term_query->query( $prepared_args ); $return = []; foreach ( $objects as $object ) { $data = $this->prepare_item_for_response( $object, $request ); $return[] = $this->prepare_response_for_collection( $data ); } $response = rest_ensure_response( $return ); // See if pagination is needed before calculating. if ( $per_page > 0 && ( count( $objects ) === $per_page || $page > 1 ) ) { $term_count = $this->get_term_count( $taxonomy, $prepared_args ); $response = ( new Pagination() )->add_headers( $response, $request, $term_count, ceil( $term_count / $per_page ) ); } return $response; } /** * Get count of terms for current query. * * @param string $taxonomy Taxonomy to get terms from. * @param array $args Array of args to pass to wp_count_terms. * @return int */ protected function get_term_count( $taxonomy, $args ) { $count_args = $args; unset( $count_args['number'], $count_args['offset'] ); return (int) wp_count_terms( $taxonomy, $count_args ); } } V1/CartExtensions.php 0000644 00000003437 15073235735 0010535 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; /** * CartExtensions class. */ class CartExtensions extends AbstractCartRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'cart-extensions'; /** * The routes schema. * * @var string */ const SCHEMA_TYPE = 'cart-extensions'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/extensions'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'namespace' => [ 'description' => __( 'Extension\'s name - this will be used to ensure the data in the request is routed appropriately.', 'woocommerce' ), 'type' => 'string', ], 'data' => [ 'description' => __( 'Additional data to pass to the extension', 'woocommerce' ), 'type' => 'object', ], ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Handle the request and return a valid response for this endpoint. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { try { return $this->schema->get_item_response( $request ); } catch ( \WC_REST_Exception $e ) { throw new RouteException( $e->getErrorCode(), $e->getMessage(), $e->getCode() ); } } } V1/Order.php 0000644 00000004135 15073235735 0006633 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\SchemaController; use Automattic\WooCommerce\StoreApi\Schemas\v1\AbstractSchema; use Automattic\WooCommerce\StoreApi\Utilities\OrderController; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; use Automattic\WooCommerce\StoreApi\Utilities\OrderAuthorizationTrait; /** * Order class. */ class Order extends AbstractRoute { use OrderAuthorizationTrait; /** * The route identifier. * * @var string */ const IDENTIFIER = 'order'; /** * The schema item identifier. * * @var string */ const SCHEMA_TYPE = 'order'; /** * Order controller class instance. * * @var OrderController */ protected $order_controller; /** * Constructor. * * @param SchemaController $schema_controller Schema Controller instance. * @param AbstractSchema $schema Schema class for this route. */ public function __construct( SchemaController $schema_controller, AbstractSchema $schema ) { parent::__construct( $schema_controller, $schema ); $this->order_controller = new OrderController(); } /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/order/(?P<id>[\d]+)'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => [ $this, 'is_authorized' ], 'args' => [ 'context' => $this->get_context_param( [ 'default' => 'view' ] ), ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Handle the request and return a valid response for this endpoint. * * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $order_id = absint( $request['id'] ); return rest_ensure_response( $this->schema->get_item_response( wc_get_order( $order_id ) ) ); } } V1/ProductAttributeTerms.php 0000644 00000003636 15073235735 0012104 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; /** * ProductAttributeTerms class. */ class ProductAttributeTerms extends AbstractTermsRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'product-attribute-terms'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products/attributes/(?P<attribute_id>[\d]+)/terms'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ 'args' => array( 'attribute_id' => array( 'description' => __( 'Unique identifier for the attribute.', 'woocommerce' ), 'type' => 'integer', ), ), [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => $this->get_collection_params(), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get the query params for collections of attributes. * * @return array */ public function get_collection_params() { $params = parent::get_collection_params(); $params['orderby']['enum'][] = 'menu_order'; return $params; } /** * Get a collection of attribute terms. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $attribute = wc_get_attribute( $request['attribute_id'] ); if ( ! $attribute || ! taxonomy_exists( $attribute->slug ) ) { throw new RouteException( 'woocommerce_rest_taxonomy_invalid', __( 'Attribute does not exist.', 'woocommerce' ), 404 ); } return $this->get_terms_response( $attribute->slug, $request ); } } V1/ProductCategories.php 0000644 00000002243 15073235735 0011204 0 ustar 00 <?php namespace Automattic\WooCommerce\StoreApi\Routes\V1; /** * ProductCategories class. */ class ProductCategories extends AbstractTermsRoute { /** * The route identifier. * * @var string */ const IDENTIFIER = 'product-categories'; /** * The routes schema. * * @var string */ const SCHEMA_TYPE = 'product-category'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products/categories'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => $this->get_collection_params(), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get a collection of terms. * * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { return $this->get_terms_response( 'product_cat', $request ); } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0 |
proxy
|
phpinfo
|
Settings