WPC Product Bundles for WooCommerce
Phone Orders correctly handles WPC Product Bundles by skipping child items, marking them as child cart items, and keeping the bundle price read‑only. This article provides a complete code snippet that ensures bundled products behave correctly when an agent adds them to a manual order.
The Complete Code Snippet
Copy and paste the following code into your child theme’s functions.php file or via the Code Snippets plugin.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
add_filter( 'wpo_skip_add_to_cart_item', function ( $skip, $item ) { return isset($item['woosb_parent_id']) ? true : $skip; }, 10, 2 ); add_filter( 'wpo_is_child_cart_item', function ( $child, $item ) { return isset($item['woosb_parent_id']) ? true : $child; }, 10, 2 ); add_filter( 'wpo_search_product_types', function ( $types ) { $types[] = "woosb"; return $types; } ); add_filter('wpo_add_configured_products_skip_item', function ($skip, $item_data) { return isset($item_data['woosb_parent_key']) ? true : $skip; }, 10, 2); add_filter('wpo_children_cart_item', function ($children, $item) { return isset($item['woosb_keys']) ? $item['woosb_keys'] : $children; }, 10, 2); add_filter('wpo_cart_item_is_price_readonly', function ($is_readonly, $item) { return isset($item['woosb_keys']) ? true : $is_readonly; }, 10, 2); add_filter('wpo_load_order_skip_item', function ($skip, $order_item, $order) { return $order_item->get_meta('_woosb_parent_id') ? true : $skip; }, 10, 3); if (!function_exists( 'wpo_woosb_build_ids_str' )) { function wpo_woosb_build_ids_str( $bundle ) { if ( ! is_object( $bundle ) || ! $bundle->is_type( 'woosb' ) ) { return ''; } $ids = array(); foreach ( (array) $bundle->get_items() as $key => $item ) { $child = wc_get_product( $item['id'] ); if ( ! $child ) { continue; } $id = $item['id']; $attrs = isset( $item['attrs'] ) ? (array) $item['attrs'] : array(); if ( $child->is_type( 'variable' ) ) { $variation_id = 0; $defaults = $child->get_default_attributes(); if ($defaults) { $match = array(); foreach ( $defaults as $attr_name => $attr_value ) { $match[ 'attribute_' . $attr_name ] = $attr_value; } $variation_id = (int) WC_Data_Store::load( 'product' )->find_matching_product_variation( $child, $match ); } if ( !$variation_id ) { foreach ( $child->get_children() as $child_variation_id ) { $variation = wc_get_product( $child_variation_id ); if ( $variation && $variation->is_purchasable() && $variation->is_in_stock() ) { $variation_id = $child_variation_id; break; } } } if ( !$variation_id ) { continue; } $variation = wc_get_product( $variation_id ); $id = $variation_id; $attrs = $variation->get_variation_attributes(); foreach ( $attrs as $attr_key => $attr_value ) { if ( '' !== $attr_value ) { continue; } $taxonomy = str_replace( 'attribute_', '', $attr_key ); $attributes = $child->get_attributes(); if ( empty( $attributes[ $taxonomy ] ) ) { continue; } $options = $attributes[ $taxonomy ]->is_taxonomy() ? wp_list_pluck( wc_get_product_terms( $child->get_id(), $taxonomy ), 'slug' ) : $attributes[ $taxonomy ]->get_options(); if ( !empty( $options ) ) { $attrs[ $attr_key ] = reset( $options ); } } } $ids[] = $id . '/' . $key . '/' . $item['qty'] . '/' . ( $attrs ? rawurlencode( wp_json_encode( $attrs ) ) : '' ); } return implode( ',', $ids ); } } add_filter( 'wpo_get_item_by_product', function ( $loaded_product, $item_data, $product ) { if ( !empty( $item_data['woosb_ids'] ) ) { $loaded_product['woosb_ids'] = $item_data['woosb_ids']; } return $loaded_product; }, 10, 3 ); add_filter( 'wpo_load_order_loaded_product', function ( $loaded_product, $order_item, $order, $is_edit ) { if ( is_callable( array( $order_item, 'get_meta' ) ) && ( $ids = $order_item->get_meta( '_woosb_ids' ) ) ) { $loaded_product['woosb_ids'] = $ids; } return $loaded_product; }, 10, 4 ); add_filter( 'wpo_update_cart_loaded_product', function ( $loaded_product, $item ) { if ( !empty( $item['woosb_ids'] ) ) { $loaded_product['woosb_ids'] = $item['woosb_ids']; } return $loaded_product; }, 10, 2 ); add_filter( 'wpo_update_cart_cart_item_meta', function ( $cart_item_meta, $item, $items ) { $product = wc_get_product( $item['product_id'] ); if (!$product || !$product->is_type( 'woosb' ) ) { return $cart_item_meta; } $ids = !empty( $item['woosb_ids'] ) ? $item['woosb_ids'] : wpo_woosb_build_ids_str( $product ); if ($ids) { $cart_item_meta['woosb_ids'] = $ids; } return $cart_item_meta; }, 10, 3 ); $wpo_woosb_restore_child_price = function ($cart) { foreach ( $cart->get_cart() as $cart_item ) { if (empty( $cart_item['woosb_parent_key']) || empty($cart_item['data']) ) { continue; } $price = $cart_item['data']->get_price('edit'); if ($price !== null && $price !== '') { continue; } $qty = ! empty($cart_item['quantity']) ? (float) $cart_item['quantity'] : 1; $subtotal = isset($cart_item['line_subtotal']) ? (float) $cart_item['line_subtotal'] : 0; if ( wc_prices_include_tax() && isset( $cart_item['line_subtotal_tax'] ) ) { $subtotal += (float) $cart_item['line_subtotal_tax']; } $cart_item['data']->set_price( $qty ? $subtotal / $qty : 0 ); } }; add_action('wpo_apply_fees_from_wc_cart', $wpo_woosb_restore_child_price ); add_action('woocommerce_before_calculate_totals', $wpo_woosb_restore_child_price, 1 ); |
How the Code Works
| Filter | Purpose |
|---|---|
wpo_skip_add_to_cart_item | Returns true for any item that has a woosb_parent_id key (i.e., a child product of a bundle). The plugin skips adding child items directly – only the parent bundle is added. |
wpo_is_child_cart_item | Marks any item with woosb_parent_id as a child cart item. The plugin later groups child items under the parent bundle. |
wpo_search_product_types | Adds the product type woosb to the product search, so agents can find bundle products in the phone order interface. |
wpo_add_configured_products_skip_item | Skips adding child items when the plugin auto‑configures bundled products. |
wpo_children_cart_item | Returns the array of woosb_keys (child item IDs) for a parent bundle item. The plugin uses this to group the child items under the parent. |
wpo_cart_item_is_price_readonly | Makes the price read‑only for any cart item that has woosb_keys (i.e., the parent bundle). |
wpo_load_order_skip_item | Skips loading child items when the plugin loads an existing order from the database. |
The bundle item itself is the only item added directly; the plugin automatically pulls in the child products from the WPC configuration.
Why You Need This Code
Without these filters, adding a WPC Product Bundle to a phone order produces incorrect results. Child items may appear twice, the total price may be wrong, or the bundle may appear as a single product without its components. The code ensures the bundle displays correctly with its child items listed underneath it, and the price field remains read‑only to preserve the bundle’s calculated total.
Step‑by‑Step Implementation
- Copy the entire code snippet.
- Install and activate the free Code Snippets plugin.
- Go to Snippets → Add New.
- Give the snippet a title, for example “Phone Orders – WPC Product Bundles”.
- Paste the code into the Code text area.
- Set the “Run snippet” option to “Run everywhere”.
- Click Save Changes and Activate.
Testing the Integration
- Create a bundle product using the WPC Product Bundles plugin (at least two child products).
- Add the bundle to a phone order.
- Verify that the bundle appears in the cart with all child items listed underneath it.
- Complete the order.
- Check the order details in WooCommerce → Orders. The bundle should display correctly with its child items, and the total price should match the bundle’s configuration.