get_cogs_total_value() ); return ( null === $cents || $cents <= 0 ) ? null : $cents; } /** * Units still available, or null when it cannot be told. LotCost reads no order: a piece counts as sold when * it has left the stock. The stock is known when WooCommerce counts it, or when the stock status says "out of stock". * * @param \WC_Product $product Product or variation. */ private static function available_units( \WC_Product $product ): ?int { if ( true === $product->managing_stock() ) { $quantity = $product->get_stock_quantity(); return null === $quantity ? null : max( 0, (int) $quantity ); } return 'outofstock' === $product->get_stock_status() ? 0 : null; } /** * Describe one piece (a simple product or a variation). * * @param \WC_Product $product Simple product or variation. * @param string $product_title Name of the product (the parent's name for a variation). * @return array Same shape as CatalogVariantInfo in src/types.ts. */ private static function variant_info( \WC_Product $product, string $product_title ): array { $is_variation = $product instanceof \WC_Product_Variation; $label = $is_variation ? wc_get_formatted_variation( $product, true, false ) : ''; return array( 'id' => (string) $product->get_id(), 'productId' => (string) ( $is_variation ? $product->get_parent_id() : $product->get_id() ), 'productTitle' => $product_title, 'title' => '' !== $label ? $label : ( $is_variation ? '#' . $product->get_id() : '' ), 'priceCents' => self::to_cents( $product->get_price() ), 'costCents' => self::store_cost_cents( $product ), 'inventoryQuantity' => self::available_units( $product ), ); } /** * Describe pieces by id. Ids that are not a piece (missing, in the trash, another product type, a variation whose * product is gone) are left out. * * @param array $variant_ids Ids of simple products and variations. * @return array Variant id (string) => description. */ public function describe_variants( array $variant_ids ): array { $ids = array(); foreach ( $variant_ids as $variant_id ) { $id = absint( $variant_id ); if ( $id > 0 ) { $ids[ $id ] = $id; } } if ( ! $ids ) { return array(); } // One query warms the caches; anything it does not return is read one by one. $loaded = array(); foreach ( array_chunk( array_values( $ids ), 200 ) as $chunk ) { $products = wc_get_products( array( 'include' => $chunk, 'type' => array( 'simple', 'variation' ), 'status' => array( 'publish', 'draft', 'pending', 'private', 'future' ), 'limit' => count( $chunk ), 'return' => 'objects', ) ); foreach ( $products as $product ) { $loaded[ $product->get_id() ] = $product; } } $parents = array(); $info = array(); foreach ( $ids as $id ) { $product = $loaded[ $id ] ?? wc_get_product( $id ); if ( ! $product || 'trash' === $product->get_status() ) { continue; } if ( $product instanceof \WC_Product_Variation ) { $parent_id = (int) $product->get_parent_id(); if ( ! isset( $parents[ $parent_id ] ) ) { $parent = wc_get_product( $parent_id ); $parents[ $parent_id ] = $parent ? $parent : false; } $parent = $parents[ $parent_id ]; if ( ! $parent || ! $parent->is_type( 'variable' ) || 'trash' === $parent->get_status() ) { continue; } $info[ (string) $id ] = self::variant_info( $product, $parent->get_name() ); } elseif ( $product->is_type( 'simple' ) ) { $info[ (string) $id ] = self::variant_info( $product, $product->get_name() ); } } return $info; } /** * Find simple and variable products by title for the "add pieces" picker. A variable product comes with its * variations, since each variation is a piece. * * @param string $term Search text. * @param int $limit Maximum number of products. * @return array[] Each: id, title, type, variants (descriptions). */ public function search( string $term, int $limit = 20 ): array { $args = array( 'type' => self::SUPPORTED_TYPES, 'status' => array( 'publish', 'draft', 'pending', 'private' ), 'limit' => max( 1, min( 50, $limit ) ), 'orderby' => 'title', 'order' => 'ASC', 'return' => 'objects', ); if ( '' !== $term ) { $args['s'] = $term; } $hits = array(); foreach ( wc_get_products( $args ) as $product ) { $variants = array(); if ( $product->is_type( 'variable' ) ) { foreach ( array_slice( $product->get_children(), 0, 100 ) as $child_id ) { $variation = wc_get_product( $child_id ); if ( $variation instanceof \WC_Product_Variation ) { $variants[] = self::variant_info( $variation, $product->get_name() ); } } } else { $variants[] = self::variant_info( $product, $product->get_name() ); } $hits[] = array( 'id' => (string) $product->get_id(), 'title' => $product->get_name(), 'type' => $product->get_type(), 'variants' => $variants, ); } return $hits; } }