编辑文件:GetProduct.php
<?php declare(strict_types=1); namespace Automattic\WooCommerce\Api\Queries\Products; use Automattic\WooCommerce\Api\Attributes\Description; use Automattic\WooCommerce\Api\Attributes\Name; use Automattic\WooCommerce\Api\Attributes\RequiredCapability; use Automattic\WooCommerce\Api\Attributes\ReturnType; use Automattic\WooCommerce\Api\UnauthorizedException; use Automattic\WooCommerce\Api\Interfaces\Product; use Automattic\WooCommerce\Api\Utils\Products\ProductMapper; /** * Query to retrieve a single product by ID. * * Demonstrates: authorize(), $_query_info, UnauthorizedException. * * Authorization logic: admins (manage_woocommerce) can read any product, * non-admin users can only read their own products. */ #[Name( 'product' )] #[Description( 'Retrieve a single product by ID.' )] #[RequiredCapability( 'read_product' )] class GetProduct { /** * Authorize access to a specific product. * * Admins can read any product. Non-admin users can only read products * they authored themselves. * * Every inaccessible case throws `UnauthorizedException('Product not * found.')` — whether the ID doesn't exist, points at a non-product * post type, or points at a product the caller doesn't own. This * prevents callers from enumerating product IDs vs non-product post * IDs via the response they get back (which would otherwise be "not * found" vs "no permission"). * * @param int $id The product ID. * @param bool $_preauthorized Whether the declared capability check passed. * @return bool Whether the current user can read this product. * @throws UnauthorizedException When the product is not accessible. */ public function authorize( int $id, bool $_preauthorized ): bool { // Reject non-positive IDs up front. `get_post( 0 )` inside a // WordPress loop returns `$GLOBALS['post']` (not null), so a bare // `get_post( $id )` below would accidentally operate on whatever // global post was set upstream of this request. if ( $id <= 0 ) { throw new UnauthorizedException( 'Product not found.' ); } $post = get_post( $id ); if ( ! $post || 'product' !== $post->post_type ) { throw new UnauthorizedException( 'Product not found.' ); } // Honor the declared #[RequiredCapability] (read_product). if ( $_preauthorized ) { return true; } // `manage_woocommerce` is the canonical "admin sees everything" // capability in WooCommerce. The declared #[RequiredCapability] // pre-authorizes on `read_product` (the read-level post-type cap, // which is what the schema advertises), but an admin whose cap set // grants `manage_woocommerce` without `read_product` would // otherwise fall through to the ownership check and get "Product // not found" for any product they don't own — contrary to the // documented admin-can-see-everything contract. if ( current_user_can( 'manage_woocommerce' ) ) { return true; } // Non-admin users can only read their own products. Throw the same // "not found" exception rather than returning false — a distinct // "you don't have permission" error here would tell the caller // that the ID is a product (just not theirs), leaking the // product-ID space vs the rest of the post-ID space. // // Reject guest users explicitly: get_current_user_id() returns 0 // for unauthenticated callers, and products created via WP-CLI, // imports, or programmatic inserts without an author can have // post_author = 0 — a bare `!==` check would mis-grant access to // anonymous callers for those products. $current_user_id = get_current_user_id(); if ( 0 === $current_user_id || $current_user_id !== (int) $post->post_author ) { throw new UnauthorizedException( 'Product not found.' ); } return true; } /** * Retrieve a product by ID. * * @param int $id The product ID. * @param ?array $_query_info Unified query info tree from the GraphQL request. * @return ?object */ #[ReturnType( Product::class )] public function execute( #[Description( 'The ID of the product to retrieve.' )] int $id, ?array $_query_info = null, ): ?object { // Mirrors the guard in authorize(): never pass a non-positive ID to // wc_get_product(). authorize() would normally reject these first, // but a future caller path might invoke execute() directly. if ( $id <= 0 ) { return null; } $wc_product = wc_get_product( $id ); if ( ! $wc_product instanceof \WC_Product ) { return null; } return ProductMapper::from_wc_product( $wc_product, $_query_info ); } }
保存
返回