Changeset 3754062
- Timestamp:
- 10/19/2026 05:50:19 AM (3 weeks ago)
- Location:
- nanflows-bundle
- Files:
-
- 14 edited
- 1 copied
-
tags/0.1.3 (copied) (copied from nanflows-bundle/trunk )
-
tags/0.1.3/assets/admin/app.css (modified) (1 diff)
-
tags/0.1.3/assets/admin/app.js (modified) (5 diffs)
-
tags/0.1.3/assets/frontend/bundle.js (modified) (2 diffs)
-
tags/0.1.3/includes/Admin/Admin.php (modified) (2 diffs)
-
tags/0.1.3/includes/Frontend/BundleCard.php (modified) (2 diffs)
-
tags/0.1.3/nanflows-bundle.php (modified) (2 diffs)
-
tags/0.1.3/readme.txt (modified) (2 diffs)
-
trunk/assets/admin/app.css (modified) (1 diff)
-
trunk/assets/admin/app.js (modified) (5 diffs)
-
trunk/assets/frontend/bundle.js (modified) (2 diffs)
-
trunk/includes/Admin/Admin.php (modified) (2 diffs)
-
trunk/includes/Frontend/BundleCard.php (modified) (2 diffs)
-
trunk/nanflows-bundle.php (modified) (2 diffs)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
nanflows-bundle/tags/0.1.3/assets/admin/app.css
r3737861 r3754062 1286 1286 } 1287 1287 1288 /* Volume and Quantity Break (Pro): "Add 1 more and get 10% off". */ 1289 .nfb-live__nudge { 1290 margin: 0 0 10px; 1291 padding: 6px 10px; 1292 border-radius: 6px; 1293 background: #edfaef; 1294 color: #00631a; 1295 font-weight: 600; 1296 } 1297 1288 1298 .nfb-live__save { 1289 1299 display: inline-flex; -
nanflows-bundle/tags/0.1.3/assets/admin/app.js
r3737861 r3754062 447 447 'div', 448 448 { key: i, className: 'nfb-options__row' }, 449 ( field.columns || [] ).map( ( col ) => 450 h( TextControl, { 449 ( field.columns || [] ).map( function ( col ) { 450 const cell = function ( key ) { 451 if ( row[ key ] !== undefined && row[ key ] !== '' ) { 452 return row[ key ]; 453 } 454 const other = ( field.columns || [] ).find( ( c ) => c.key === key ); 455 return other && other.default !== undefined ? other.default : ''; 456 }; 457 // show_if: [ key, value ]: only while this row's other column has that value. 458 if ( col.show_if && String( cell( col.show_if[ 0 ] ) ) !== String( col.show_if[ 1 ] ) ) { 459 return null; 460 } 461 const set = ( v ) => change( field.key, rows.map( ( r, j ) => ( j === i ? Object.assign( {}, r, { [ col.key ]: col.type === 'number' ? ( v === '' ? '' : Number( v ) ) : v } ) : r ) ) ); 462 if ( col.type === 'select' ) { 463 return h( SelectControl, { key: col.key, label: col.label, value: String( cell( col.key ) ), options: ( col.options || [] ).map( ( o ) => ( { value: o[ 0 ], label: o[ 1 ] } ) ), onChange: set, __nextHasNoMarginBottom: true } ); 464 } 465 return h( TextControl, { 451 466 key: col.key, 452 467 label: col.label, … … 454 469 min: col.min, 455 470 max: col.max, 471 step: col.step, 456 472 value: row[ col.key ] === undefined ? '' : String( row[ col.key ] ), 457 onChange: ( v ) => change( field.key, rows.map( ( r, j ) => ( j === i ? Object.assign( {}, r, { [ col.key ]: col.type === 'number' ? ( v === '' ? '' : Number( v ) ) : v } ) : r ) ) ),473 onChange: set, 458 474 __nextHasNoMarginBottom: true, 459 } ) 460 ),475 } ); 476 } ), 461 477 h( Button, { icon: 'no-alt', label: __( 'Remove', 'nanflows-bundle' ), isDestructive: true, onClick: () => change( field.key, rows.filter( ( r, j ) => j !== i ) ) } ) 462 478 ) … … 2479 2495 } 2480 2496 2497 /** 2498 * Volume and Quantity Break (Pro): the "Add 1 more and get 10% off" line, 2499 * for the smallest quantity the preview shows. Empty when switched off. 2500 */ 2501 function tierNudge( d ) { 2502 const o = d.options || {}; 2503 if ( ( d.type !== 'volume' && d.type !== 'quantity_break' ) || o.nudge === false ) { 2504 return ''; 2505 } 2506 const tiers = ( o.tiers || [] ).filter( ( t ) => Number( t.min ) > 0 ).slice().sort( ( a, b ) => a.min - b.min ); 2507 if ( ! tiers.length ) { 2508 return ''; 2509 } 2510 const units = d.type === 'quantity_break' ? Number( tiers[ 0 ].min ) : Math.max( 1, Number( o.min_items ) || 1 ); 2511 const next = tiers.find( ( t ) => Number( t.min ) > units ); 2512 const shown = next || tiers[ tiers.length - 1 ]; 2513 const off = shown.off === 'amount' ? money( toMinor( shown.amount ) ) : Number( shown.percent || 0 ) + '%'; 2514 if ( ! next ) { 2515 /* translators: %s: discount, e.g. "20%" or "$10.00" */ 2516 return sprintf( __( 'You are getting the best discount: %s off', 'nanflows-bundle' ), off ); 2517 } 2518 return d.type === 'quantity_break' 2519 ? /* translators: 1: number of items, 2: discount, e.g. "10%" or "$5.00" */ sprintf( __( 'Buy %1$d and get %2$s off', 'nanflows-bundle' ), Number( next.min ), off ) 2520 : /* translators: 1: number of items, 2: discount, e.g. "10%" or "$5.00" */ sprintf( __( 'Add %1$d more and get %2$s off', 'nanflows-bundle' ), Number( next.min ) - units, off ); 2521 } 2522 2481 2523 /** The tiers of a quantity break, as the shopper picks them. */ 2482 2524 function QuantityTiers( props ) { … … 2492 2534 tiers.map( function ( tier, i ) { 2493 2535 const regular = product ? toMinor( product.price ) * tier.min : 0; 2494 const price = Math.round( regular * ( 1 - Number( tier.percent || 0 ) / 100 ) ); 2536 const flat = tier.off === 'amount'; 2537 const price = flat ? Math.max( 0, regular - toMinor( tier.amount ) ) : Math.round( regular * ( 1 - Number( tier.percent || 0 ) / 100 ) ); 2495 2538 return h( 2496 2539 'li', 2497 2540 { key: i, className: i === 0 ? 'is-picked' : '' }, 2498 2541 h( 'input', { type: 'radio', checked: i === 0, readOnly: true, tabIndex: -1, 'aria-hidden': true } ), 2499 h( 'span', { className: 'nfb-live__name' }, sprintf( /* translators: %d: quantity */ __( 'Buy %d', 'nanflows-bundle' ), tier.min ), h( 'small', { className: 'nfb-live__badge' }, sprintf( /* translators: %s: percentage */ __( 'Save %s%%', 'nanflows-bundle' ), tier.percent ) ) ),2542 h( 'span', { className: 'nfb-live__name' }, sprintf( /* translators: %d: quantity */ __( 'Buy %d', 'nanflows-bundle' ), tier.min ), h( 'small', { className: 'nfb-live__badge' }, flat ? sprintf( /* translators: %s: amount */ __( 'Save %s', 'nanflows-bundle' ), money( toMinor( tier.amount ) ) ) : sprintf( /* translators: %s: percentage */ __( 'Save %s%%', 'nanflows-bundle' ), tier.percent ) ) ), 2500 2543 h( 'span', null, product ? h( Fragment, null, h( 'del', null, money( regular ) ), ' ', h( 'strong', null, money( price ) ) ) : '—' ) 2501 2544 ); … … 2595 2638 h( 'dd', null, waiting ? h( 'span', { className: 'nfb-summary__wait' }, __( 'set in step 2', 'nanflows-bundle' ) ) : h( 'strong', { className: 'nfb-live__price' }, money( b.price ) ) ) 2596 2639 ), 2597 ! waiting && b.savings > 0 && rows.length > 0 && d.type !== 'quantity_break' && h( 'p', { className: 'nfb-live__save' }, h( Icon, { name: 'tag' } ), ' ', sprintf( /* translators: %s: amount */ __( 'You save %s', 'nanflows-bundle' ), money( b.savings ) ) ), 2640 rows.length > 0 && tierNudge( d ) && h( 'p', { className: 'nfb-live__nudge' }, tierNudge( d ) ), 2641 ! waiting && b.savings > 0 && rows.length > 0 && d.type !== 'quantity_break' && h( 'p', { className: 'nfb-live__save' }, h( Icon, { name: 'tag' } ), ' ', sprintf( /* translators: %s: amount */ __( 'You save %s', 'nanflows-bundle' ), money( b.savings ) ), b.regular > 0 && Math.round( ( b.savings / b.regular ) * 100 ) >= 1 ? ' (' + Math.round( ( b.savings / b.regular ) * 100 ) + '%)' : '' ), 2598 2642 b.note && rows.length > 0 && h( 'p', { className: 'nfb-live__note' }, b.note ), 2599 2643 ! props.embedded && h( 'div', { className: 'nfb-preview__buy' }, h( 'span', { className: 'nfb-qty' }, '− 1 +' ), h( 'span', { className: 'button button-primary' }, config.buttonText || __( 'Add Bundle to Cart', 'nanflows-bundle' ) ) ), -
nanflows-bundle/tags/0.1.3/assets/frontend/bundle.js
r3736789 r3754062 14 14 } 15 15 16 /** The "(25%)" after the amount saved: rounded, hidden under 1%. */ 17 function percentOff( card, b ) { 18 const box = card.querySelector( '.nfb-bundle__savings-percent' ); 19 const number = box && box.querySelector( '.nfb-bundle__savings-pct' ); 20 if ( ! number ) { 21 return; 22 } 23 const percent = b.regular > 0 ? Math.round( ( b.savings / b.regular ) * 100 ) : 0; 24 number.textContent = String( percent ); 25 box.hidden = percent < 1; 26 } 27 16 28 function update( card ) { 17 29 let currency; … … 52 64 set( '.nfb-bundle__price strong', b.price ); 53 65 set( card.querySelector( '.nfb-bundle__savings-amount' ) ? '.nfb-bundle__savings-amount' : '.nfb-bundle__savings', b.savings ); 66 percentOff( card, b ); 54 67 55 68 // "Frequently bought together" with every extra unticked: the main -
nanflows-bundle/tags/0.1.3/includes/Admin/Admin.php
r3736789 r3754062 72 72 /** 73 73 * "Bundles" in the plugin's row on the Plugins screen, before 74 * Deactivate, like other plugins' Settings links. 74 * Deactivate, like other plugins' Settings links. Without Pro installed, 75 * "Upgrade to Pro" comes first and opens the Pro page on nanflows.com. 75 76 * 76 77 * @param array<string, string> $links … … 81 82 return $links; 82 83 } 83 return array_merge(84 array(85 'bundles' => '<a href="' . esc_url( admin_url( 'admin.php?page=' . self::PAGE ) ) . '">' . esc_html__( 'Bundles', 'nanflows-bundle' ) . '</a>',86 ),87 (array) $links88 );84 $own = array(); 85 if ( ! defined( 'NANFLOWS_BUNDLE_PRO_VERSION' ) ) { 86 $own['upgrade'] = '<a href="' . esc_url( self::UPGRADE_URL ) . '" target="_blank" rel="noopener" style="color:#1a9f3a;font-weight:600">' . esc_html__( 'Upgrade to Pro', 'nanflows-bundle' ) . '</a>'; 87 } 88 $own['bundles'] = '<a href="' . esc_url( admin_url( 'admin.php?page=' . self::PAGE ) ) . '">' . esc_html__( 'Bundles', 'nanflows-bundle' ) . '</a>'; 89 return array_merge( $own, (array) $links ); 89 90 } 90 91 -
nanflows-bundle/tags/0.1.3/includes/Frontend/BundleCard.php
r3736789 r3754062 215 215 <?php if ( $settings['show_savings'] && $savings > 0 ) : ?> 216 216 <dt><?php esc_html_e( 'You save', 'nanflows-bundle' ); ?></dt> 217 <dd class="nfb-bundle__savings"><?php echo Icons::get( 'tag' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- fixed markup, escaped inside. ?><span class="nfb-bundle__savings-amount"><?php echo wp_kses_post( wc_price( $savings ) ); ?></span> </dd>217 <dd class="nfb-bundle__savings"><?php echo Icons::get( 'tag' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- fixed markup, escaped inside. ?><span class="nfb-bundle__savings-amount"><?php echo wp_kses_post( wc_price( $savings ) ); ?></span> <?php echo self::percent_off( $regular, $savings ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- a number in fixed markup. ?></dd> 218 218 <?php endif; ?> 219 219 </dl> … … 281 281 ); 282 282 } 283 284 /** 285 * "(25%)" after the amount saved, as the editor shows it. The script 286 * keeps the number current and hides it under 1%. 287 */ 288 public static function percent_off( float $regular, float $savings ): string { 289 $percent = $regular > 0 ? (int) round( $savings / $regular * 100 ) : 0; 290 return '<span class="nfb-bundle__savings-percent"' . ( $percent >= 1 ? '' : ' hidden' ) . '>(<span class="nfb-bundle__savings-pct">' . $percent . '</span>%)</span>'; 291 } 283 292 } -
nanflows-bundle/tags/0.1.3/nanflows-bundle.php
r3737861 r3754062 4 4 * Plugin URI: https://nanflows.com/products/nanflows-bundle 5 5 * Description: Sell product bundles in WooCommerce: fixed bundles and "frequently bought together" offers, with stock taken from the products inside. 6 * Version: 0.1. 26 * Version: 0.1.3 7 7 * Requires at least: 6.4 8 8 * Requires PHP: 7.4 … … 20 20 defined( 'ABSPATH' ) || exit; 21 21 22 define( 'NANFLOWS_BUNDLE_VERSION', '0.1. 2' );22 define( 'NANFLOWS_BUNDLE_VERSION', '0.1.3' ); 23 23 define( 'NANFLOWS_BUNDLE_FILE', __FILE__ ); 24 24 define( 'NANFLOWS_BUNDLE_DIR', plugin_dir_path( __FILE__ ) ); -
nanflows-bundle/tags/0.1.3/readme.txt
r3737861 r3754062 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.4 7 Stable tag: 0.1. 27 Stable tag: 0.1.3 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 171 171 == Changelog == 172 172 173 = 0.1.3 = 174 * The bundle box shows the saving as a percentage too, e.g. "You save $28.25 (25%)". 175 * "Upgrade to Pro" link on the Plugins screen. 176 * With Bundles Pro: volume and quantity tiers can take a percentage or a flat amount off, and the bundle box says how to reach the next discount ("Add 1 more and get 10% off"). 177 173 178 = 0.1.2 = 174 179 * A bundle can have a page of its own, with a heading or a banner (computers and phones) at the top, in your theme's design. -
nanflows-bundle/trunk/assets/admin/app.css
r3737861 r3754062 1286 1286 } 1287 1287 1288 /* Volume and Quantity Break (Pro): "Add 1 more and get 10% off". */ 1289 .nfb-live__nudge { 1290 margin: 0 0 10px; 1291 padding: 6px 10px; 1292 border-radius: 6px; 1293 background: #edfaef; 1294 color: #00631a; 1295 font-weight: 600; 1296 } 1297 1288 1298 .nfb-live__save { 1289 1299 display: inline-flex; -
nanflows-bundle/trunk/assets/admin/app.js
r3737861 r3754062 447 447 'div', 448 448 { key: i, className: 'nfb-options__row' }, 449 ( field.columns || [] ).map( ( col ) => 450 h( TextControl, { 449 ( field.columns || [] ).map( function ( col ) { 450 const cell = function ( key ) { 451 if ( row[ key ] !== undefined && row[ key ] !== '' ) { 452 return row[ key ]; 453 } 454 const other = ( field.columns || [] ).find( ( c ) => c.key === key ); 455 return other && other.default !== undefined ? other.default : ''; 456 }; 457 // show_if: [ key, value ]: only while this row's other column has that value. 458 if ( col.show_if && String( cell( col.show_if[ 0 ] ) ) !== String( col.show_if[ 1 ] ) ) { 459 return null; 460 } 461 const set = ( v ) => change( field.key, rows.map( ( r, j ) => ( j === i ? Object.assign( {}, r, { [ col.key ]: col.type === 'number' ? ( v === '' ? '' : Number( v ) ) : v } ) : r ) ) ); 462 if ( col.type === 'select' ) { 463 return h( SelectControl, { key: col.key, label: col.label, value: String( cell( col.key ) ), options: ( col.options || [] ).map( ( o ) => ( { value: o[ 0 ], label: o[ 1 ] } ) ), onChange: set, __nextHasNoMarginBottom: true } ); 464 } 465 return h( TextControl, { 451 466 key: col.key, 452 467 label: col.label, … … 454 469 min: col.min, 455 470 max: col.max, 471 step: col.step, 456 472 value: row[ col.key ] === undefined ? '' : String( row[ col.key ] ), 457 onChange: ( v ) => change( field.key, rows.map( ( r, j ) => ( j === i ? Object.assign( {}, r, { [ col.key ]: col.type === 'number' ? ( v === '' ? '' : Number( v ) ) : v } ) : r ) ) ),473 onChange: set, 458 474 __nextHasNoMarginBottom: true, 459 } ) 460 ),475 } ); 476 } ), 461 477 h( Button, { icon: 'no-alt', label: __( 'Remove', 'nanflows-bundle' ), isDestructive: true, onClick: () => change( field.key, rows.filter( ( r, j ) => j !== i ) ) } ) 462 478 ) … … 2479 2495 } 2480 2496 2497 /** 2498 * Volume and Quantity Break (Pro): the "Add 1 more and get 10% off" line, 2499 * for the smallest quantity the preview shows. Empty when switched off. 2500 */ 2501 function tierNudge( d ) { 2502 const o = d.options || {}; 2503 if ( ( d.type !== 'volume' && d.type !== 'quantity_break' ) || o.nudge === false ) { 2504 return ''; 2505 } 2506 const tiers = ( o.tiers || [] ).filter( ( t ) => Number( t.min ) > 0 ).slice().sort( ( a, b ) => a.min - b.min ); 2507 if ( ! tiers.length ) { 2508 return ''; 2509 } 2510 const units = d.type === 'quantity_break' ? Number( tiers[ 0 ].min ) : Math.max( 1, Number( o.min_items ) || 1 ); 2511 const next = tiers.find( ( t ) => Number( t.min ) > units ); 2512 const shown = next || tiers[ tiers.length - 1 ]; 2513 const off = shown.off === 'amount' ? money( toMinor( shown.amount ) ) : Number( shown.percent || 0 ) + '%'; 2514 if ( ! next ) { 2515 /* translators: %s: discount, e.g. "20%" or "$10.00" */ 2516 return sprintf( __( 'You are getting the best discount: %s off', 'nanflows-bundle' ), off ); 2517 } 2518 return d.type === 'quantity_break' 2519 ? /* translators: 1: number of items, 2: discount, e.g. "10%" or "$5.00" */ sprintf( __( 'Buy %1$d and get %2$s off', 'nanflows-bundle' ), Number( next.min ), off ) 2520 : /* translators: 1: number of items, 2: discount, e.g. "10%" or "$5.00" */ sprintf( __( 'Add %1$d more and get %2$s off', 'nanflows-bundle' ), Number( next.min ) - units, off ); 2521 } 2522 2481 2523 /** The tiers of a quantity break, as the shopper picks them. */ 2482 2524 function QuantityTiers( props ) { … … 2492 2534 tiers.map( function ( tier, i ) { 2493 2535 const regular = product ? toMinor( product.price ) * tier.min : 0; 2494 const price = Math.round( regular * ( 1 - Number( tier.percent || 0 ) / 100 ) ); 2536 const flat = tier.off === 'amount'; 2537 const price = flat ? Math.max( 0, regular - toMinor( tier.amount ) ) : Math.round( regular * ( 1 - Number( tier.percent || 0 ) / 100 ) ); 2495 2538 return h( 2496 2539 'li', 2497 2540 { key: i, className: i === 0 ? 'is-picked' : '' }, 2498 2541 h( 'input', { type: 'radio', checked: i === 0, readOnly: true, tabIndex: -1, 'aria-hidden': true } ), 2499 h( 'span', { className: 'nfb-live__name' }, sprintf( /* translators: %d: quantity */ __( 'Buy %d', 'nanflows-bundle' ), tier.min ), h( 'small', { className: 'nfb-live__badge' }, sprintf( /* translators: %s: percentage */ __( 'Save %s%%', 'nanflows-bundle' ), tier.percent ) ) ),2542 h( 'span', { className: 'nfb-live__name' }, sprintf( /* translators: %d: quantity */ __( 'Buy %d', 'nanflows-bundle' ), tier.min ), h( 'small', { className: 'nfb-live__badge' }, flat ? sprintf( /* translators: %s: amount */ __( 'Save %s', 'nanflows-bundle' ), money( toMinor( tier.amount ) ) ) : sprintf( /* translators: %s: percentage */ __( 'Save %s%%', 'nanflows-bundle' ), tier.percent ) ) ), 2500 2543 h( 'span', null, product ? h( Fragment, null, h( 'del', null, money( regular ) ), ' ', h( 'strong', null, money( price ) ) ) : '—' ) 2501 2544 ); … … 2595 2638 h( 'dd', null, waiting ? h( 'span', { className: 'nfb-summary__wait' }, __( 'set in step 2', 'nanflows-bundle' ) ) : h( 'strong', { className: 'nfb-live__price' }, money( b.price ) ) ) 2596 2639 ), 2597 ! waiting && b.savings > 0 && rows.length > 0 && d.type !== 'quantity_break' && h( 'p', { className: 'nfb-live__save' }, h( Icon, { name: 'tag' } ), ' ', sprintf( /* translators: %s: amount */ __( 'You save %s', 'nanflows-bundle' ), money( b.savings ) ) ), 2640 rows.length > 0 && tierNudge( d ) && h( 'p', { className: 'nfb-live__nudge' }, tierNudge( d ) ), 2641 ! waiting && b.savings > 0 && rows.length > 0 && d.type !== 'quantity_break' && h( 'p', { className: 'nfb-live__save' }, h( Icon, { name: 'tag' } ), ' ', sprintf( /* translators: %s: amount */ __( 'You save %s', 'nanflows-bundle' ), money( b.savings ) ), b.regular > 0 && Math.round( ( b.savings / b.regular ) * 100 ) >= 1 ? ' (' + Math.round( ( b.savings / b.regular ) * 100 ) + '%)' : '' ), 2598 2642 b.note && rows.length > 0 && h( 'p', { className: 'nfb-live__note' }, b.note ), 2599 2643 ! props.embedded && h( 'div', { className: 'nfb-preview__buy' }, h( 'span', { className: 'nfb-qty' }, '− 1 +' ), h( 'span', { className: 'button button-primary' }, config.buttonText || __( 'Add Bundle to Cart', 'nanflows-bundle' ) ) ), -
nanflows-bundle/trunk/assets/frontend/bundle.js
r3736789 r3754062 14 14 } 15 15 16 /** The "(25%)" after the amount saved: rounded, hidden under 1%. */ 17 function percentOff( card, b ) { 18 const box = card.querySelector( '.nfb-bundle__savings-percent' ); 19 const number = box && box.querySelector( '.nfb-bundle__savings-pct' ); 20 if ( ! number ) { 21 return; 22 } 23 const percent = b.regular > 0 ? Math.round( ( b.savings / b.regular ) * 100 ) : 0; 24 number.textContent = String( percent ); 25 box.hidden = percent < 1; 26 } 27 16 28 function update( card ) { 17 29 let currency; … … 52 64 set( '.nfb-bundle__price strong', b.price ); 53 65 set( card.querySelector( '.nfb-bundle__savings-amount' ) ? '.nfb-bundle__savings-amount' : '.nfb-bundle__savings', b.savings ); 66 percentOff( card, b ); 54 67 55 68 // "Frequently bought together" with every extra unticked: the main -
nanflows-bundle/trunk/includes/Admin/Admin.php
r3736789 r3754062 72 72 /** 73 73 * "Bundles" in the plugin's row on the Plugins screen, before 74 * Deactivate, like other plugins' Settings links. 74 * Deactivate, like other plugins' Settings links. Without Pro installed, 75 * "Upgrade to Pro" comes first and opens the Pro page on nanflows.com. 75 76 * 76 77 * @param array<string, string> $links … … 81 82 return $links; 82 83 } 83 return array_merge(84 array(85 'bundles' => '<a href="' . esc_url( admin_url( 'admin.php?page=' . self::PAGE ) ) . '">' . esc_html__( 'Bundles', 'nanflows-bundle' ) . '</a>',86 ),87 (array) $links88 );84 $own = array(); 85 if ( ! defined( 'NANFLOWS_BUNDLE_PRO_VERSION' ) ) { 86 $own['upgrade'] = '<a href="' . esc_url( self::UPGRADE_URL ) . '" target="_blank" rel="noopener" style="color:#1a9f3a;font-weight:600">' . esc_html__( 'Upgrade to Pro', 'nanflows-bundle' ) . '</a>'; 87 } 88 $own['bundles'] = '<a href="' . esc_url( admin_url( 'admin.php?page=' . self::PAGE ) ) . '">' . esc_html__( 'Bundles', 'nanflows-bundle' ) . '</a>'; 89 return array_merge( $own, (array) $links ); 89 90 } 90 91 -
nanflows-bundle/trunk/includes/Frontend/BundleCard.php
r3736789 r3754062 215 215 <?php if ( $settings['show_savings'] && $savings > 0 ) : ?> 216 216 <dt><?php esc_html_e( 'You save', 'nanflows-bundle' ); ?></dt> 217 <dd class="nfb-bundle__savings"><?php echo Icons::get( 'tag' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- fixed markup, escaped inside. ?><span class="nfb-bundle__savings-amount"><?php echo wp_kses_post( wc_price( $savings ) ); ?></span> </dd>217 <dd class="nfb-bundle__savings"><?php echo Icons::get( 'tag' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- fixed markup, escaped inside. ?><span class="nfb-bundle__savings-amount"><?php echo wp_kses_post( wc_price( $savings ) ); ?></span> <?php echo self::percent_off( $regular, $savings ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- a number in fixed markup. ?></dd> 218 218 <?php endif; ?> 219 219 </dl> … … 281 281 ); 282 282 } 283 284 /** 285 * "(25%)" after the amount saved, as the editor shows it. The script 286 * keeps the number current and hides it under 1%. 287 */ 288 public static function percent_off( float $regular, float $savings ): string { 289 $percent = $regular > 0 ? (int) round( $savings / $regular * 100 ) : 0; 290 return '<span class="nfb-bundle__savings-percent"' . ( $percent >= 1 ? '' : ' hidden' ) . '>(<span class="nfb-bundle__savings-pct">' . $percent . '</span>%)</span>'; 291 } 283 292 } -
nanflows-bundle/trunk/nanflows-bundle.php
r3737861 r3754062 4 4 * Plugin URI: https://nanflows.com/products/nanflows-bundle 5 5 * Description: Sell product bundles in WooCommerce: fixed bundles and "frequently bought together" offers, with stock taken from the products inside. 6 * Version: 0.1. 26 * Version: 0.1.3 7 7 * Requires at least: 6.4 8 8 * Requires PHP: 7.4 … … 20 20 defined( 'ABSPATH' ) || exit; 21 21 22 define( 'NANFLOWS_BUNDLE_VERSION', '0.1. 2' );22 define( 'NANFLOWS_BUNDLE_VERSION', '0.1.3' ); 23 23 define( 'NANFLOWS_BUNDLE_FILE', __FILE__ ); 24 24 define( 'NANFLOWS_BUNDLE_DIR', plugin_dir_path( __FILE__ ) ); -
nanflows-bundle/trunk/readme.txt
r3737861 r3754062 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.4 7 Stable tag: 0.1. 27 Stable tag: 0.1.3 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 171 171 == Changelog == 172 172 173 = 0.1.3 = 174 * The bundle box shows the saving as a percentage too, e.g. "You save $28.25 (25%)". 175 * "Upgrade to Pro" link on the Plugins screen. 176 * With Bundles Pro: volume and quantity tiers can take a percentage or a flat amount off, and the bundle box says how to reach the next discount ("Add 1 more and get 10% off"). 177 173 178 = 0.1.2 = 174 179 * A bundle can have a page of its own, with a heading or a banner (computers and phones) at the top, in your theme's design.
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)