JezK
Edit File: ShopperListItems.php
<?php declare( strict_types = 1 ); namespace Automattic\WooCommerce\StoreApi\Routes\V1; use Automattic\WooCommerce\Internal\ShopperLists\ShopperList; use Automattic\WooCommerce\Internal\ShopperLists\ShopperListItem; use Automattic\WooCommerce\StoreApi\Exceptions\RouteException; /** * GET / POST on /shopper-lists/{slug}/items. * * GET returns the items in a list. * POST saves an item to the list either from an existing cart line or from direct item payload fields. */ class ShopperListItems extends AbstractRoute { // Stopgap CSRF guard, replaced once the upstream trait lands on trunk. use ShopperListsNonceCheck; /** * Route identifier. * * @var string */ const IDENTIFIER = 'shopper-list-items'; /** * Schema identifier this route uses. * * @var string */ const SCHEMA_TYPE = 'shopper-list-item'; /** * Get the path of this REST route. * * @return string */ public function get_path() { return self::get_path_regex(); } /** * Get the path regex for this REST route. * * @return string */ public static function get_path_regex() { return '/shopper-lists/(?P<slug>[a-z0-9-]+)/items'; } /** * Get method arguments for this REST route. * * @return array */ public function get_args() { return array( 'args' => array( 'slug' => array( 'description' => __( 'Stable slug for the list.', 'woocommerce' ), 'type' => 'string', ), ), array( 'methods' => \WP_REST_Server::READABLE, 'callback' => array( $this, 'get_response' ), 'permission_callback' => function () { return is_user_logged_in(); }, 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), array( 'methods' => \WP_REST_Server::CREATABLE, 'callback' => array( $this, 'get_response' ), 'permission_callback' => function () { return is_user_logged_in(); }, 'args' => array( 'cart_item_key' => array( 'description' => __( 'Existing cart item key to copy into the list.', 'woocommerce' ), 'type' => 'string', ), 'product_id' => array( 'description' => __( 'Product or variation ID to save. Required when cart_item_key is not supplied.', 'woocommerce' ), 'type' => 'integer', ), 'variation' => array( 'description' => __( 'Chosen attributes (for variations).', 'woocommerce' ), 'type' => 'array', 'context' => array( 'view', 'edit' ), 'items' => array( 'type' => 'object', 'properties' => array( 'attribute' => array( 'description' => __( 'Variation attribute name.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), 'value' => array( 'description' => __( 'Variation attribute value.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), ), ), ), 'quantity' => array( 'description' => __( 'Quantity for the saved item.', 'woocommerce' ), 'type' => 'integer', 'default' => 1, ), ), ), 'schema' => array( $this->schema, 'get_public_item_schema' ), ); } /** * Return the items in the requested list. * * @throws RouteException When the list doesn't exist. * * @param \WP_REST_Request $request Request object. * * @phpstan-param \WP_REST_Request<array<string, mixed>> $request * * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $list = ShopperList::get_by_slug( (string) $request['slug'] ); if ( ! $list ) { throw new RouteException( 'woocommerce_rest_shopper_list_not_found', esc_html__( 'Your saved list isn\'t available right now.', 'woocommerce' ), 404 ); } $items = array_values( $list->get_items() ); $this->prime_product_caches_for_items( $items ); $response = array(); foreach ( $items as $item ) { $response[] = $this->prepare_response_for_collection( $this->prepare_item_for_response( $item, $request ) ); } return rest_ensure_response( $response ); } /** * Add an item to the requested list from cart_item_key or direct product payload fields. * * @throws RouteException On validation failure. * * @param \WP_REST_Request $request Request object. * * @phpstan-param \WP_REST_Request<array<string, mixed>> $request * * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { $list = ShopperList::get_by_slug( (string) $request['slug'] ); if ( ! $list ) { throw new RouteException( 'woocommerce_rest_shopper_list_not_found', esc_html__( 'Your saved list isn\'t available right now.', 'woocommerce' ), 404 ); } [ $lookup_id, $variation, $quantity ] = $this->resolve_item_payload( $request ); try { $item = ShopperListItem::from_product( $lookup_id, $variation, $quantity ); } catch ( \InvalidArgumentException $e ) { throw new RouteException( 'woocommerce_rest_shopper_list_invalid_variation', esc_html( $e->getMessage() ), 400 ); } if ( ! $item ) { throw new RouteException( 'woocommerce_rest_shopper_list_unknown_product', esc_html__( 'No product exists for the supplied item.', 'woocommerce' ), 404 ); } $list->add_item( $item ); $list->save(); $saved = $list->find_item( $item->get_key() ) ?? $item; return new \WP_REST_Response( $this->schema->get_item_response( $saved ), 201 ); } /** * Resolve the POST input into a uniform payload (product lookup id, variation, quantity). * * Accepts either an existing cart_item_key, or direct product_id/variation_id/variation. * * @throws RouteException When neither a cart_item_key nor a product_id is supplied, or the cart_item_key is unknown. * * @param \WP_REST_Request $request Request object. * * @phpstan-param \WP_REST_Request<array<string, mixed>> $request * * @return array{0:int,1:array,2:int} `[ lookup_id, variation, quantity ]`. */ private function resolve_item_payload( \WP_REST_Request $request ): array { $cart_item_key = (string) $request->get_param( 'cart_item_key' ); if ( $cart_item_key ) { if ( ! did_action( 'woocommerce_load_cart_from_session' ) || ! wc()->cart ) { wc_load_cart(); } $cart_contents = wc()->cart->get_cart(); if ( empty( $cart_contents[ $cart_item_key ] ) ) { throw new RouteException( 'woocommerce_rest_shopper_list_invalid_cart_item_key', esc_html__( 'No cart item exists for the supplied key.', 'woocommerce' ), 404 ); } $line = $cart_contents[ $cart_item_key ]; $product_id = absint( $line['product_id'] ?? 0 ); $variation_id = absint( $line['variation_id'] ?? 0 ); $variation_attrs = isset( $line['variation'] ) && is_array( $line['variation'] ) ? $line['variation'] : array(); return array( $variation_id ? $variation_id : $product_id, $variation_attrs, absint( $line['quantity'] ?? 1 ), ); }//end if $product_id = absint( $request->get_param( 'product_id' ) ); if ( ! $product_id ) { throw new RouteException( 'woocommerce_rest_shopper_list_missing_item_input', esc_html__( 'Provide cart_item_key or product_id.', 'woocommerce' ), 400 ); } $variation = wp_list_pluck( (array) $request->get_param( 'variation' ), 'value', 'attribute' ); return array( $product_id, (array) array_combine( array_map( 'wc_variation_attribute_name', array_keys( $variation ) ), array_values( $variation ) ), absint( $request->get_param( 'quantity' ) ), ); } /** * Prime post caches before the per-item product lookup loop in the schema. * * @param ShopperListItem[] $items Items. */ private function prime_product_caches_for_items( array $items ): void { $ids = array_map( fn( $item ) => $item->get_variation_id() ? $item->get_variation_id() : $item->get_product_id(), $items ); _prime_post_caches( array_unique( $ids ) ); } }