From c7e30d354ed293e97e7f8b533b682e5e937c5bf2 Mon Sep 17 00:00:00 2001 From: WooCommerce Date: Thu, 11 Dec 2025 10:21:12 +0000 Subject: [PATCH] Updates to 8.2.0 --- assets/css/modal.css | 7 +- assets/css/view-subscription.css | 11 +++ assets/js/frontend/view-subscription.js | 18 +++-- assets/js/modal.js | 71 ++++++++++++------- build/index.asset.php | 2 +- build/index.js | 6 +- changelog.txt | 25 +++++++ includes/class-wc-subscriptions-plugin.php | 1 + .../core/class-wc-product-subscription.php | 26 +++++++ includes/core/class-wc-subscription.php | 27 ++++++- .../core/class-wc-subscriptions-addresses.php | 1 + .../core/class-wc-subscriptions-product.php | 11 ++- includes/core/class-wcs-modal.php | 32 +++++++++ includes/core/class-wcs-query.php | 3 +- ...s-email-customer-on-hold-renewal-order.php | 7 +- ...ass-wcs-email-customer-renewal-invoice.php | 7 +- ...ass-wcs-email-processing-renewal-order.php | 7 +- includes/core/wcs-formatting-functions.php | 18 +++-- includes/core/wcs-user-functions.php | 3 + ...ass-wc-subscription-downloads-products.php | 40 ++++------- ...ass-wc-subscription-downloads-settings.php | 35 +++++++++ .../class-wc-subscription-downloads.php | 2 +- .../class-wcs-cart-early-renewal.php | 12 +++- .../class-wcs-early-renewal-modal-handler.php | 3 + includes/gifting/class-wcs-gifting.php | 2 +- ...ass-wcsg-email-completed-renewal-order.php | 24 ++++++- .../class-wcsg-email-customer-new-account.php | 26 ++++++- ...ss-wcsg-email-processing-renewal-order.php | 26 ++++++- ...wcsg-email-recipient-new-initial-order.php | 25 ++++++- ...class-wcs-email-customer-payment-retry.php | 7 +- .../emails/class-wcs-email-payment-retry.php | 7 +- .../class-wc-subscriptions-switcher.php | 56 ++++++++++++++- languages/woocommerce-subscriptions.pot | 4 +- src/Internal/Telemetry/Events.php | 5 ++ templates/gifting/subscription-totals.php | 17 ++++- .../html-early-renewal-modal-content.php | 43 +++++++---- templates/html-modal.php | 14 ++-- templates/myaccount/my-subscriptions.php | 2 +- templates/myaccount/related-orders.php | 2 +- templates/myaccount/related-subscriptions.php | 21 ++++-- templates/myaccount/subscription-details.php | 45 ++++++++---- .../myaccount/subscription-totals-table.php | 19 ++++- vendor/composer/installed.php | 12 ++-- woocommerce-subscriptions.php | 8 +-- 44 files changed, 573 insertions(+), 167 deletions(-) diff --git a/assets/css/modal.css b/assets/css/modal.css index 808529f..fc36ae0 100644 --- a/assets/css/modal.css +++ b/assets/css/modal.css @@ -3,10 +3,10 @@ body.wcs-modal-open { } .wcs-modal { + display: none; position: fixed; top: 0; left: 0; - display: flex; align-items: center; justify-content: center; height: 0; @@ -17,6 +17,7 @@ body.wcs-modal-open { } .wcs-modal.open { + display: flex; position: fixed; width: 100%; height: 100vh; @@ -50,6 +51,10 @@ body.wcs-modal-open { top: 0; right: 0; z-index: 50; + border: none; + background: transparent; + padding: 0; + cursor: pointer; } .wcs-modal .content-wrapper .modal-header { diff --git a/assets/css/view-subscription.css b/assets/css/view-subscription.css index 1b829dc..8fd9f79 100644 --- a/assets/css/view-subscription.css +++ b/assets/css/view-subscription.css @@ -15,6 +15,17 @@ margin-bottom: 0.5em; } +button.subscription-auto-renew-toggle, +button.subscription-auto-renew-toggle:hover { + background: none; + border: none; + padding: 0; + cursor: pointer; + font: inherit; + box-shadow: none; + text-shadow: none; +} + .subscription-auto-renew-toggle { margin-left: 5px; margin-bottom: 2px; diff --git a/assets/js/frontend/view-subscription.js b/assets/js/frontend/view-subscription.js index ca54bb1..0a7f4ba 100644 --- a/assets/js/frontend/view-subscription.js +++ b/assets/js/frontend/view-subscription.js @@ -11,8 +11,11 @@ jQuery( function ( $ ) { var $early_renewal_modal_content = $( '.wcs-modal > .content-wrapper' ); function getTxtColor() { - if ( ! txtColor && $icon && $icon.length ) { - txtColor = getComputedStyle( $icon[ 0 ] ).color; + if ( ! txtColor ) { + // Create a temporary link to get the theme's accent color. + var $tempLink = $( '' ).appendTo( $toggleContainer ); + txtColor = getComputedStyle( $tempLink[ 0 ] ).color; + $tempLink.remove(); } return txtColor; @@ -38,9 +41,6 @@ jQuery( function ( $ ) { function onToggle( e ) { e.preventDefault(); - // Remove focus from the toggle element. - $toggle.trigger( 'blur' ); - // Ignore the request if the toggle is disabled. if ( $toggle.hasClass( 'subscription-auto-renew-toggle--disabled' ) ) { return; @@ -105,17 +105,20 @@ jQuery( function ( $ ) { $icon.removeClass( 'fa-toggle-off' ).addClass( 'fa-toggle-on' ); $toggle .removeClass( 'subscription-auto-renew-toggle--off' ) - .addClass( 'subscription-auto-renew-toggle--on' ); + .addClass( 'subscription-auto-renew-toggle--on' ) + .attr( 'aria-checked', 'true' ); } function displayToggleOff() { $icon.removeClass( 'fa-toggle-on' ).addClass( 'fa-toggle-off' ); $toggle .removeClass( 'subscription-auto-renew-toggle--on' ) - .addClass( 'subscription-auto-renew-toggle--off' ); + .addClass( 'subscription-auto-renew-toggle--off' ) + .attr( 'aria-checked', 'false' ); } function blockToggle() { + $toggle.addClass( 'subscription-auto-renew-toggle--disabled' ); $toggleContainer.block( { message: null, overlayCSS: { opacity: 0.0 }, @@ -123,6 +126,7 @@ jQuery( function ( $ ) { } function unblockToggle() { + $toggle.removeClass( 'subscription-auto-renew-toggle--disabled' ); $toggleContainer.unblock(); } diff --git a/assets/js/modal.js b/assets/js/modal.js index 4e9a584..5f0ea0f 100644 --- a/assets/js/modal.js +++ b/assets/js/modal.js @@ -1,11 +1,13 @@ jQuery( function ( $ ) { - const modals = $( '.wcs-modal' ); + const $modals = $( '.wcs-modal' ); + let $currentModal; + let $triggerElement; // Resize all open modals on window resize. $( window ).on( 'resize', resizeModals ); // Initialize modals - $( modals ).each( function () { + $( $modals ).each( function () { trigger = $( this ).data( 'modal-trigger' ); $( trigger ).on( 'click', { modal: this }, show_modal ); } ); @@ -18,34 +20,35 @@ jQuery( function ( $ ) { * @param {JQuery event} event */ function show_modal( event ) { - const modal = $( event.data.modal ); + $triggerElement = $( event.target ); + $currentModal = $( event.data.modal ); - if ( ! should_show_modal( modal ) ) { + if ( ! should_show_modal( $currentModal ) ) { return; } // Prevent the trigger element event being triggered. event.preventDefault(); - const contentWrapper = modal.find( '.content-wrapper' ); - const close = modal.find( '.close' ); + const $contentWrapper = $currentModal.find( '.content-wrapper' ); + const $close = $currentModal.find( '.close' ); - modal.trigger( 'focus' ); - modal.addClass( 'open' ); - - resizeModal( modal ); + $currentModal.addClass( 'open' ); + resizeModal( $currentModal ); $( document.body ).toggleClass( 'wcs-modal-open', true ); + $currentModal.focus(); + document.addEventListener( 'focusin', keepFocusInModal ); // Attach callbacks to handle closing the modal. - close.on( 'click', () => close_modal( modal ) ); - modal.on( 'click', () => close_modal( modal ) ); - contentWrapper.on( 'click', ( e ) => e.stopPropagation() ); + $close.on( 'click', () => close_modal( $currentModal ) ); + $currentModal.on( 'click', () => close_modal( $currentModal ) ); + $contentWrapper.on( 'click', ( e ) => e.stopPropagation() ); // Close the modal if the escape key is pressed. - modal.on( 'keyup', function ( e ) { + $currentModal.on( 'keyup', function ( e ) { if ( 27 === e.keyCode ) { - close_modal( modal ); + close_modal( $currentModal ); } } ); } @@ -53,14 +56,17 @@ jQuery( function ( $ ) { /** * Closes a modal and resets any forced height styles. * - * @param {JQuery Object} modal + * @param {JQuery Object} $modal */ - function close_modal( modal ) { - modal.removeClass( 'open' ); - $( modal ).find( '.content-wrapper' ).css( 'height', '' ); + function close_modal( $modal ) { + $modal.removeClass( 'open' ); + $( $modal ).find( '.content-wrapper' ).css( 'height', '' ); - if ( 0 === modals.filter( '.open' ).length ) { + if ( 0 === $modals.filter( '.open' ).length ) { $( document.body ).removeClass( 'wcs-modal-open' ); + $currentModal = false; + document.removeEventListener( 'focusin', keepFocusInModal ); + $triggerElement.focus(); } } @@ -86,7 +92,7 @@ jQuery( function ( $ ) { * Resize all open modals to fit the display. */ function resizeModals() { - $( modals ).each( function () { + $( $modals ).each( function () { if ( ! $( this ).hasClass( 'open' ) ) { return; } @@ -98,17 +104,28 @@ jQuery( function ( $ ) { /** * Resize a modal to fit the display. * - * @param {JQuery Object} modal + * @param {JQuery Object} $modal */ - function resizeModal( modal ) { - var modal_container = $( modal ).find( '.content-wrapper' ); + function resizeModal( $modal ) { + const $modal_container = $( $modal ).find( '.content-wrapper' ); // On smaller displays the height is already forced to be 100% in CSS. We just clear any height we might set previously. if ( $( window ).width() <= 414 ) { - modal_container.css( 'height', '' ); - } else if ( modal_container.height() > $( window ).height() ) { + $modal_container.css( 'height', '' ); + } else if ( $modal_container.height() > $( window ).height() ) { // Force the container height to trigger scroll etc if it doesn't fit on the screen. - modal_container.css( 'height', '90%' ); + $modal_container.css( 'height', '90%' ); + } + } + + /** + * If focus moves out of the open modal, return focus to it. + * + * @param event + */ + function keepFocusInModal( event ) { + if ( $currentModal && ! $currentModal[0].contains( event.target ) ) { + $currentModal.focus(); } } } ); diff --git a/build/index.asset.php b/build/index.asset.php index dc8fe25..6edb859 100644 --- a/build/index.asset.php +++ b/build/index.asset.php @@ -1 +1 @@ - array('react', 'wc-blocks-checkout', 'wc-blocks-data-store', 'wc-price-format', 'wc-settings', 'wp-data', 'wp-element', 'wp-i18n', 'wp-plugins'), 'version' => 'ac38e67791839651a41a'); + array('react', 'wc-blocks-checkout', 'wc-blocks-data-store', 'wc-price-format', 'wc-settings', 'wp-data', 'wp-element', 'wp-i18n', 'wp-plugins'), 'version' => '841d6ab2fe0080f15a21'); diff --git a/build/index.js b/build/index.js index fed1387..1e5456a 100644 --- a/build/index.js +++ b/build/index.js @@ -8,7 +8,7 @@ * %3$d is the length, %4$s is week, month, year */ (0,r.__)("%1$s %2$s %3$d %4$s","woocommerce-subscriptions"),t,i,e,n)}function d({subscriptionLength:e,billingInterval:s}){return e===s}const g=(0,c.getSetting)("countryData",{}),m=(0,c.getSetting)("countries",{}),_={...Object.fromEntries(Object.keys(g).filter((e=>!0===g[e].allowBilling)).map((e=>[e,m[e]||""]))),...Object.fromEntries(Object.keys(g).filter((e=>!0===g[e].allowShipping)).map((e=>[e,m[e]||""])))},h=Object.fromEntries(Object.keys(_).map((e=>[e,g[e].locale||{}]))),b={address:["first_name","last_name","company","address_1","address_2","city","postcode","country","state","phone"],contact:["email"],order:[]},w=Object.entries(h).reduce(((e,[s,i])=>(e[s]=Object.entries(i).reduce(((e,[s,i])=>(e[s]=(e=>{const s={};return void 0!==e.label&&(s.label=e.label),void 0!==e.required&&(s.required=e.required),void 0!==e.hidden&&(s.hidden=e.hidden),void 0===e.label||e.optionalLabel||(s.optionalLabel=(0,r.sprintf)(/* translators: %s Field label. */ /* translators: %s Field label. */ -(0,r.__)("%s (optional)","woocommerce-subscriptions"),e.label)),void 0!==e.optionalLabel&&(s.optionalLabel=e.optionalLabel),e.index&&("number"==typeof e.index&&(s.index=e.index),"string"==typeof e.index&&(s.index=parseInt(e.index,10))),e.hidden&&(s.required=!1),s})(i),e)),{}),e)),{}),y=["state","country","postcode","city"],f=e=>e.some((e=>!!e.shipping_rates.length)),v=(0,c.getSetting)("collectableMethodIds",[]),x=(0,c.getSetting)("localPickupEnabled",!1);var k=i(848);const S=(0,c.getSetting)("displayCartPricesIncludingTax",!1),L=({currency:e,values:s,isLoading:i})=>{const{total_discount:t,total_discount_tax:o}=s,c=parseInt(t,10);if(!c)return null;const a=parseInt(o,10),l=S?c+a:c;return(0,k.jsx)(n.TotalsItem,{className:"wc-block-components-totals-discount",currency:e,label:(0,r.__)("Discount","woocommerce-subscriptions"),value:-1*l,showSkeleton:i})},j=({values:e,currency:s,shippingRatePackages:i,isLoading:t,needsShipping:n,calculatedShipping:o,shippingAddress:p})=>{const u=(0,a.useSelect)((e=>e(l.checkoutStore).prefersCollection()));if(!n||!o)return null;const d=(_=((e,s)=>e.map((e=>({...e,shipping_rates:e.shipping_rates.filter((e=>{const i=(t=e.method_id,!!x&&(Array.isArray(t)?!!t.find((e=>v.includes(e))):v.includes(t)));var t;return s?i:!i}))}))))(i,null!=u&&u),!!f(_)&&_.every((e=>e.shipping_rates.every((e=>!e.selected||(e=>v.includes(e.method_id))(e)))))),g=!!(m=p).country&&((e,s,i="")=>{const t=i&&void 0!==w[i]?w[i]:{};return e.map((e=>({key:e,...s&&e in s?s[e]:{},...t&&e in t?t[e]:{}}))).sort(((e,s)=>e.index-s.index))})((0,c.getSetting)("addressFieldsLocations",b).address,c.defaultFields,m.country).filter((({key:e})=>y.includes(e))).every((({key:e,hidden:s,required:i})=>!0===s||!1===i||e in m&&""!==m[e]));var m,_;return(0,k.jsx)(I,{label:d?(0,r.__)("Pickup","woocommerce-subscriptions"):(0,r.__)("Delivery","woocommerce-subscriptions"),placeholder:(0,k.jsx)("span",{className:"wc-block-components-shipping-placeholder__value",children:g?(0,r.__)("No available delivery option","woocommerce-subscriptions"):(0,r.__)("Enter address to calculate","woocommerce-subscriptions")}),isLoading:t,shippingRatePackages:i,currency:s,values:e})},I=({values:e,currency:s,shippingRatePackages:i,label:t,placeholder:o,isLoading:a})=>{const l=i?.flatMap((e=>e.shipping_rates)).find((({selected:e})=>e))?.name,p=!!f(u=i)&&u.some((e=>e.shipping_rates.some((e=>e.selected))));var u;const d=!p||i?.length>1?t:(0,r.__)("Shipping","woocommerce-subscriptions"),g=S?parseInt(e.total_shipping,10)+parseInt(e.total_shipping_tax,10):parseInt(e.total_shipping,10),m=0===g&&(0,c.isWcVersion)("9.0",">=")?(0,k.jsx)("strong",{children:(0,r.__)("Free","woocommerce-subscriptions")}):g;return(0,k.jsx)(n.TotalsItem,{value:p?m:o,label:d,currency:s,showSkeleton:a,description:!!l&&(0,r.sprintf)( +(0,r.__)("%s (optional)","woocommerce-subscriptions"),e.label)),void 0!==e.optionalLabel&&(s.optionalLabel=e.optionalLabel),e.index&&("number"==typeof e.index&&(s.index=e.index),"string"==typeof e.index&&(s.index=parseInt(e.index,10))),e.hidden&&(s.required=!1),s})(i),e)),{}),e)),{}),y=["state","country","postcode","city"],f=e=>e.some((e=>!!e.shipping_rates.length)),v=(0,c.getSetting)("collectableMethodIds",[]),x=(0,c.getSetting)("localPickupEnabled",!1);var k=i(848);const S=(0,c.getSetting)("displayCartPricesIncludingTax",!1),L=({currency:e,values:s,isLoading:i})=>{const{total_discount:t,total_discount_tax:o}=s,c=parseInt(t,10);if(!c)return null;const a=parseInt(o,10),l=S?c+a:c;return(0,k.jsx)(n.TotalsItem,{className:"wc-block-components-totals-discount",currency:e,label:(0,r.__)("Discount","woocommerce-subscriptions"),value:-1*l,showSkeleton:i})},j=({values:e,currency:s,shippingRatePackages:i,isLoading:t,calculatedShipping:n,shippingAddress:o})=>{const p=(0,a.useSelect)((e=>e(l.checkoutStore).prefersCollection()));if(!n)return null;const u=(m=((e,s)=>e.map((e=>({...e,shipping_rates:e.shipping_rates.filter((e=>{const i=(t=e.method_id,!!x&&(Array.isArray(t)?!!t.find((e=>v.includes(e))):v.includes(t)));var t;return s?i:!i}))}))))(i,null!=p&&p),!!f(m)&&m.every((e=>e.shipping_rates.every((e=>!e.selected||(e=>v.includes(e.method_id))(e)))))),d=!!(g=o).country&&((e,s,i="")=>{const t=i&&void 0!==w[i]?w[i]:{};return e.map((e=>({key:e,...s&&e in s?s[e]:{},...t&&e in t?t[e]:{}}))).sort(((e,s)=>e.index-s.index))})((0,c.getSetting)("addressFieldsLocations",b).address,c.defaultFields,g.country).filter((({key:e})=>y.includes(e))).every((({key:e,hidden:s,required:i})=>!0===s||!1===i||e in g&&""!==g[e]));var g,m;return(0,k.jsx)(I,{label:u?(0,r.__)("Pickup","woocommerce-subscriptions"):(0,r.__)("Delivery","woocommerce-subscriptions"),placeholder:(0,k.jsx)("span",{className:"wc-block-components-shipping-placeholder__value",children:d?(0,r.__)("No available delivery option","woocommerce-subscriptions"):(0,r.__)("Enter address to calculate","woocommerce-subscriptions")}),isLoading:t,shippingRatePackages:i,currency:s,values:e})},I=({values:e,currency:s,shippingRatePackages:i,label:t,placeholder:o,isLoading:a})=>{const l=i?.flatMap((e=>e.shipping_rates)).find((({selected:e})=>e))?.name,p=!!f(u=i)&&u.some((e=>e.shipping_rates.some((e=>e.selected))));var u;const d=!p||i?.length>1?t:(0,r.__)("Shipping","woocommerce-subscriptions"),g=S?parseInt(e.total_shipping,10)+parseInt(e.total_shipping_tax,10):parseInt(e.total_shipping,10),m=0===g&&(0,c.isWcVersion)("9.0",">=")?(0,k.jsx)("strong",{children:(0,r.__)("Free","woocommerce-subscriptions")}):g;return(0,k.jsx)(n.TotalsItem,{value:p?m:o,label:d,currency:s,showSkeleton:a,description:!!l&&(0,r.sprintf)( // translators: %s selected shipping rate (ex: flat rate) // translators: %s selected shipping rate (ex: flat rate) (0,r.__)("via %s","woocommerce-subscriptions"),l)})},P=({nextPaymentDate:e,subscriptionLength:s,billingPeriod:i,billingInterval:t})=>{const n=function({subscriptionLength:e,billingPeriod:s}){const i=p(e);return(0,r.sprintf)("For %1$d %2$s",e,i[s],"woocommerce-subscriptions")}({subscriptionLength:s,billingPeriod:i}),o=d({subscriptionLength:s,billingInterval:t})?(0,r.sprintf)(/* Translators: %1$s is a date. */ /* Translators: %1$s is a date. */ @@ -16,7 +16,7 @@ (0,r.__)("Starting: %1$s","woocommerce-subscriptions"),e);return(0,k.jsxs)("span",{children:[!!e&&o," ",!!s&&s>=t&&(0,k.jsx)("span",{className:"wcs-recurring-totals__subscription-length",children:n})]})},O=({currency:e,billingInterval:s,billingPeriod:i,nextPaymentDate:t,subscriptionLength:o,totals:c,isLoading:a})=>{const l=d({billingInterval:s,subscriptionLength:o})?(0,r.__)("Total","woocommerce-subscriptions"):function({billingInterval:e,billingPeriod:s}){switch(e){case 1:if("day"===s)return(0,r.__)("Daily recurring total","woocommerce-subscriptions");if("week"===s)return(0,r.__)("Weekly recurring total","woocommerce-subscriptions");if("month"===s)return(0,r.__)("Monthly recurring total","woocommerce-subscriptions");if("year"===s)return(0,r.__)("Yearly recurring total","woocommerce-subscriptions");break;case 2:return(0,r.sprintf)(/* translators: %1$s is week, month, year */ /* translators: %1$s is week, month, year */ (0,r.__)("Recurring total every 2nd %1$s","woocommerce-subscriptions"),s);case 3:return(0,r.sprintf)(/* Translators: %1$s is week, month, year */ /* Translators: %1$s is week, month, year */ (0,r.__)("Recurring total every 3rd %1$s","woocommerce-subscriptions"),s);default:return(0,r.sprintf)(/* Translators: %1$d is number of weeks, months, days, years. %2$s is week, month, year */ /* Translators: %1$d is number of weeks, months, days, years. %2$s is week, month, year */ -(0,r.__)("Recurring total every %1$dth %2$s","woocommerce-subscriptions"),e,s)}}({billingInterval:s,billingPeriod:i});return(0,k.jsx)(n.TotalsItem,{className:"wcs-recurring-totals-panel__title",currency:e,label:l,value:c,showSkeleton:a,description:(0,k.jsx)(P,{nextPaymentDate:t,subscriptionLength:o,billingInterval:s,billingPeriod:i})})},C=({subscription:e,needsShipping:s,calculatedShipping:i,shippingAddress:t})=>{const{isLoading:c}=(()=>{const{cartIsLoading:e,isLoadingRates:s,hasPendingItemsOperations:i,isApplyingCoupon:t,isRemovingCoupon:n}=(0,a.useSelect)((e=>{const s=e(l.cartStore);return{cartIsLoading:!s.hasFinishedResolution("getCartData",[]),isLoadingRates:s.isAddressFieldsForShippingRatesUpdating(),hasPendingItemsOperations:s.hasPendingItemsOperations(),isApplyingCoupon:s.isApplyingCoupon(),isRemovingCoupon:s.isRemovingCoupon()}}),[]),r=(0,a.useSelect)((e=>e(l.checkoutStore).isCalculating()),[]);return{isLoading:e||s||t||n||r||i}})(),{totals:p,billing_interval:u,billing_period:d,next_payment_date:g,subscription_length:m,shipping_rates:_}=e;if(!g)return null;const h=(0,o.getCurrencyFromPriceResponse)(p);return(0,k.jsxs)("div",{className:"wcs-recurring-totals-panel",children:[(0,k.jsx)(O,{billingInterval:u,billingPeriod:d,nextPaymentDate:g,subscriptionLength:m,totals:parseInt(p.total_price,10),currency:h,isLoading:c}),(0,k.jsxs)(n.Panel,{className:"wcs-recurring-totals-panel__details",initialOpen:!1,title:(0,r.__)("Details","woocommerce-subscriptions"),children:[(0,k.jsxs)(n.TotalsWrapper,{children:[(0,k.jsx)(n.Subtotal,{currency:h,values:p,showSkeleton:c}),(0,k.jsx)(L,{currency:h,values:p,isLoading:c})]}),(0,k.jsx)(n.TotalsWrapper,{className:"wc-block-components-totals-shipping",children:(0,k.jsx)(j,{currency:h,needsShipping:s,calculatedShipping:i,values:p,shippingAddress:t,shippingRatePackages:_,isLoading:c})}),!S&&(0,k.jsx)(n.TotalsWrapper,{children:(0,k.jsx)(n.TotalsTaxes,{currency:h,values:p})}),(0,k.jsx)(n.TotalsWrapper,{children:(0,k.jsx)(n.TotalsItem,{className:"wcs-recurring-totals-panel__details-total",currency:h,label:(0,r.__)("Total","woocommerce-subscriptions"),value:parseInt(p.total_price,10),showSkeleton:c})})]})]})},R=({extensions:e,cart:s})=>{const{subscriptions:i}=e,{cartNeedsShipping:t,cartHasCalculatedShipping:n,shippingAddress:r}=s;return i&&0!==i.length?i.map((({key:e,...s})=>(0,k.jsx)(C,{subscription:s,needsShipping:t,calculatedShipping:n,shippingAddress:r},e))):null},D=window.wp.element,$=(0,c.getSetting)("collectableMethodIds",[]),T=({extensions:e,collapsible:s,collapse:i,showItems:t,noResultsMessage:n,renderOption:r,components:o,context:c})=>{const{subscriptions:a=[]}=e,{ShippingRatesControlPackage:l}=o,p=(0,D.useMemo)((()=>Object.values(a).map((e=>e.shipping_rates)).filter(Boolean).flat()),[a]),u=(0,D.useMemo)((()=>p.length>1||i),[p.length,i]),d=(0,D.useMemo)((()=>p.length>1||t),[p.length,t]);return p.filter((e=>!e.match_initial_rates&&e.needs_shipping)).map((({package_id:e,...i})=>(i.shipping_rates=i.shipping_rates.filter((e=>!$.includes(e.method_id))),(0,k.jsx)(l,{packageId:e,packageData:i,collapsible:s,collapse:u,showItems:d,noResultsMessage:n,renderOption:r,highlightChecked:"woocommerce/checkout"===c},e))))};var E=i(609);const F=(0,c.getSetting)("collectableMethodIds",[]),N=({packageId:e,packageData:s,showItems:i,renderPickupLocation:t,pickupLocations:n,packageCount:r,LocalPickupSelect:o})=>{var c;const{selectShippingRate:p}=(0,a.useDispatch)(l.cartStore),[u,d]=(0,E.useState)(null!==(c=s.shipping_rates.find((e=>e.selected))?.rate_id)&&void 0!==c?c:s.shipping_rates[0]?.rate_id);return(0,D.useEffect)((()=>{u&&p(u,e)}),[]),(0,k.jsx)(o,{title:s.name,packageData:s,selectedOption:null!=u?u:"",showItems:i,renderPickupLocation:t,pickupLocations:n,packageCount:r,onChange:s=>{d(s),p(s,e)}})},M=({extensions:e,showItems:s,renderPickupLocation:i,components:t})=>{const{subscriptions:n=[]}=e,{LocalPickupSelect:r}=t,o=(0,D.useMemo)((()=>Object.values(n).map((e=>e.shipping_rates)).filter(Boolean).flat()),[n]),c=(0,D.useMemo)((()=>o.length>1||s),[o.length,s]);return o.filter((e=>!e.match_initial_rates)).map((({package_id:e,...s})=>(s.shipping_rates=s.shipping_rates.filter((e=>F.includes(e.method_id))),(0,k.jsx)(N,{packageId:e,packageData:s,showItems:c,renderPickupLocation:i,pickupLocations:s.shipping_rates,packageCount:o.length,LocalPickupSelect:r},e))))};(0,t.registerPlugin)("woocommerce-subscriptions",{render:()=>(0,k.jsxs)(k.Fragment,{children:[(0,k.jsx)(n.ExperimentalOrderShippingPackages,{children:(0,k.jsx)(T,{})}),(0,k.jsx)(n.ExperimentalOrderLocalPickupPackages,{children:(0,k.jsx)(M,{})}),(0,k.jsx)(n.ExperimentalOrderMeta,{children:(0,k.jsx)(R,{})})]}),scope:"woocommerce-checkout"}),(0,n.registerCheckoutFilters)("woocommerce-subscriptions",{totalLabel:(e,{subscriptions:s})=>s?.length>0?(0,r.__)("Total due today","woocommerce-subscriptions"):e,subtotalPriceFormat:(e,{subscriptions:s})=>{if(s?.billing_period&&s?.billing_interval){const{billing_interval:i,subscription_length:t}=s;return d({subscriptionLength:t,billingInterval:i})?u(s,1===t? +(0,r.__)("Recurring total every %1$dth %2$s","woocommerce-subscriptions"),e,s)}}({billingInterval:s,billingPeriod:i});return(0,k.jsx)(n.TotalsItem,{className:"wcs-recurring-totals-panel__title",currency:e,label:l,value:c,showSkeleton:a,description:(0,k.jsx)(P,{nextPaymentDate:t,subscriptionLength:o,billingInterval:s,billingPeriod:i})})},C=({subscription:e,needsShipping:s,calculatedShipping:i,shippingAddress:t})=>{const{isLoading:c}=(()=>{const{cartIsLoading:e,isLoadingRates:s,hasPendingItemsOperations:i,isApplyingCoupon:t,isRemovingCoupon:n}=(0,a.useSelect)((e=>{const s=e(l.cartStore);return{cartIsLoading:!s.hasFinishedResolution("getCartData",[]),isLoadingRates:s.isAddressFieldsForShippingRatesUpdating(),hasPendingItemsOperations:s.hasPendingItemsOperations(),isApplyingCoupon:s.isApplyingCoupon(),isRemovingCoupon:s.isRemovingCoupon()}}),[]),r=(0,a.useSelect)((e=>e(l.checkoutStore).isCalculating()),[]);return{isLoading:e||s||t||n||r||i}})(),{totals:p,billing_interval:u,billing_period:d,next_payment_date:g,subscription_length:m,shipping_rates:_}=e;if(!g)return null;const h=e?.shipping_rates?.some((e=>e.needs_shipping)),b=(0,o.getCurrencyFromPriceResponse)(p);return(0,k.jsxs)("div",{className:"wcs-recurring-totals-panel",children:[(0,k.jsx)(O,{billingInterval:u,billingPeriod:d,nextPaymentDate:g,subscriptionLength:m,totals:parseInt(p.total_price,10),currency:b,isLoading:c}),(0,k.jsxs)(n.Panel,{className:"wcs-recurring-totals-panel__details",initialOpen:!1,title:(0,r.__)("Details","woocommerce-subscriptions"),children:[(0,k.jsxs)(n.TotalsWrapper,{children:[(0,k.jsx)(n.Subtotal,{currency:b,values:p,showSkeleton:c}),(0,k.jsx)(L,{currency:b,values:p,isLoading:c})]}),s&&h&&(0,k.jsx)(n.TotalsWrapper,{className:"wc-block-components-totals-shipping",children:(0,k.jsx)(j,{currency:b,calculatedShipping:i,values:p,shippingAddress:t,shippingRatePackages:_,isLoading:c})}),!S&&(0,k.jsx)(n.TotalsWrapper,{children:(0,k.jsx)(n.TotalsTaxes,{currency:b,values:p})}),(0,k.jsx)(n.TotalsWrapper,{children:(0,k.jsx)(n.TotalsItem,{className:"wcs-recurring-totals-panel__details-total",currency:b,label:(0,r.__)("Total","woocommerce-subscriptions"),value:parseInt(p.total_price,10),showSkeleton:c})})]})]})},R=({extensions:e,cart:s})=>{const{subscriptions:i}=e,{cartNeedsShipping:t,cartHasCalculatedShipping:n,shippingAddress:r}=s;return i&&0!==i.length?i.map((({key:e,...s})=>(0,k.jsx)(C,{subscription:s,needsShipping:t,calculatedShipping:n,shippingAddress:r},e))):null},D=window.wp.element,$=(0,c.getSetting)("collectableMethodIds",[]),T=({extensions:e,collapsible:s,collapse:i,showItems:t,noResultsMessage:n,renderOption:r,components:o,context:c})=>{const{subscriptions:a=[]}=e,{ShippingRatesControlPackage:l}=o,p=(0,D.useMemo)((()=>Object.values(a).map((e=>e.shipping_rates)).filter(Boolean).flat()),[a]),u=(0,D.useMemo)((()=>p.length>1||i),[p.length,i]),d=(0,D.useMemo)((()=>p.length>1||t),[p.length,t]);return p.filter((e=>!e.match_initial_rates&&e.needs_shipping)).map((({package_id:e,...i})=>(i.shipping_rates=i.shipping_rates.filter((e=>!$.includes(e.method_id))),(0,k.jsx)(l,{packageId:e,packageData:i,collapsible:s,collapse:u,showItems:d,noResultsMessage:n,renderOption:r,highlightChecked:"woocommerce/checkout"===c},e))))};var E=i(609);const F=(0,c.getSetting)("collectableMethodIds",[]),N=({packageId:e,packageData:s,showItems:i,renderPickupLocation:t,pickupLocations:n,packageCount:r,LocalPickupSelect:o})=>{var c;const{selectShippingRate:p}=(0,a.useDispatch)(l.cartStore),[u,d]=(0,E.useState)(null!==(c=s.shipping_rates.find((e=>e.selected))?.rate_id)&&void 0!==c?c:s.shipping_rates[0]?.rate_id);return(0,D.useEffect)((()=>{u&&p(u,e)}),[]),(0,k.jsx)(o,{title:s.name,packageData:s,selectedOption:null!=u?u:"",showItems:i,renderPickupLocation:t,pickupLocations:n,packageCount:r,onChange:s=>{d(s),p(s,e)}})},M=({extensions:e,showItems:s,renderPickupLocation:i,components:t})=>{const{subscriptions:n=[]}=e,{LocalPickupSelect:r}=t,o=(0,D.useMemo)((()=>Object.values(n).map((e=>e.shipping_rates)).filter(Boolean).flat()),[n]),c=(0,D.useMemo)((()=>o.length>1||s),[o.length,s]);return o.filter((e=>!e.match_initial_rates&&e.needs_shipping)).map((({package_id:e,...s})=>(s.shipping_rates=s.shipping_rates.filter((e=>F.includes(e.method_id))),(0,k.jsx)(N,{packageId:e,packageData:s,showItems:c,renderPickupLocation:i,pickupLocations:s.shipping_rates,packageCount:o.length,LocalPickupSelect:r},e))))};(0,t.registerPlugin)("woocommerce-subscriptions",{render:()=>(0,k.jsxs)(k.Fragment,{children:[(0,k.jsx)(n.ExperimentalOrderShippingPackages,{children:(0,k.jsx)(T,{})}),(0,k.jsx)(n.ExperimentalOrderLocalPickupPackages,{children:(0,k.jsx)(M,{})}),(0,k.jsx)(n.ExperimentalOrderMeta,{children:(0,k.jsx)(R,{})})]}),scope:"woocommerce-checkout"}),(0,n.registerCheckoutFilters)("woocommerce-subscriptions",{totalLabel:(e,{subscriptions:s})=>s?.length>0?(0,r.__)("Total due today","woocommerce-subscriptions"):e,subtotalPriceFormat:(e,{subscriptions:s})=>{if(s?.billing_period&&s?.billing_interval){const{billing_interval:i,subscription_length:t}=s;return d({subscriptionLength:t,billingInterval:i})?u(s,1===t? // translators: the word used to describe billing frequency, e.g. "for" 1 day or "for" 1 month. // translators: the word used to describe billing frequency, e.g. "for" 1 day or "for" 1 month. (0,r.__)("for 1","woocommerce-subscriptions"): @@ -31,6 +31,6 @@ (0,r.__)("%s (resubscription)","woocommerce-subscriptions"),e):s?.switch_type?(0,r.sprintf)( // translators: %1$s Product name, %2$s Switch type (upgraded, downgraded, or crossgraded). // translators: %1$s Product name, %2$s Switch type (upgraded, downgraded, or crossgraded). -(0,r.__)("%1$s (%2$s)","woocommerce-subscriptions"),e,function(e){switch(e){case"upgraded":return(0,r.__)("Upgrade","woocommerce-subscriptions");case"downgraded":return(0,r.__)("Downgrade","woocommerce-subscriptions");case"crossgraded":return(0,r.__)("Crossgrade","woocommerce-subscriptions");default:return""}}(s.switch_type)):e,cartItemPrice:(e,{subscriptions:s},{context:i})=>s?.sign_up_fees?"cart"===i?(0,r.sprintf)(/* translators: %s is the subscription price to pay immediately (ie: $10). */ /* translators: %s is the subscription price to pay immediately (ie: $10). */ +(0,r.__)("%1$s (%2$s)","woocommerce-subscriptions"),e,function(e){switch(e){case"upgraded":return(0,r.__)("Upgrade","woocommerce-subscriptions");case"downgraded":return(0,r.__)("Downgrade","woocommerce-subscriptions");case"crossgraded":return(0,r.__)("Crossgrade","woocommerce-subscriptions");default:return""}}(s.switch_type)):e,cartItemPrice:(e,{subscriptions:s},{context:i})=>s?.sign_up_fees&&parseInt(s.sign_up_fees,10)>0?"cart"===i?(0,r.sprintf)(/* translators: %s is the subscription price to pay immediately (ie: $10). */ /* translators: %s is the subscription price to pay immediately (ie: $10). */ (0,r.__)("Due today %s","woocommerce-subscriptions"),e):(0,r.sprintf)(/* translators: %s is the subscription price to pay immediately (ie: $10). */ /* translators: %s is the subscription price to pay immediately (ie: $10). */ (0,r.__)("%s due today","woocommerce-subscriptions"),e):e,placeOrderButtonLabel:e=>{const s=(0,c.getSetting)("subscriptions_data");return s?.place_order_override?s?.place_order_override:e}})})(); \ No newline at end of file diff --git a/changelog.txt b/changelog.txt index aa5bc75..be73580 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,30 @@ *** WooCommerce Subscriptions Changelog *** +2025-12-10 - version 8.2.0 +* Fix: Various accessibility issues. + * Added button roles. + * Added labels to remove product links. + * Adding missing header labels on related orders table. + * Prevented various hidden elements from being exposed to screen readers. + * Fixed focus problems for the renewal dialog. + * Fixed focus when closing modals. + * Fixed toggle state change using spacebar. + * Fixed label for sign up now button. + * Fixed toggle aria-label text to reflect state. + * Added aria-haspopup attribute to buttons that open modals. + * Fixed link for view order aria-label. + * Fixed renew now button text and aria-label. + * Added aria-modal and role attributes to modals and dialogs. +* Fix: Resubscribe button now appears correctly for cancelled limited subscriptions. +* Fix: Missing translation for pricing string on languages without plural form. +* Fix: Customized subscriptions links in WooCommerce emails now work correctly. +* Fix: Possible fatal errors when switching grouped subscriptions. +* Fix: Prevent unnecessary log entries related to gifted subscriptions. +* Fix: Make it easier for translation plugins to respect individual customer language preferences when sending subscription emails. +* Fix: Prevent recurring local pickup options from displaying for virtual subscriptions when the cart contains physical products. +* Fix: Prevent shipping summary from displaying on Blocks Checkout for virtual subscriptions. +* Fix: Item price in blocks checkout is correctly displayed without "due today" words for items with zero sign up fee. + 2025-11-13 - version 8.1.0 * Fix: Prevent a fatal error that can occur when previewing emails in WooCommerce email settings. * Fix: Prevent technical subscription-specific discount types from appearing in the coupon edit UI. diff --git a/includes/class-wc-subscriptions-plugin.php b/includes/class-wc-subscriptions-plugin.php index 5416eb5..66f0b7c 100644 --- a/includes/class-wc-subscriptions-plugin.php +++ b/includes/class-wc-subscriptions-plugin.php @@ -45,6 +45,7 @@ class WC_Subscriptions_Plugin extends WC_Subscriptions_Core_Plugin { add_action( 'admin_enqueue_scripts', array( $this, 'maybe_show_welcome_message' ) ); add_action( 'plugins_loaded', array( $this, 'init_gifting' ) ); add_action( 'plugins_loaded', array( $this, 'init_downloads' ) ); + add_action( 'admin_notices', array( WC_Subscription_Downloads_Settings::class, 'add_notice_about_bundled_feature' ) ); } /** diff --git a/includes/core/class-wc-product-subscription.php b/includes/core/class-wc-product-subscription.php index 1b4aa5b..ba66117 100644 --- a/includes/core/class-wc-product-subscription.php +++ b/includes/core/class-wc-product-subscription.php @@ -76,6 +76,32 @@ class WC_Product_Subscription extends WC_Product_Simple { return apply_filters( 'woocommerce_product_add_to_cart_text', $text, $this ); } + /** + * Provides the descriptive text for add-to-cart buttons. + * + * @return mixed + */ + public function add_to_cart_description() { + if ( $this->is_purchasable() && $this->is_in_stock() ) { + // For accessibility reasons it is recommended that the aria-label is the same as, or else starts with, the + // same text that is visible on the button itself. + $text = sprintf( + // Translators: %1$s: Pre-determined add-to-cart text 2: Product title. + _x( '%1$s: “%2$s”', 'Add-to-cart button description', 'woocommerce-subscriptions' ), + WC_Subscriptions_Product::get_add_to_cart_text(), + $this->get_name() + ); + } else { + $text = sprintf( + // Translators: %1$s: Product title. + __( 'Read more about “%1$s”', 'woocommerce-subscriptions' ), + $this->get_name() + ); + } + + return apply_filters( 'woocommerce_product_add_to_cart_description', sprintf( $text, $this->get_name() ), $this ); + } + /** * Get the add to cart button text for the single page * diff --git a/includes/core/class-wc-subscription.php b/includes/core/class-wc-subscription.php index b02f18e..7a52a2b 100644 --- a/includes/core/class-wc-subscription.php +++ b/includes/core/class-wc-subscription.php @@ -428,13 +428,38 @@ class WC_Subscription extends WC_Order { /** * Checks if the subscription contains an unavailable product. * + * A product is considered unavailable if it is: + * - Deleted (not found) + * - Not published (draft, trash, private, etc.) + * + * Note: This method intentionally does NOT use is_purchasable() to avoid incorrectly + * flagging limited products as unavailable. Limited products return is_purchasable() = false + * for users with existing subscriptions, but they should still be available for resubscribe. + * Functions like wcs_can_user_resubscribe_to() have specific logic to handle limited products + * by checking if the user has an active subscription. + * * @return bool */ public function contains_unavailable_product() { /** @var WC_Order_Item_Product $line_item */ foreach ( $this->get_items() as $line_item ) { $product = $line_item->get_product(); - if ( ! $product instanceof WC_Product || ! $product->is_purchasable() ) { + + // Product doesn't exist (deleted). + if ( ! $product instanceof WC_Product ) { + return true; + } + + // If the product is a subscription variation, use the parent product. + if ( $product->is_type( 'subscription_variation' ) ) { + $parent_product_id = $product->get_parent_id(); + $product = wc_get_product( $parent_product_id ); + } + + // Check if product is published. Products with other statuses (draft, trash, private) + // are not available for purchase or resubscribe. + $product_status = $product->get_status(); + if ( 'publish' !== $product_status ) { return true; } } diff --git a/includes/core/class-wc-subscriptions-addresses.php b/includes/core/class-wc-subscriptions-addresses.php index a618798..afbe718 100644 --- a/includes/core/class-wc-subscriptions-addresses.php +++ b/includes/core/class-wc-subscriptions-addresses.php @@ -64,6 +64,7 @@ class WC_Subscriptions_Addresses { $actions['change_address'] = array( 'url' => esc_url( add_query_arg( array( 'subscription' => $subscription->get_id() ), wc_get_endpoint_url( 'edit-address', 'shipping' ) ) ), 'name' => __( 'Change address', 'woocommerce-subscriptions' ), + 'role' => 'link', ); } diff --git a/includes/core/class-wc-subscriptions-product.php b/includes/core/class-wc-subscriptions-product.php index 9cd7a99..606e590 100644 --- a/includes/core/class-wc-subscriptions-product.php +++ b/includes/core/class-wc-subscriptions-product.php @@ -383,10 +383,17 @@ class WC_Subscriptions_Product { } break; } + } elseif ( 1 === $billing_interval ) { + $subscription_string = sprintf( + // translators: 1$: recurring amount, 2$: subscription period (e.g. "month") (e.g. "$15 / month"). + __( '%1$s / %2$s', 'woocommerce-subscriptions' ), + $price, + wcs_get_subscription_period_strings( $billing_interval, $billing_period ) + ); } else { $subscription_string = sprintf( - // translators: 1$: recurring amount, 2$: subscription period (e.g. "month" or "3 months") (e.g. "$15 / month" or "$15 every 2nd month"). - _n( '%1$s / %2$s', '%1$s every %2$s', $billing_interval, 'woocommerce-subscriptions' ), + // translators: 1$: recurring amount, 2$: subscription period (e.g. "3 months") (e.g. "$15 every 2nd month"). + __( '%1$s every %2$s', 'woocommerce-subscriptions' ), $price, wcs_get_subscription_period_strings( $billing_interval, $billing_period ) ); diff --git a/includes/core/class-wcs-modal.php b/includes/core/class-wcs-modal.php index 3ed19de..1628439 100644 --- a/includes/core/class-wcs-modal.php +++ b/includes/core/class-wcs-modal.php @@ -51,6 +51,13 @@ class WCS_Modal { */ private $actions = array(); + /** + * A unique ID for the modal to use in HTML ID attribute. + * + * @var string + */ + private $id = ''; + /** * Registers the scripts and stylesheets needed to display the modals. * @@ -104,6 +111,7 @@ class WCS_Modal { $this->trigger = $trigger; $this->heading = $heading; $this->actions = $actions; + $this->id = wp_unique_id( 'wcs-modal-' ); // Allow callers to provide the callback without any parameters. Assuming the content provided is the callback. if ( 'callback' === $this->content_type && ! isset( $content['parameters'] ) ) { @@ -229,6 +237,30 @@ class WCS_Modal { return $this->trigger; } + /** + * Sets the modal's unique ID. + * + * This is used for the actual HTML ID attribute, and so should follow the normal CSS identifier rules. + * + * @since 8.2.0 + * + * @param string $id The modal's unique ID. + */ + public function set_id( $id ) { + $this->id = $id; + } + + /** + * Returns the modal's unique ID. + * + * @since 8.2.0 + * + * @return string The modal's unique ID. + */ + public function get_id() { + return $this->id; + } + /** * Returns a flattened string of HTML element attributes from an array of attributes and values. * diff --git a/includes/core/class-wcs-query.php b/includes/core/class-wcs-query.php index 7d5fc54..9585f85 100644 --- a/includes/core/class-wcs-query.php +++ b/includes/core/class-wcs-query.php @@ -13,12 +13,13 @@ class WCS_Query extends WC_Query { add_filter( 'the_title', array( $this, 'change_endpoint_title' ), 11, 1 ); + add_filter( 'woocommerce_get_query_vars', array( $this, 'add_wcs_query_vars' ) ); + if ( ! is_admin() ) { add_filter( 'query_vars', array( $this, 'add_query_vars' ), 0 ); add_action( 'parse_request', array( $this, 'parse_request' ), 0 ); add_action( 'pre_get_posts', array( $this, 'maybe_redirect_payment_methods' ) ); add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ), 11 ); - add_filter( 'woocommerce_get_query_vars', array( $this, 'add_wcs_query_vars' ) ); // Inserting your new tab/page into the My Account page. add_filter( 'woocommerce_account_menu_items', array( $this, 'add_menu_items' ) ); diff --git a/includes/core/emails/class-wcs-email-customer-on-hold-renewal-order.php b/includes/core/emails/class-wcs-email-customer-on-hold-renewal-order.php index 615a166..9d8d49a 100644 --- a/includes/core/emails/class-wcs-email-customer-on-hold-renewal-order.php +++ b/includes/core/emails/class-wcs-email-customer-on-hold-renewal-order.php @@ -21,8 +21,7 @@ class WCS_Email_Customer_On_Hold_Renewal_Order extends WC_Email_Customer_On_Hold $this->customer_email = true; $this->title = __( 'On-hold Renewal Order', 'woocommerce-subscriptions' ); $this->description = __( 'This is an order notification sent to customers containing order details after a renewal order is placed on-hold.', 'woocommerce-subscriptions' ); - $this->subject = __( 'Your {site_title} renewal order has been received!', 'woocommerce-subscriptions' ); - $this->heading = __( 'Thank you for your renewal order', 'woocommerce-subscriptions' ); + $this->template_html = 'emails/customer-on-hold-renewal-order.php'; $this->template_plain = 'emails/plain/customer-on-hold-renewal-order.php'; $this->template_base = WC_Subscriptions_Plugin::instance()->get_plugin_directory( 'templates/' ); @@ -47,7 +46,7 @@ class WCS_Email_Customer_On_Hold_Renewal_Order extends WC_Email_Customer_On_Hold * @return string */ public function get_default_subject() { - return $this->subject; + return __( 'Your {site_title} renewal order has been received!', 'woocommerce-subscriptions' ); } /** @@ -57,7 +56,7 @@ class WCS_Email_Customer_On_Hold_Renewal_Order extends WC_Email_Customer_On_Hold * @return string */ public function get_default_heading() { - return $this->heading; + return __( 'Thank you for your renewal order', 'woocommerce-subscriptions' ); } /** diff --git a/includes/core/emails/class-wcs-email-customer-renewal-invoice.php b/includes/core/emails/class-wcs-email-customer-renewal-invoice.php index 4500e90..38f9589 100644 --- a/includes/core/emails/class-wcs-email-customer-renewal-invoice.php +++ b/includes/core/emails/class-wcs-email-customer-renewal-invoice.php @@ -44,9 +44,6 @@ class WCS_Email_Customer_Renewal_Invoice extends WC_Email_Customer_Invoice { $this->template_plain = 'emails/plain/customer-renewal-invoice.php'; $this->template_base = WC_Subscriptions_Plugin::instance()->get_plugin_directory( 'templates/' ); - $this->subject = __( 'Invoice for renewal order {order_number} from {order_date}', 'woocommerce-subscriptions' ); - $this->heading = __( 'Invoice for renewal order {order_number}', 'woocommerce-subscriptions' ); - // Triggers for this email add_action( 'woocommerce_generated_manual_renewal_order_renewal_notification', array( $this, 'trigger' ) ); add_action( 'woocommerce_order_status_failed_renewal_notification', array( $this, 'trigger' ) ); @@ -63,7 +60,7 @@ class WCS_Email_Customer_Renewal_Invoice extends WC_Email_Customer_Invoice { * @return string */ public function get_default_subject( $paid = false ) { - return $this->subject; + return __( 'Invoice for renewal order {order_number} from {order_date}', 'woocommerce-subscriptions' ); } /** @@ -74,7 +71,7 @@ class WCS_Email_Customer_Renewal_Invoice extends WC_Email_Customer_Invoice { * @return string */ public function get_default_heading( $paid = false ) { - return $this->heading; + return __( 'Invoice for renewal order {order_number}', 'woocommerce-subscriptions' ); } /** diff --git a/includes/core/emails/class-wcs-email-processing-renewal-order.php b/includes/core/emails/class-wcs-email-processing-renewal-order.php index 5690cb6..c7c587b 100644 --- a/includes/core/emails/class-wcs-email-processing-renewal-order.php +++ b/includes/core/emails/class-wcs-email-processing-renewal-order.php @@ -24,9 +24,6 @@ class WCS_Email_Processing_Renewal_Order extends WC_Email_Customer_Processing_Or $this->description = __( 'This is an order notification sent to the customer after payment for a subscription renewal order is completed. It contains the renewal order details.', 'woocommerce-subscriptions' ); $this->customer_email = true; - $this->heading = __( 'Thank you for your order', 'woocommerce-subscriptions' ); - $this->subject = __( 'Your {site_title} renewal order receipt from {order_date}', 'woocommerce-subscriptions' ); - $this->template_html = 'emails/customer-processing-renewal-order.php'; $this->template_plain = 'emails/plain/customer-processing-renewal-order.php'; $this->template_base = WC_Subscriptions_Plugin::instance()->get_plugin_directory( 'templates/' ); @@ -48,7 +45,7 @@ class WCS_Email_Processing_Renewal_Order extends WC_Email_Customer_Processing_Or * @return string */ public function get_default_subject() { - return $this->subject; + return __( 'Your {site_title} renewal order receipt from {order_date}', 'woocommerce-subscriptions' ); } /** @@ -58,7 +55,7 @@ class WCS_Email_Processing_Renewal_Order extends WC_Email_Customer_Processing_Or * @return string */ public function get_default_heading() { - return $this->heading; + return __( 'Thank you for your order', 'woocommerce-subscriptions' ); } /** diff --git a/includes/core/wcs-formatting-functions.php b/includes/core/wcs-formatting-functions.php index 69b753f..bb4061b 100644 --- a/includes/core/wcs-formatting-functions.php +++ b/includes/core/wcs-formatting-functions.php @@ -190,12 +190,22 @@ function wcs_price_string( $subscription_details ) { break; } } elseif ( ! empty( $subscription_details['initial_amount'] ) ) { - // translators: 1$: initial amount, 2$: initial description (e.g. "up front"), 3$: recurring amount, 4$: subscription period (e.g. "month" or "3 months") - $subscription_string = sprintf( _n( '%1$s %2$s then %3$s / %4$s', '%1$s %2$s then %3$s every %4$s', $subscription_details['subscription_interval'], 'woocommerce-subscriptions' ), $initial_amount_string, $subscription_details['initial_description'], $recurring_amount_string, $subscription_period_string ); + if ( 1 === $subscription_details['subscription_interval'] ) { + // translators: 1$: initial amount, 2$: initial description (e.g. "up front"), 3$: recurring amount, 4$: subscription period (e.g. "month") + $subscription_string = sprintf( __( '%1$s %2$s then %3$s / %4$s', 'woocommerce-subscriptions' ), $initial_amount_string, $subscription_details['initial_description'], $recurring_amount_string, $subscription_period_string ); + } else { + // translators: 1$: initial amount, 2$: initial description (e.g. "up front"), 3$: recurring amount, 4$: subscription period (e.g. "3 months") + $subscription_string = sprintf( __( '%1$s %2$s then %3$s every %4$s', 'woocommerce-subscriptions' ), $initial_amount_string, $subscription_details['initial_description'], $recurring_amount_string, $subscription_period_string ); + } } elseif ( ! empty( $subscription_details['recurring_amount'] ) || intval( $subscription_details['recurring_amount'] ) === 0 ) { if ( true === $subscription_details['use_per_slash'] ) { - // translators: 1$: recurring amount, 2$: subscription period (e.g. "month" or "3 months") (e.g. "$15 / month" or "$15 every 2nd month") - $subscription_string = sprintf( _n( '%1$s / %2$s', '%1$s every %2$s', $subscription_details['subscription_interval'], 'woocommerce-subscriptions' ), $recurring_amount_string, $subscription_period_string ); + if ( 1 === $subscription_details['subscription_interval'] ) { + // translators: 1$: recurring amount, 2$: subscription period (e.g. "month") (e.g. "$15 / month") + $subscription_string = sprintf( __( '%1$s / %2$s', 'woocommerce-subscriptions' ), $recurring_amount_string, $subscription_period_string ); + } else { + // translators: 1$: recurring amount, 2$: subscription period (e.g. "3 months") (e.g. "$15 every 2nd month") + $subscription_string = sprintf( __( '%1$s every %2$s', 'woocommerce-subscriptions' ), $recurring_amount_string, $subscription_period_string ); + } } else { // translators: %1$: recurring amount (e.g. "$15"), %2$: subscription period (e.g. "month") (e.g. "$15 every 2nd month") $subscription_string = sprintf( __( '%1$s every %2$s', 'woocommerce-subscriptions' ), $recurring_amount_string, $subscription_period_string ); diff --git a/includes/core/wcs-user-functions.php b/includes/core/wcs-user-functions.php index 64797ce..558a7e5 100644 --- a/includes/core/wcs-user-functions.php +++ b/includes/core/wcs-user-functions.php @@ -310,6 +310,7 @@ function wcs_get_all_user_actions_for_subscription( $subscription, $user_id ) { 'url' => wcs_get_users_change_status_link( $subscription->get_id(), 'active', $current_status ), 'name' => __( 'Reactivate', 'woocommerce-subscriptions' ), 'block_ui' => true, + 'role' => 'button', ); } @@ -318,6 +319,7 @@ function wcs_get_all_user_actions_for_subscription( $subscription, $user_id ) { 'url' => wcs_get_users_resubscribe_link( $subscription ), 'name' => __( 'Resubscribe', 'woocommerce-subscriptions' ), 'block_ui' => true, + 'role' => 'button', ); } @@ -328,6 +330,7 @@ function wcs_get_all_user_actions_for_subscription( $subscription, $user_id ) { 'url' => wcs_get_users_change_status_link( $subscription->get_id(), 'cancelled', $current_status ), 'name' => _x( 'Cancel', 'an action on a subscription', 'woocommerce-subscriptions' ), 'block_ui' => true, + 'role' => 'button', ); } } diff --git a/includes/downloads/class-wc-subscription-downloads-products.php b/includes/downloads/class-wc-subscription-downloads-products.php index e7e5c22..6f6a115 100644 --- a/includes/downloads/class-wc-subscription-downloads-products.php +++ b/includes/downloads/class-wc-subscription-downloads-products.php @@ -68,9 +68,7 @@ class WC_Subscription_Downloads_Products {