$product->get_id(), 'title' => $product->get_name(), 'description' => wp_strip_all_tags( $product->get_description() ), 'short_description' => wp_strip_all_tags( $product->get_short_description() ), 'url' => get_permalink( $product->get_id() ), 'sku' => $product->get_sku(), 'price' => $product->get_price(), 'regular_price' => $product->get_regular_price(), 'sale_price' => $product->get_sale_price(), 'currency' => get_woocommerce_currency(), 'availability' => $this->map_availability( $product->get_stock_status() ), 'stock_quantity' => $product->get_stock_quantity(), 'condition' => 'new', 'brand' => $this->get_brand( $product ), 'gtin' => $this->get_gtin( $product ), 'mpn' => $this->get_mpn( $product ), 'isbn' => $this->get_isbn( $product ), 'images' => $this->get_images( $product ), 'categories' => $this->get_categories( $product ), 'weight' => $product->get_weight(), 'weight_unit' => get_option( 'woocommerce_weight_unit', 'kg' ), 'dimensions' => $this->get_dimensions( $product ), 'attributes' => $this->get_attributes( $product ), 'rating_value' => $this->get_rating_value( $product ), 'rating_count' => $this->get_rating_count( $product ), 'shipping_free' => $this->is_free_shipping( $product ), 'return_policy_url' => get_option( 'aicommerce_return_policy_url', '' ), 'variants' => array(), 'is_variable' => $product->is_type( 'variable' ), 'parent_id' => $product->get_parent_id(), 'date_created' => $product->get_date_created() ? $product->get_date_created()->format( 'c' ) : null, 'date_modified' => $product->get_date_modified() ? $product->get_date_modified()->format( 'c' ) : null, ); // Group variants for variable products. if ( $product->is_type( 'variable' ) && $product instanceof \WC_Product_Variable ) { $data['variants'] = $this->extract_variants( $product ); } /** * Filters the normalized product data for feed generation. * * @param array $data Normalized product data. * @param \WC_Product $product The WooCommerce product. */ return apply_filters( 'aicommerce_feed_product_data', $data, $product ); } /** * Extract data from multiple products with memory management. * * Unsets each product after extraction and flushes the object cache * if memory usage exceeds the threshold. * * @param array $products Array of WC_Product objects. * * @return array Array of normalized product data. */ public function extract_batch( array $products ): array { $extracted = array(); foreach ( $products as $key => $product ) { if ( ! $product instanceof \WC_Product ) { continue; } $extracted[] = $this->extract( $product ); // Memory management: release product reference. unset( $products[ $key ] ); // Flush object cache if memory usage is high. if ( memory_get_usage( true ) > self::MEMORY_THRESHOLD ) { wp_cache_flush(); } } return $extracted; } /** * Map WooCommerce stock status to feed availability values. * * @param string $stock_status WC stock status. * * @return string Feed-compatible availability value. */ private function map_availability( string $stock_status ): string { $map = array( 'instock' => 'in_stock', 'outofstock' => 'out_of_stock', 'onbackorder' => 'backorder', ); return $map[ $stock_status ] ?? 'out_of_stock'; } /** * Get brand with fallback chain. * * @param \WC_Product $product The product. * * @return string Brand name or empty string. */ private function get_brand( \WC_Product $product ): string { $brand = $product->get_meta( '_aicommerce_brand' ); if ( empty( $brand ) ) { $brand = $product->get_meta( '_wc_brand' ); } if ( empty( $brand ) && taxonomy_exists( 'product_brand' ) ) { $brand_terms = get_the_terms( $product->get_id(), 'product_brand' ); if ( is_array( $brand_terms ) && ! empty( $brand_terms ) ) { $brand = $brand_terms[0]->name; } } if ( empty( $brand ) ) { $brand = get_option( 'aicommerce_default_brand', '' ); } return (string) $brand; } /** * Get GTIN with fallback chain. * * @param \WC_Product $product The product. * * @return string GTIN or empty string. */ private function get_gtin( \WC_Product $product ): string { $gtin = $product->get_meta( '_aicommerce_gtin' ); if ( empty( $gtin ) && method_exists( $product, 'get_global_unique_id' ) ) { $gtin = $product->get_global_unique_id(); } if ( empty( $gtin ) ) { $gtin = $product->get_meta( '_global_unique_id' ); } return (string) $gtin; } /** * Get MPN with fallback chain. * * @param \WC_Product $product The product. * * @return string MPN or empty string. */ private function get_mpn( \WC_Product $product ): string { $mpn = $product->get_meta( '_aicommerce_mpn' ); if ( empty( $mpn ) ) { $mpn = $product->get_meta( '_mpn' ); } return (string) $mpn; } /** * Get ISBN from product meta. * * @param \WC_Product $product The product. * * @return string ISBN or empty string. */ private function get_isbn( \WC_Product $product ): string { $isbn = $product->get_meta( '_aicommerce_isbn' ); if ( empty( $isbn ) ) { $isbn = $product->get_meta( '_isbn' ); } return (string) $isbn; } /** * Get all product images with alt text. * * @param \WC_Product $product The product. * * @return array Array of image data arrays with 'url' and 'alt' keys. */ private function get_images( \WC_Product $product ): array { $images = array(); $main_id = $product->get_image_id(); if ( $main_id ) { $url = wp_get_attachment_url( $main_id ); if ( $url ) { $alt = get_post_meta( $main_id, '_wp_attachment_image_alt', true ); $images[] = array( 'url' => $url, 'alt' => is_string( $alt ) ? $alt : '', ); } } foreach ( $product->get_gallery_image_ids() as $gallery_id ) { $url = wp_get_attachment_url( $gallery_id ); if ( $url ) { $alt = get_post_meta( $gallery_id, '_wp_attachment_image_alt', true ); $images[] = array( 'url' => $url, 'alt' => is_string( $alt ) ? $alt : '', ); } } return $images; } /** * Get product categories with parent info. * * @param \WC_Product $product The product. * * @return array Array of category data. */ private function get_categories( \WC_Product $product ): array { $terms = get_the_terms( $product->get_id(), 'product_cat' ); if ( ! is_array( $terms ) ) { return array(); } $categories = array(); foreach ( $terms as $term ) { $categories[] = array( 'id' => $term->term_id, 'name' => $term->name, 'slug' => $term->slug, 'parent_id' => $term->parent, ); } return $categories; } /** * Get product dimensions. * * @param \WC_Product $product The product. * * @return array Dimension data. */ private function get_dimensions( \WC_Product $product ): array { return array( 'length' => $product->get_length(), 'width' => $product->get_width(), 'height' => $product->get_height(), 'unit' => get_option( 'woocommerce_dimension_unit', 'cm' ), ); } /** * Get product attributes. * * @param \WC_Product $product The product. * * @return array Array of attribute data. */ private function get_attributes( \WC_Product $product ): array { $attributes = array(); foreach ( $product->get_attributes() as $key => $attribute ) { if ( is_object( $attribute ) && method_exists( $attribute, 'get_options' ) ) { $attributes[ $key ] = array( 'name' => wc_attribute_label( $key, $product ), 'options' => $attribute->get_options(), 'visible' => $attribute->get_visible(), ); } } return $attributes; } /** * Get average product rating. * * @param \WC_Product $product The product. * * @return float Average rating or 0. */ private function get_rating_value( \WC_Product $product ): float { if ( method_exists( $product, 'get_average_rating' ) ) { return (float) $product->get_average_rating(); } return 0.0; } /** * Get number of product ratings/reviews. * * @param \WC_Product $product The product. * * @return int Rating count. */ private function get_rating_count( \WC_Product $product ): int { if ( method_exists( $product, 'get_rating_count' ) ) { return (int) $product->get_rating_count(); } return 0; } /** * Check if product qualifies for free shipping. * * @param \WC_Product $product The product. * * @return bool True if free shipping applies. */ private function is_free_shipping( \WC_Product $product ): bool { $shipping_class = $product->get_shipping_class(); return 'free-shipping' === $shipping_class || 'free_shipping' === $shipping_class; } /** * Extract variants for a variable product. * * @param \WC_Product_Variable $product The variable product. * * @return array Array of variant data. */ private function extract_variants( \WC_Product_Variable $product ): array { $variants = array(); $variation_ids = $product->get_children(); foreach ( $variation_ids as $variation_id ) { $variation = wc_get_product( $variation_id ); if ( ! $variation instanceof \WC_Product_Variation ) { continue; } $image_url = ''; $image_alt = ''; if ( $variation->get_image_id() ) { $image_url = wp_get_attachment_url( $variation->get_image_id() ); $alt = get_post_meta( $variation->get_image_id(), '_wp_attachment_image_alt', true ); $image_alt = is_string( $alt ) ? $alt : ''; } $variants[] = array( 'id' => $variation->get_id(), 'sku' => $variation->get_sku(), 'price' => $variation->get_price(), 'regular_price' => $variation->get_regular_price(), 'sale_price' => $variation->get_sale_price(), 'availability' => $this->map_availability( $variation->get_stock_status() ), 'stock_quantity' => $variation->get_stock_quantity(), 'gtin' => $this->get_gtin( $variation ), 'mpn' => $this->get_mpn( $variation ), 'image' => $image_url ?: '', 'image_alt' => $image_alt, 'attributes' => $variation->get_variation_attributes(), 'weight' => $variation->get_weight(), 'dimensions' => array( 'length' => $variation->get_length(), 'width' => $variation->get_width(), 'height' => $variation->get_height(), ), ); // Release variation reference. unset( $variation ); } return $variants; } }