golden hour
/home/godaofbq/hyperbarichelp.com/wp-content/plugins/woocommerce/src/Api/Queries/Products
⬆️ Go Up
Upload
File/Folder
Size
Actions
.htaccess
420 B
Del
OK
GetProduct.php
4.47 KB
Del
OK
ListProducts.php
8.35 KB
Del
OK
Edit: ListProducts.php
<?php declare(strict_types=1); namespace Automattic\WooCommerce\Api\Queries\Products; use Automattic\WooCommerce\Api\ApiException; use Automattic\WooCommerce\Api\Attributes\ConnectionOf; use Automattic\WooCommerce\Api\Attributes\Description; use Automattic\WooCommerce\Api\Attributes\Name; use Automattic\WooCommerce\Api\Attributes\RequiredCapability; use Automattic\WooCommerce\Api\Attributes\Unroll; use Automattic\WooCommerce\Api\Enums\Products\ProductType; use Automattic\WooCommerce\Api\Enums\Products\StockStatus; use Automattic\WooCommerce\Api\InputTypes\Products\ProductFilterInput; use Automattic\WooCommerce\Api\Pagination\Connection; use Automattic\WooCommerce\Api\Pagination\Edge; use Automattic\WooCommerce\Api\Pagination\IdCursorFilter; use Automattic\WooCommerce\Api\Pagination\PageInfo; use Automattic\WooCommerce\Api\Pagination\PaginationParams; use Automattic\WooCommerce\Api\Interfaces\Product; use Automattic\WooCommerce\Api\Utils\Products\ProductMapper; /** * Query to list products with cursor-based pagination. * * Demonstrates: #[Unroll] on parameter, enum as direct param, multiple capabilities. */ #[Name( 'products' )] #[Description( 'List products with cursor-based pagination and optional filtering.' )] #[RequiredCapability( 'manage_woocommerce' )] #[RequiredCapability( 'edit_products' )] class ListProducts { /** * List products with optional filtering and pagination. * * @param PaginationParams $pagination The pagination parameters. * @param ProductFilterInput $filters Filter criteria (unrolled to flat args). * @param ?ProductType $product_type Optional product type filter. * @param ?array $_query_info Unified query info tree from the GraphQL request. * @return Connection * @throws ApiException When an unsupported `stock_status` filter value is passed. */ #[ConnectionOf( Product::class )] public function execute( PaginationParams $pagination, #[Unroll] ProductFilterInput $filters, #[Description( 'Filter by product type.' )] ?ProductType $product_type = null, ?array $_query_info = null, ): Connection { $first = $pagination->first; $last = $pagination->last; $after = $pagination->after; $before = $pagination->before; $limit = $first ?? $last ?? PaginationParams::get_default_page_size(); $query_args = array( 'post_type' => 'product', 'posts_per_page' => $limit + 1, 'orderby' => 'ID', 'order' => null !== $last ? 'DESC' : 'ASC', 'post_status' => $filters->status?->value ?? 'any', ); // Product type filter via taxonomy. `ProductType::Other` is the // output-only signal for "stored product_type doesn't match any // known standard" (typically plugin-added types), mirroring how // `StockStatus::Other` is handled for the meta-query path above. // Map it to NOT IN the standard slugs rather than the literal // 'other' term, which wouldn't match anything. if ( null !== $product_type ) { if ( ProductType::Other === $product_type ) { $query_args['tax_query'] = array( array( 'taxonomy' => 'product_type', 'field' => 'slug', 'terms' => array_values( array_filter( array_map( static fn( ProductType $t ): string => $t->value, ProductType::cases() ), static fn( string $slug ): bool => ProductType::Other->value !== $slug ) ), 'operator' => 'NOT IN', ), ); } else { $query_args['tax_query'] = array( array( 'taxonomy' => 'product_type', 'field' => 'slug', 'terms' => $product_type->value, ), ); } } // Stock status filter via meta. `StockStatus::Other` means "stored // _stock_status isn't one of the three standard WooCommerce values" // (typically a plugin-added custom status), so it maps to NOT IN // those three. `default` throws INVALID_ARGUMENT so any future // enum case added without updating this match fails loudly with a // clean 400 instead of a PHP-level UnhandledMatchError → HTTP 500. if ( null !== $filters->stock_status ) { // phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Not HTML; serialized as JSON. $meta_clause = match ( $filters->stock_status ) { StockStatus::InStock => array( 'key' => '_stock_status', 'value' => 'instock', ), StockStatus::OutOfStock => array( 'key' => '_stock_status', 'value' => 'outofstock', ), StockStatus::OnBackorder => array( 'key' => '_stock_status', 'value' => 'onbackorder', ), StockStatus::Other => array( 'key' => '_stock_status', 'value' => array( 'instock', 'outofstock', 'onbackorder' ), 'compare' => 'NOT IN', ), default => throw new ApiException( sprintf( 'Unsupported stock_status filter value: %s.', $filters->stock_status->name ), 'INVALID_ARGUMENT', status_code: 400, ), }; // phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped $query_args['meta_query'] = array( $meta_clause ); } // Search filter. if ( null !== $filters->search ) { $query_args['s'] = $filters->search; } // Total count query. Derive from $query_args — which already has // the tax_query / meta_query / search clauses applied — *before* // we set cursor query vars on it. Building $count_args from scratch // with only post_status would drop every user filter and report the // count of "all products in that status" instead of "all products // matching the filters", making Relay consumers' "X of Y" wrong. // Only `found_posts` is read, so posts_per_page => 1 keeps the // underlying SELECT cheap. $count_args = $query_args; $count_args['posts_per_page'] = 1; $count_args['fields'] = 'ids'; $count_query = new \WP_Query( $count_args ); $total_count = $count_query->found_posts; // Cursor-based filtering via IdCursorFilter (see class docblock). if ( null !== $after ) { $query_args[ IdCursorFilter::AFTER_ID ] = IdCursorFilter::decode_id_cursor( $after, 'after' ); } if ( null !== $before ) { $query_args[ IdCursorFilter::BEFORE_ID ] = IdCursorFilter::decode_id_cursor( $before, 'before' ); } IdCursorFilter::ensure_registered(); $query = new \WP_Query( $query_args ); $posts = $query->posts; // Determine pagination. $has_extra = count( $posts ) > $limit; if ( $has_extra ) { $posts = array_slice( $posts, 0, $limit ); } if ( null !== $last ) { $posts = array_reverse( $posts ); } // Narrow $_query_info to the per-node selection so each mapped // product only fetches the subtrees the client actually asked for // under `nodes { ... }` / `edges { node { ... } }`. Without this, // ProductMapper::populate_common_fields() hits its null-$query_info // fallback and runs build_reviews() (plus its count query) for // every product on the page — N+1 on reviews even when no client // selected them. $node_query_info = ProductMapper::connection_node_info( $_query_info ); // Build edges and nodes. $edges = array(); $nodes = array(); foreach ( $posts as $post ) { $wc_product = wc_get_product( $post->ID ); if ( ! $wc_product instanceof \WC_Product ) { continue; } $product = ProductMapper::from_wc_product( $wc_product, $node_query_info ); $edge = new Edge(); $edge->cursor = base64_encode( (string) $product->id ); $edge->node = $product; $edges[] = $edge; $nodes[] = $product; } $page_info = new PageInfo(); // Relay semantics for backward pagination (`last`, `before`): the // returned window ends just before `$before`, so items after the // window exist whenever `$before` was supplied — not whenever // `$after` was. `has_previous_page` in the backward case is driven // by the "did we fetch limit+1?" sentinel (`$has_extra`). $page_info->has_next_page = null !== $last ? ( null !== $before ) : $has_extra; $page_info->has_previous_page = null !== $last ? $has_extra : ( null !== $after ); $page_info->start_cursor = ! empty( $edges ) ? $edges[0]->cursor : null; $page_info->end_cursor = ! empty( $edges ) ? $edges[ count( $edges ) - 1 ]->cursor : null; $connection = new Connection(); $connection->edges = $edges; $connection->nodes = $nodes; $connection->page_info = $page_info; $connection->total_count = $total_count; return $connection; } }
Save