option key. * @param string[] $sets The set ids, in the order the map was built with. * @return string The key, or an empty string when the pair is incomplete. */ function addon_boss_combo_price_key( array $picks, array $sets ): string { $parts = array(); foreach ( $sets as $sid ) { if ( ! isset( $picks[ $sid ] ) || '' === (string) $picks[ $sid ] ) { return ''; // an incomplete pair is not a pair. } $parts[] = $sid . '::' . $picks[ $sid ]; } return $parts ? implode( '|', $parts ) : ''; } /** The stored map, keyed the same way the pair prices are. */ const ADDON_BOSS_COMBO_SHIP_META = '_addon_boss_combo_ship'; /** * Store what each pair weighs and measures. * * @param WC_Product|int $product The product carrying the sets. * @param string[] $sets The set ids the pair key is built from. * @param array $rows Each: picks (set id => option key), plus any * of weight, length, width, height. */ function addon_boss_combo_ship_put( $product, array $sets, array $rows ): void { if ( ! $product instanceof WC_Product ) { $product = function_exists( 'wc_get_product' ) ? wc_get_product( (int) $product ) : null; } if ( ! $product instanceof WC_Product || ! function_exists( 'addon_boss_combo_price_key' ) ) { return; } $clean = array(); foreach ( $sets as $sid ) { $sid = (string) $sid; if ( '' !== $sid ) { $clean[] = $sid; } } sort( $clean ); $cells = array(); foreach ( $rows as $row ) { $key = addon_boss_combo_price_key( (array) ( $row['picks'] ?? array() ), $clean ); if ( '' === $key ) { continue; } // Only the figures the merchant actually set. An empty weight means // "use the product's", not "this parcel weighs nothing". $cell = array(); foreach ( array( 'weight', 'length', 'width', 'height' ) as $field ) { $val = isset( $row[ $field ] ) ? (string) $row[ $field ] : ''; if ( '' !== $val ) { $cell[ $field ] = $val; } } if ( $cell ) { $cells[ $key ] = $cell; } } if ( ! $cells ) { $product->delete_meta_data( ADDON_BOSS_COMBO_SHIP_META ); $product->save(); return; } $product->update_meta_data( ADDON_BOSS_COMBO_SHIP_META, array( 'sets' => $clean, 'cells' => $cells ) ); $product->save(); } /** * The stored map, or an empty array. * * @return array{sets:string[],cells:array>}|array{} */ function addon_boss_combo_ship( $product ): array { if ( ! $product instanceof WC_Product ) { $product = function_exists( 'wc_get_product' ) ? wc_get_product( (int) $product ) : null; } if ( ! $product instanceof WC_Product ) { return array(); } $map = $product->get_meta( ADDON_BOSS_COMBO_SHIP_META ); if ( ! is_array( $map ) || empty( $map['sets'] ) || empty( $map['cells'] ) ) { return array(); } return array( 'sets' => (array) $map['sets'], 'cells' => (array) $map['cells'] ); } /** * The figures for one pair, or an empty array when the map does not hold it. * A pair nobody entered figures for keeps the product's own, which is the * right answer rather than a guess. */ function addon_boss_combo_ship_for( $product, array $picks ): array { $map = addon_boss_combo_ship( $product ); if ( ! $map || ! function_exists( 'addon_boss_combo_price_key' ) ) { return array(); } $key = addon_boss_combo_price_key( $picks, $map['sets'] ); return ( '' !== $key && isset( $map['cells'][ $key ] ) ) ? (array) $map['cells'][ $key ] : array(); } /** * Put the pair's parcel on the cart line. * * WooCommerce builds a shipping package from each cart item's own product * object, so setting the figures there is what actually reaches a courier's * rates. Runs after the price pass, on the same hook, because both are * rewriting the same cloned product. * * @param WC_Cart $cart The cart being calculated. */ function addon_boss_ship_standalone_pairs( $cart ): void { if ( ( is_admin() && ! defined( 'DOING_AJAX' ) ) || ! $cart instanceof WC_Cart ) { return; } if ( ! defined( 'ADDON_BOSS_STANDALONE_OPTIONS_KEY' ) ) { return; } foreach ( $cart->get_cart() as $item ) { if ( ! isset( $item['data'] ) || ! $item['data'] instanceof WC_Product ) { continue; } $snaps = (array) ( $item[ ADDON_BOSS_STANDALONE_OPTIONS_KEY ] ?? array() ); if ( ! $snaps ) { continue; } $picks = array(); foreach ( $snaps as $snap ) { $sid = (string) ( $snap['set'] ?? '' ); $key = (string) ( $snap['key'] ?? '' ); if ( '' !== $sid && '' !== $key ) { $picks[ $sid ] = $key; } } if ( ! $picks ) { continue; } $cell = addon_boss_combo_ship_for( $item['data'], $picks ); if ( ! $cell ) { continue; } if ( isset( $cell['weight'] ) ) { $item['data']->set_weight( $cell['weight'] ); } if ( isset( $cell['length'] ) ) { $item['data']->set_length( $cell['length'] ); } if ( isset( $cell['width'] ) ) { $item['data']->set_width( $cell['width'] ); } if ( isset( $cell['height'] ) ) { $item['data']->set_height( $cell['height'] ); } } } add_action( 'woocommerce_before_calculate_totals', 'addon_boss_ship_standalone_pairs', 25 );