Changeset 3494875
- Timestamp:
- 03/30/2026 06:55:58 PM (7 months ago)
- Location:
- nc-custom-product-bundles-for-woocommerce/trunk
- Files:
-
- 9 edited
-
assets/front.css (modified) (1 diff)
-
assets/front.js (modified) (2 diffs)
-
includes/class-fpbfw-admin.php (modified) (1 diff)
-
includes/class-fpbfw-product-type-bundle.php (modified) (2 diffs)
-
includes/class-fpbfw-templates.php (modified) (6 diffs)
-
languages/nc-custom-product-bundles-for-woocommerce-en_US.po (modified) (1 diff)
-
languages/nc-custom-product-bundles-for-woocommerce.pot (modified) (1 diff)
-
nc-custom-product-bundles-for-woocommerce.php (modified) (2 diffs)
-
readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
nc-custom-product-bundles-for-woocommerce/trunk/assets/front.css
r3490389 r3494875 92 92 font-weight: 600; 93 93 color: #213047; 94 } 95 96 .fpbfw-component-badge { 97 display: inline-flex; 98 width: fit-content; 99 padding: 2px 8px; 100 border-radius: 999px; 101 font-size: 0.75rem; 102 font-weight: 700; 103 color: #0f4c81; 104 background: #e7f2fb; 105 } 106 107 .fpbfw-component-variation-wrap { 108 display: block; 109 } 110 111 .fpbfw-variation-select { 112 width: 100%; 113 max-width: 420px; 114 min-height: 40px; 115 padding: 8px 10px; 116 border: 1px solid #d2d8e0; 117 border-radius: 10px; 118 background: #fff; 119 color: #26374f; 94 120 } 95 121 -
nc-custom-product-bundles-for-woocommerce/trunk/assets/front.js
r3490389 r3494875 46 46 47 47 return parsePriceFloat($card.find('.fpbfw-component-price').text()); 48 } 49 50 function syncVariationCard($card){ 51 var $select = $card.find('.fpbfw-variation-select'); 52 var $input = $card.find('input[name="FPBFW_components_selected[]"]').first(); 53 54 if (!$select.length || !$input.length) { 55 return; 56 } 57 58 var $option = $select.find('option:selected'); 59 var variationId = $option.val() || ''; 60 var price = parsePriceFloat($option.data('price') || 0); 61 62 if (variationId) { 63 $input.val(variationId); 64 } 65 66 $input.prop('checked', true); 67 68 $card.attr('data-price', price); 69 $card.data('price', price); 70 $card.find('.fpbfw-component-price').html('<span class="woocommerce-Price-amount amount">' + formatPrice(price) + '</span>'); 48 71 } 49 72 … … 161 184 } 162 185 163 $(document).on('change', 'input[name="FPBFW_components_selected[]"]', recalc); 164 $(document).ready(recalc); 186 $(document) 187 .on('change', 'input[name="FPBFW_components_selected[]"]', recalc) 188 .on('change', '.fpbfw-variation-select', function(e){ 189 syncVariationCard($(this).closest('.fpbfw-component-card')); 190 recalc(); 191 }) 192 .on('click', '.fpbfw-component-card', function(e){ 193 var $target = $(e.target); 194 if ($target.closest('a, button, select, option, input').length) { 195 return; 196 } 197 198 var $input = $(this).find('input[name="FPBFW_components_selected[]"]').first(); 199 if (!$input.length) { 200 return; 201 } 202 203 $input.prop('checked', !$input.prop('checked')).trigger('change'); 204 }) 205 .on('click mousedown', '.fpbfw-variation-select, .fpbfw-component-link', function(e){ 206 e.stopPropagation(); 207 }); 208 209 $(document).ready(function(){ 210 $('.fpbfw-component-card--variable').each(function(){ 211 syncVariationCard($(this)); 212 }); 213 recalc(); 214 }); 165 215 })(jQuery); -
nc-custom-product-bundles-for-woocommerce/trunk/includes/class-fpbfw-admin.php
r3491273 r3494875 660 660 661 661 echo '</select>'; 662 echo '<span class="description fpbfw-help">' . esc_html__('Selecciona los productos que el cliente podrá incluir en el bundle.', 'nc-custom-product-bundles-for-woocommerce') . '</span>';662 echo '<span class="description fpbfw-help">' . esc_html__('Selecciona productos simples, variables o variaciones concretas. Para bundles fijos, lo recomendable es usar variaciones concretas.', 'nc-custom-product-bundles-for-woocommerce') . '</span>'; 663 663 echo '</p>'; 664 664 -
nc-custom-product-bundles-for-woocommerce/trunk/includes/class-fpbfw-product-type-bundle.php
r3490389 r3494875 104 104 } 105 105 106 protected static function get_allowed_component_ids($product_id )106 protected static function get_allowed_component_ids($product_id, $expand_variable_children = false) 107 107 { 108 108 $allowed = (array) get_post_meta($product_id, '_FPBFW_components', true); 109 return array_values(array_unique(array_filter(array_map('intval', $allowed)))); 109 $allowed = array_values(array_unique(array_filter(array_map('intval', $allowed)))); 110 111 if (!$expand_variable_children) { 112 return $allowed; 113 } 114 115 $expanded = []; 116 117 foreach ($allowed as $component_id) { 118 $expanded[] = $component_id; 119 120 $component = wc_get_product($component_id); 121 if (!$component || !$component->is_type('variable')) { 122 continue; 123 } 124 125 foreach ((array) $component->get_children() as $variation_id) { 126 $variation_id = (int) $variation_id; 127 if ($variation_id > 0) { 128 $expanded[] = $variation_id; 129 } 130 } 131 } 132 133 return array_values(array_unique(array_filter(array_map('intval', $expanded)))); 110 134 } 111 135 … … 114 138 $selected = isset($_REQUEST['FPBFW_components_selected']) ? (array) wp_unslash($_REQUEST['FPBFW_components_selected']) : []; 115 139 $selected = array_values(array_unique(array_filter(array_map('intval', $selected)))); 116 $allowed = self::get_allowed_component_ids($product_id );140 $allowed = self::get_allowed_component_ids($product_id, true); 117 141 118 142 if (empty($selected) || empty($allowed)) { -
nc-custom-product-bundles-for-woocommerce/trunk/includes/class-fpbfw-templates.php
r3491221 r3494875 52 52 } 53 53 54 protected static function get_components_for( $product_id ) { 55 $ids = (array) get_post_meta( $product_id, '_FPBFW_components', true ); 56 return array_filter( array_map( 'wc_get_product', $ids ) ); 54 protected static function get_components_for( $product_id, $expand_variable_children = false ) { 55 $ids = (array) get_post_meta( $product_id, '_FPBFW_components', true ); 56 $ids = array_values( array_unique( array_filter( array_map( 'intval', $ids ) ) ) ); 57 $products = []; 58 $seen_ids = []; 59 60 foreach ( $ids as $component_id ) { 61 $product = wc_get_product( $component_id ); 62 if ( ! $product ) { 63 continue; 64 } 65 66 if ( $expand_variable_children && $product->is_type( 'variable' ) ) { 67 foreach ( (array) $product->get_children() as $variation_id ) { 68 $variation_id = (int) $variation_id; 69 if ( isset( $seen_ids[ $variation_id ] ) ) { 70 continue; 71 } 72 73 $variation = wc_get_product( $variation_id ); 74 if ( ! $variation || ! $variation->exists() ) { 75 continue; 76 } 77 78 $seen_ids[ $variation_id ] = true; 79 $products[] = $variation; 80 } 81 82 continue; 83 } 84 85 if ( isset( $seen_ids[ $component_id ] ) ) { 86 continue; 87 } 88 89 $seen_ids[ $component_id ] = true; 90 $products[] = $product; 91 } 92 93 return $products; 57 94 } 58 95 … … 92 129 93 130 return 0.0; 131 } 132 133 protected static function get_component_price_bounds( $product ) { 134 if ( ! $product instanceof WC_Product ) { 135 return [ 0.0, 0.0 ]; 136 } 137 138 if ( $product->is_type( 'variable' ) ) { 139 $min = (float) $product->get_variation_price( 'min', true ); 140 $max = (float) $product->get_variation_price( 'max', true ); 141 142 if ( $min > 0 || $max > 0 ) { 143 return [ max( 0, $min ), max( 0, $max ) ]; 144 } 145 } 146 147 $amount = self::get_component_price_amount( $product ); 148 return [ $amount, $amount ]; 149 } 150 151 protected static function get_variable_component_options( $product ) { 152 if ( ! $product instanceof WC_Product || ! $product->is_type( 'variable' ) ) { 153 return []; 154 } 155 156 $options = []; 157 158 foreach ( (array) $product->get_children() as $variation_id ) { 159 $variation = wc_get_product( $variation_id ); 160 if ( ! $variation || ! $variation->exists() ) { 161 continue; 162 } 163 164 if ( ! $variation->is_purchasable() ) { 165 continue; 166 } 167 168 $label = wp_strip_all_tags( wc_get_formatted_variation( $variation, true, false, false ) ); 169 if ( '' === $label ) { 170 $label = $variation->get_name(); 171 } 172 173 $price = self::get_component_price_amount( $variation ); 174 175 $options[] = [ 176 'id' => (int) $variation_id, 177 'label' => $label, 178 'price' => $price, 179 ]; 180 } 181 182 return $options; 94 183 } 95 184 … … 145 234 146 235 protected static function calculate_optional_price_range( $components, $pricing_mode, $min_select, $rules ) { 147 $prices = []; 236 $min_prices = []; 237 $max_prices = []; 148 238 149 239 foreach ( (array) $components as $component ) { 150 $amount = self::get_component_price_amount( $component ); 151 if ( $amount > 0 ) { 152 $prices[] = $amount; 153 } 154 } 155 156 if ( empty( $prices ) ) { 240 list( $min_amount, $max_amount ) = self::get_component_price_bounds( $component ); 241 242 if ( $min_amount > 0 ) { 243 $min_prices[] = $min_amount; 244 } 245 246 if ( $max_amount > 0 ) { 247 $max_prices[] = $max_amount; 248 } 249 } 250 251 if ( empty( $min_prices ) || empty( $max_prices ) ) { 157 252 return [ 0.0, 0.0 ]; 158 253 } 159 254 160 sort( $prices, SORT_NUMERIC ); 161 162 $min_count = max( 1, min( (int) $min_select, count( $prices ) ) ); 163 $max_count = count( $prices ); 164 165 $min_total = array_sum( array_slice( $prices, 0, $min_count ) ); 166 $max_total = array_sum( $prices ); 255 sort( $min_prices, SORT_NUMERIC ); 256 sort( $max_prices, SORT_NUMERIC ); 257 258 $min_count = max( 1, min( (int) $min_select, count( $min_prices ) ) ); 259 $max_count = count( $max_prices ); 260 261 $min_total = array_sum( array_slice( $min_prices, 0, $min_count ) ); 262 $max_total = array_sum( array_slice( $max_prices, 0, $max_count ) ); 167 263 168 264 if ( 'rules' === $pricing_mode ) { … … 188 284 } 189 285 190 $components = self::get_components_for( $product->get_id() );286 $components = self::get_components_for( $product->get_id(), false ); 191 287 if ( empty( $components ) ) { 192 288 return; … … 216 312 foreach ( $components as $comp ) { 217 313 $component_id = $comp->get_id(); 218 $component_link = get_permalink( $component_id ); 314 $is_variable = $comp->is_type( 'variable' ); 315 $is_variation = $comp->is_type( 'variation' ); 316 $variation_options = $is_variable ? self::get_variable_component_options( $comp ) : []; 317 $selected_variation = $is_variable && ! empty( $variation_options ) ? $variation_options[0] : null; 318 $component_link = $is_variation ? get_permalink( $comp->get_parent_id() ) : get_permalink( $component_id ); 219 319 $component_name = $comp->get_name(); 220 $component_price_ html = self::get_component_price_html( $comp );221 $component_price_ raw = self::get_component_price_amount( $comp );320 $component_price_raw = $selected_variation ? (float) $selected_variation['price'] : self::get_component_price_amount( $comp ); 321 $component_price_html = $component_price_raw > 0 ? self::format_plain_price_html( $component_price_raw ) : ''; 222 322 $component_image = $comp->get_image( 'woocommerce_thumbnail', [ 'loading' => 'lazy' ] ); 323 $component_badge = $is_variable ? '<span class="fpbfw-component-badge">' . esc_html__( 'Variable', 'nc-custom-product-bundles-for-woocommerce' ) . '</span>' : ( $is_variation ? '<span class="fpbfw-component-badge">' . esc_html__( 'Variación', 'nc-custom-product-bundles-for-woocommerce' ) . '</span>' : '' ); 324 $selected_value = $selected_variation ? (int) $selected_variation['id'] : (int) $component_id; 325 $variation_select = ''; 223 326 $input_id = 'fpbfw_component_' . $component_id; 224 327 328 if ( $is_variable && ! empty( $variation_options ) ) { 329 $variation_select = '<span class="fpbfw-component-variation-wrap"><select class="fpbfw-variation-select" aria-label="' . esc_attr__( 'Elige una variación', 'nc-custom-product-bundles-for-woocommerce' ) . '">'; 330 331 foreach ( $variation_options as $index => $variation_option ) { 332 $variation_select .= sprintf( 333 '<option value="%1$d" data-price="%2$s"%3$s>%4$s</option>', 334 (int) $variation_option['id'], 335 esc_attr( wc_format_decimal( $variation_option['price'] ) ), 336 selected( 0, $index, false ), 337 esc_html( $variation_option['label'] ) 338 ); 339 } 340 341 $variation_select .= '</select></span>'; 342 } 343 225 344 printf( 226 '< label class="fpbfw-component-card" for="%1$s" data-price="%2$s">345 '<div class="fpbfw-component-card%10$s" data-price="%2$s"> 227 346 <span class="fpbfw-component-check"> 228 <input id="%1$s" type="checkbox" name="FPBFW_components_selected[]" value="%3$d" />347 <input id="%1$s" type="checkbox" name="FPBFW_components_selected[]" value="%3$d" aria-label="%5$s" /> 229 348 </span> 230 349 <span class="fpbfw-component-media">%4$s</span> 231 350 <span class="fpbfw-component-body"> 232 351 <span class="fpbfw-component-title">%5$s</span> 233 <span class="fpbfw-component-price">%6$s</span> 352 %6$s 353 %7$s 234 354 <span class="fpbfw-component-actions"> 235 <a class="fpbfw-component-link" href="% 7$s" target="_blank" rel="noopener">%8$s</a>355 <a class="fpbfw-component-link" href="%8$s" target="_blank" rel="noopener">%9$s</a> 236 356 </span> 237 357 </span> 238 </ label>',358 </div>', 239 359 esc_attr( $input_id ), 240 360 esc_attr( wc_format_decimal( $component_price_raw ) ), 241 (int) $component_id,361 $selected_value, 242 362 $component_image, 243 363 esc_html( $component_name ), 244 $component_price_html ? wp_kses_post( $component_price_html ) : '', 364 $component_badge, 365 ( $variation_select ? $variation_select : '' ) . '<span class="fpbfw-component-price">' . ( $component_price_html ? wp_kses_post( $component_price_html ) : '' ) . '</span>', 245 366 esc_url( $component_link ), 246 esc_html__( 'Ver producto', 'nc-custom-product-bundles-for-woocommerce' ) 367 esc_html__( 'Ver producto', 'nc-custom-product-bundles-for-woocommerce' ), 368 $is_variable ? ' fpbfw-component-card--variable' : '' 247 369 ); 248 370 } … … 277 399 foreach ( $components as $comp ) { 278 400 $component_id = $comp->get_id(); 279 $link = get_permalink( $component_id );401 $link = $comp->is_type( 'variation' ) ? get_permalink( $comp->get_parent_id() ) : get_permalink( $component_id ); 280 402 $image = $comp->get_image( 'woocommerce_thumbnail', [ 'loading' => 'lazy' ] ); 281 403 $name = esc_html( $comp->get_name() ); -
nc-custom-product-bundles-for-woocommerce/trunk/languages/nc-custom-product-bundles-for-woocommerce-en_US.po
r3491273 r3494875 1 1 msgid "" 2 2 msgstr "" 3 "Project-Id-Version: NC Custom Product Bundles for WooCommerce 1.1. 4\n"3 "Project-Id-Version: NC Custom Product Bundles for WooCommerce 1.1.5\n" 4 4 "POT-Creation-Date: 2026-03-24 00:00+0000\n" 5 5 "PO-Revision-Date: 2026-03-24 00:00+0000\n" -
nc-custom-product-bundles-for-woocommerce/trunk/languages/nc-custom-product-bundles-for-woocommerce.pot
r3491273 r3494875 1 1 msgid "" 2 2 msgstr "" 3 "Project-Id-Version: NC Custom Product Bundles for WooCommerce 1.1. 4\n"3 "Project-Id-Version: NC Custom Product Bundles for WooCommerce 1.1.5\n" 4 4 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/nc-custom-product-bundles-for-woocommerce\n" 5 5 "POT-Creation-Date: 2026-03-24 00:00+0000\n" -
nc-custom-product-bundles-for-woocommerce/trunk/nc-custom-product-bundles-for-woocommerce.php
r3491273 r3494875 3 3 * Plugin Name: NC Custom Product Bundles for WooCommerce 4 4 * Description: Create WooCommerce bundles with fixed or optional components, dynamic pricing, quantity discount rules, visual front-end selection, CSV import/export, and translation-ready support. 5 * Version: 1.1. 45 * Version: 1.1.5 6 6 * Author: Nayden4o 7 7 * License: GPL-2.0-or-later … … 20 20 } 21 21 22 define( 'FPBFW_VERSION', '1.1. 4' );22 define( 'FPBFW_VERSION', '1.1.5' ); 23 23 define( 'FPBFW_PLUGIN_FILE', __FILE__ ); 24 24 define( 'FPBFW_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); -
nc-custom-product-bundles-for-woocommerce/trunk/readme.txt
r3491273 r3494875 6 6 Requires PHP: 8.1 7 7 Requires Plugins: woocommerce 8 Stable tag: 1.1. 48 Stable tag: 1.1.5 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 69 69 [Kit Tuning V.3 Volkswagen Golf R Hatchback Mk8 Facelift](https://taller-flash.es/producto/kit-tuning-v-3-volkswagen-golf-r-hatchback-mk8-facelift) 70 70 71 == Video Demo == 72 73 [Watch the plugin video demo on YouTube](https://www.youtube.com/watch?v=8UuIKgSbdNs) 74 71 75 == CSV Import / Export == 72 76 … … 129 133 == Changelog == 130 134 135 = 1.1.5 = 136 - Added front-end variation selection for variable products inside optional bundles. 137 - Improved optional bundle price recalculation when changing bundle variation selections. 138 - Clarified admin guidance for using concrete variations in fixed bundles. 139 131 140 = 1.1.4 = 132 141 - Fixed Bundle admin interactions and restored quantity-rule row controls.
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)