diff --git a/assets/css/admin.css b/assets/css/admin.css
index 7ae14ae..447f98e 100644
--- a/assets/css/admin.css
+++ b/assets/css/admin.css
@@ -77,6 +77,14 @@ a.close-subscriptions-search {
display: block;
width: 50%;
}
+
+.woocommerce_options_panel ._subscription_downloads_field .description,
+.woocommerce_options_panel .subscription_linked_downloadable_products .description {
+ clear: both;
+ display: block;
+ margin: 0;
+}
+
/* Variation Subscription Product Sync Settings */
.variable_subscription_sync .subscription_sync_annual > .select2-container {
max-width: 48%;
@@ -385,6 +393,12 @@ p._subscription_gifting_field_description.form-field .description {
opacity: 0.5;
}
+#woocommerce-product-data
+ .form-field._subscription_downloads_field
+ .select2-selection__choice {
+ font-size: 14px;
+}
+
/* Variation Pricing Fields with WooCommerce 3.0+ */
.wc-metaboxes-wrapper .variable_subscription_trial label,
.wc-metaboxes-wrapper .variable_subscription_pricing label,
diff --git a/assets/js/admin/admin.js b/assets/js/admin/admin.js
index 3101e78..257b38d 100644
--- a/assets/js/admin/admin.js
+++ b/assets/js/admin/admin.js
@@ -639,6 +639,53 @@ jQuery( function ( $ ) {
},
} );
+ /**
+ * Relocates the linked downloadable product fields in the context of a variable subscription product, and
+ * selectively shows/hides it, depending on whether the variation is downloadable or not.
+ */
+ function initLinkedDownloadableProductFieldsForVariationProduct() {
+ $( '#variable_product_options .variable_subscription_linked_downloadable_products' )
+ .not( '.wcs_moved' )
+ .each( function () {
+ const $this = $( this );
+ const $variablePricing = $( this ).siblings( '.variable_pricing' );
+
+ const $downloadableFiles = $( this ).siblings( '.form-row.downloadable_files' ).parent( 'div' );
+
+ $this.insertAfter( $downloadableFiles );
+ $this.addClass( 'wcs_gifting_moved' );
+ } );
+ }
+
+ /**
+ * Move the linked downloadable product fields to the correct location (which is underneath the downloadable files
+ * section).
+ */
+ function relocateLinkedDownloadableProductFields() {
+ const $linkedDownloadableProducts = $( '.subscription_linked_downloadable_products_section' );
+ const $productTypeSelector = $('select#product-type');
+
+ const showHide = () => {
+ $productTypeSelector.val() === WCSubscriptions.productType
+ ? $linkedDownloadableProducts.show()
+ : $linkedDownloadableProducts.hide();
+ };
+
+ $linkedDownloadableProducts
+ .not( '.wcs_moved' )
+ .each( function () {
+ const $downloadableFilesSection = $( '.form-field.downloadable_files' ).parent( 'div' );
+
+ if ( $downloadableFilesSection.length === 1 ) {
+ $( this ).insertAfter( $downloadableFilesSection );
+ $( this ).addClass( 'wcs_moved' );
+ }
+ } );
+
+ showHide();
+ $productTypeSelector.on( 'change', showHide );
+ }
+
$( '.options_group.pricing ._sale_price_field .description' ).prepend(
''
);
@@ -653,9 +700,9 @@ jQuery( function ( $ ) {
$( '.options_group.subscription_pricing' )
);
- // Move the subscription variation pricing and gifting sections to a better location in the DOM on load
- // We do this because these sections are initially loaded at the end of the variable product options section,
- // which is not the best location for them, so we move to before the "Sale price" section.
+ // Move the subscription variation pricing, gifting and linked downloadable product sections to a better location in
+ // the DOM on load. We do this because these sections are initially loaded at the end of the variable product
+ // options section, which is not the best location for them, so we move to before the "Sale price" section.
if (
$( '#variable_product_options .variable_subscription_pricing' ).length >
0
@@ -668,6 +715,7 @@ jQuery( function ( $ ) {
) {
$.moveSubscriptionGiftingFields();
}
+
// When a variation is added
$( '#woocommerce-product-data' ).on(
'woocommerce_variations_added woocommerce_variations_loaded',
@@ -677,6 +725,7 @@ jQuery( function ( $ ) {
$.showHideVariableSubscriptionMeta();
$.showHideSyncOptions();
$.setSubscriptionLengths();
+ initLinkedDownloadableProductFieldsForVariationProduct();
}
);
@@ -690,6 +739,7 @@ jQuery( function ( $ ) {
$.showHideSyncOptions();
$.disableEnableOneTimeShipping();
$.showHideSubscriptionsPanels();
+ relocateLinkedDownloadableProductFields();
}
// Update subscription ranges when subscription period or interval is changed
diff --git a/assets/js/admin/writepanel.js b/assets/js/admin/writepanel.js
deleted file mode 100644
index 6b91bd0..0000000
--- a/assets/js/admin/writepanel.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/* global wc_subscription_downloads_product */
-jQuery( document ).ready( function ( $ ) {
- function subscriptionDownloadsChosen() {
- $( 'select.subscription-downloads-ids' ).ajaxChosen({
- method: 'GET',
- url: wc_subscription_downloads_product.ajax_url,
- dataType: 'json',
- afterTypeDelay: 100,
- minTermLength: 1,
- data: {
- action: 'wc_subscription_downloads_search',
- security: wc_subscription_downloads_product.security
- }
- }, function ( data ) {
-
- var orders = {};
-
- $.each( data, function ( i, val ) {
- orders[i] = val;
- });
-
- return orders;
- });
- }
-
- subscriptionDownloadsChosen();
-
- $( 'body' ).on( 'woocommerce_variations_added', function () {
- subscriptionDownloadsChosen();
- });
-});
diff --git a/assets/js/gifting/wcs-gifting.js b/assets/js/gifting/wcs-gifting.js
index 65f5d86..2efe9af 100644
--- a/assets/js/gifting/wcs-gifting.js
+++ b/assets/js/gifting/wcs-gifting.js
@@ -18,7 +18,8 @@ jQuery( document ).ready( function ( $ ) {
.find( '.recipient_email' )
.trigger( 'focus' );
}
- } );
+ } )
+ .removeClass( 'hidden' );
const shipToDifferentAddressCheckbox = $( document ).find(
'#ship-to-different-address-checkbox'
@@ -31,12 +32,14 @@ jQuery( document ).ready( function ( $ ) {
$( this )
.closest( '.wcsg_add_recipient_fields_container' )
.find( '.wcsg_add_recipient_fields' )
- .slideUp( 250 );
+ .slideUp( 250 )
+ .addClass( 'hidden' );
const recipientEmailElement = $( this )
.closest( '.wcsg_add_recipient_fields_container' )
.find( '.recipient_email' );
recipientEmailElement.val( '' );
+ hideValidationErrorForEmailField( recipientEmailElement );
setShippingAddressNoticeVisibility( true );
if ( $( 'form.checkout' ).length !== 0 ) {
diff --git a/build/admin.asset.php b/build/admin.asset.php
new file mode 100644
index 0000000..6c89127
--- /dev/null
+++ b/build/admin.asset.php
@@ -0,0 +1 @@
+ array('react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-i18n', 'wp-primitives'), 'version' => '8bad0ce18409676b15d1');
diff --git a/build/admin.js b/build/admin.js
new file mode 100644
index 0000000..e9c601d
--- /dev/null
+++ b/build/admin.js
@@ -0,0 +1 @@
+(()=>{"use strict";var e,o={20:(e,o,t)=>{var s=t(609),n=Symbol.for("react.element"),i=(Symbol.for("react.fragment"),Object.prototype.hasOwnProperty),r=s.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,c={key:!0,ref:!0,__self:!0,__source:!0};function a(e,o,t){var s,a={},d=null,l=null;for(s in void 0!==t&&(d=""+t),void 0!==o.key&&(d=""+o.key),void 0!==o.ref&&(l=o.ref),o)i.call(o,s)&&!c.hasOwnProperty(s)&&(a[s]=o[s]);if(e&&e.defaultProps)for(s in o=e.defaultProps)void 0===a[s]&&(a[s]=o[s]);return{$$typeof:n,type:e,key:d,ref:l,props:a,_owner:r.current}}o.jsx=a,o.jsxs=a},175:(e,o,t)=>{var s=t(609),n=t(338);const i=window.wp.components,r=window.wp.primitives;var c=t(848);const a=(0,c.jsx)(r.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,c.jsx)(r.Path,{d:"M12 13.06l3.712 3.713 1.061-1.06L13.061 12l3.712-3.712-1.06-1.06L12 10.938 8.288 7.227l-1.061 1.06L10.939 12l-3.712 3.712 1.06 1.061L12 13.061z"})}),d=window.wp.i18n,l=window.wp.apiFetch;var m=t.n(l);const u=({imagesPath:e,redirectUrl:o,data:t,dismissOption:n,isSubscriptionsListing:r})=>{const[l,u]=(0,s.useState)(r),p=(0,s.useRef)(!1),{headerImage:w,heading:b,description:_,primaryButton:g,secondaryButton:h}=t,f=()=>{if(p.current)return;const e="?page=wc-admin"===window.location.search;u(e)};(0,s.useEffect)(()=>{if(r)return;const{pushState:e,replaceState:o}=window.history;window.history.pushState=function(...o){e.apply(window.history,o),window.dispatchEvent(new Event("pushState"))},window.history.replaceState=function(...e){o.apply(window.history,e),window.dispatchEvent(new Event("replaceState"))},window.addEventListener("popstate",f),window.addEventListener("replaceState",f),window.addEventListener("pushState",f),f()},[]);const v=e=>{var t;t=n,m()({path:`/wc/v3/subscriptions/settings/${t}`,method:"post",data:{value:true}}),p.current=!0,"done-btn"!==e?(u(!1),window.removeEventListener("popstate",f),window.removeEventListener("replaceState",f),window.removeEventListener("pushState",f)):window.location.href=o};return l?(0,c.jsx)("div",{className:"woocommerce-subscriptions-announcement",children:(0,c.jsx)("div",{className:"woocommerce-subscriptions-announcement__container",children:(0,c.jsxs)(i.Card,{className:"woocommerce-tour-kit-step",elevation:2,children:[(0,c.jsx)(i.CardHeader,{isBorderless:!0,size:"small",className:"woocommerce-tour-kit-step__header",children:(0,c.jsx)(i.Flex,{className:"woocommerce-tour-kit-step-controls",justify:"flex-end",children:(0,c.jsx)(i.Button,{className:"woocommerce-tour-kit-step-controls__close-btn",label:(0,d.__)("Close","woocommerce-subscriptions"),icon:(0,c.jsx)(i.Icon,{icon:a,viewBox:"6 4 12 14"}),iconSize:16,onClick:()=>v("close-btn")})})}),w&&(0,c.jsx)(i.CardMedia,{className:"woocommerce-tour-kit-step__header-image",children:(0,c.jsx)("img",{src:e+"/"+w,alt:(0,d.__)("Step image","woocommerce-subscriptions")})}),(0,c.jsxs)(i.CardBody,{className:"woocommerce-tour-kit-step__body",size:"small",children:[(0,c.jsx)("h2",{className:"woocommerce-tour-kit-step__heading",children:b}),(0,c.jsx)("p",{className:"woocommerce-tour-kit-step__description",dangerouslySetInnerHTML:{__html:_}})]}),(0,c.jsx)(i.CardFooter,{isBorderless:!0,size:"small",children:(0,c.jsx)("div",{className:"woocommerce-tour-kit-step-navigation",children:(0,c.jsxs)("div",{children:[(0,c.jsx)(i.Button,{className:"woocommerce-tour-kit-step-navigation__back-btn",variant:"tertiary",onClick:()=>v("close-btn"),children:h.text||(0,d.__)("Close","woocommerce-subscriptions")}),(0,c.jsx)(i.Button,{className:"woocommerce-tour-kit-step-navigation__next-btn",variant:"primary",disabled:g.isDisabled,onClick:()=>v("done-btn"),children:g.text||(0,d.__)("Done","woocommerce-subscriptions")})]})})})]})})}):null};if(window?.wcsDownloadsSettings?.isStandaloneDownloadsEnabled){const{imagesPath:e,pluginsUrl:o,isSubscriptionsListing:t,isStandaloneDownloadsEnabled:s}=window.wcsDownloadsSettings,i=(0,n.H)(document.getElementById("wcs-downloads-welcome-announcement-root")),r=(()=>{const e=(0,d.__)("WooCommerce Subscription Downloads is now part of WooCommerce Subscriptions","woocommerce-subscriptions"),o=`${(0,d.__)("No separate extension required! WooCommerce Subscriptions now allows you to include simple and variable products with your subscriptions. And to make it easier, you can now configure which simple and variable products to include when creating a subscription product.","woocommerce-subscriptions")}\n\t\t
\n\t\t${(0,d.__)("The WooCommerce Subscription Downloads extension can now be disabled via Plugins.","woocommerce-subscriptions")}`,t=(0,d.__)("Go to plugins","woocommerce-subscriptions");return{name:"gifting-announcement",heading:e,headerImage:"gifting-modal-icon.svg",description:o,secondaryButton:{text:(0,d.__)("Maybe later","woocommerce-subscriptions")},primaryButton:{text:t}}})();i.render((0,c.jsx)(u,{imagesPath:e,data:r,dismissOption:"woocommerce_subscriptions_downloads_is_welcome_announcement_dismissed",redirectUrl:o,isSubscriptionsListing:!!t}))}const p=(0,n.H)(document.getElementById("wcs-gifting-welcome-announcement-root")),w=(e=>{const o=e?(0,d.__)("Gifting is now part of WooCommerce Subscriptions","woocommerce-subscriptions"):(0,d.__)("Introducing subscription gifting","woocommerce-subscriptions"),t=e?`${(0,d.__)("No separate extension needed! The built-in gifting feature is fully compatible with product, cart and checkout blocks, plus you can now choose which subscription products can be gifted.","woocommerce-subscriptions")}\n\t\t\t
\n\t\t\t${(0,d.__)("The Gifting for WooCommerce Subscriptions extension can now be disabled via Plugins.","woocommerce-subscriptions")}`:(0,d.__)("Let your shoppers purchase subscriptions as gifts using the new built-in gifting feature in WooCommerce Subscriptions. It works seamlessly with product, cart and checkout blocks, and can be enabled storewide or managed per product.","woocommerce-subscriptions"),s=e?(0,d.__)("Go to plugins","woocommerce-subscriptions"):(0,d.__)("Set up gifting","woocommerce-subscriptions");return{name:"gifting-announcement",heading:o,headerImage:"gifting-modal-icon.svg",description:t,secondaryButton:{text:(0,d.__)("Maybe later","woocommerce-subscriptions")},primaryButton:{text:s}}})(window.wcsGiftingSettings.isStandaloneGiftingEnabled),{imagesPath:b,pluginsUrl:_,subscriptionsUrl:g,isStandaloneGiftingEnabled:h,isSubscriptionsListing:f}=window.wcsGiftingSettings;p.render((0,c.jsx)(u,{imagesPath:b,data:w,dismissOption:"woocommerce_subscriptions_gifting_is_welcome_announcement_dismissed",redirectUrl:h?_:g,isSubscriptionsListing:!!f}))},338:(e,o,t)=>{var s=t(795);o.H=s.createRoot,s.hydrateRoot},609:e=>{e.exports=window.React},795:e=>{e.exports=window.ReactDOM},848:(e,o,t)=>{e.exports=t(20)}},t={};function s(e){var n=t[e];if(void 0!==n)return n.exports;var i=t[e]={exports:{}};return o[e](i,i.exports,s),i.exports}s.m=o,e=[],s.O=(o,t,n,i)=>{if(!t){var r=1/0;for(l=0;l=i)&&Object.keys(s.O).every(e=>s.O[e](t[a]))?t.splice(a--,1):(c=!1,i0&&e[l-1][2]>i;l--)e[l]=e[l-1];e[l]=[t,n,i]},s.n=e=>{var o=e&&e.__esModule?()=>e.default:()=>e;return s.d(o,{a:o}),o},s.d=(e,o)=>{for(var t in o)s.o(o,t)&&!s.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:o[t]})},s.o=(e,o)=>Object.prototype.hasOwnProperty.call(e,o),(()=>{var e={884:0,15:0};s.O.j=o=>0===e[o];var o=(o,t)=>{var n,i,[r,c,a]=t,d=0;if(r.some(o=>0!==e[o])){for(n in c)s.o(c,n)&&(s.m[n]=c[n]);if(a)var l=a(s)}for(o&&o(t);ds(175));n=s.O(n)})();
\ No newline at end of file
diff --git a/build/gifting-welcome-announcement.asset.php b/build/gifting-welcome-announcement.asset.php
deleted file mode 100644
index c9d1e03..0000000
--- a/build/gifting-welcome-announcement.asset.php
+++ /dev/null
@@ -1 +0,0 @@
- array('react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => 'b52cf873a54e347c8f9e');
diff --git a/build/gifting-welcome-announcement.js b/build/gifting-welcome-announcement.js
deleted file mode 100644
index c167892..0000000
--- a/build/gifting-welcome-announcement.js
+++ /dev/null
@@ -1 +0,0 @@
-(()=>{"use strict";var e,o={20:(e,o,t)=>{var i=t(609),n=Symbol.for("react.element"),s=(Symbol.for("react.fragment"),Object.prototype.hasOwnProperty),r=i.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,c={key:!0,ref:!0,__self:!0,__source:!0};function a(e,o,t){var i,a={},d=null,l=null;for(i in void 0!==t&&(d=""+t),void 0!==o.key&&(d=""+o.key),void 0!==o.ref&&(l=o.ref),o)s.call(o,i)&&!c.hasOwnProperty(i)&&(a[i]=o[i]);if(e&&e.defaultProps)for(i in o=e.defaultProps)void 0===a[i]&&(a[i]=o[i]);return{$$typeof:n,type:e,key:d,ref:l,props:a,_owner:r.current}}o.jsx=a,o.jsxs=a},338:(e,o,t)=>{var i=t(795);o.H=i.createRoot,i.hydrateRoot},609:e=>{e.exports=window.React},736:(e,o,t)=>{var i=t(609),n=t(338);const s=window.wp.components,r=window.wp.element,c=window.wp.primitives,a=(0,r.createElement)(c.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,r.createElement)(c.Path,{d:"M12 13.06l3.712 3.713 1.061-1.06L13.061 12l3.712-3.712-1.06-1.06L12 10.938 8.288 7.227l-1.061 1.06L10.939 12l-3.712 3.712 1.06 1.061L12 13.061z"})),d=window.wp.i18n,l=window.wp.apiFetch;var m=t.n(l),p=t(848);const u=({imagesPath:e,redirectUrl:o,data:t,dismissOption:n,isSubscriptionsListing:r})=>{const[c,l]=(0,i.useState)(r),u=(0,i.useRef)(!1),{headerImage:w,heading:h,description:_,primaryButton:b,secondaryButton:g}=t,f=()=>{if(u.current)return;const e="?page=wc-admin"===window.location.search;l(e)};(0,i.useEffect)((()=>{if(r)return;const{pushState:e,replaceState:o}=window.history;window.history.pushState=function(...o){e.apply(window.history,o),window.dispatchEvent(new Event("pushState"))},window.history.replaceState=function(...e){o.apply(window.history,e),window.dispatchEvent(new Event("replaceState"))},window.addEventListener("popstate",f),window.addEventListener("replaceState",f),window.addEventListener("pushState",f),f()}),[]);const v=e=>{var t;t=n,m()({path:`/wc/v3/subscriptions/settings/${t}`,method:"post",data:{value:true}}),u.current=!0,"done-btn"!==e?(l(!1),window.removeEventListener("popstate",f),window.removeEventListener("replaceState",f),window.removeEventListener("pushState",f)):window.location.href=o};return c?(0,p.jsx)("div",{className:"woocommerce-subscriptions-announcement",children:(0,p.jsx)("div",{className:"woocommerce-subscriptions-announcement__container",children:(0,p.jsxs)(s.Card,{className:"woocommerce-tour-kit-step",elevation:2,children:[(0,p.jsx)(s.CardHeader,{isBorderless:!0,size:"small",className:"woocommerce-tour-kit-step__header",children:(0,p.jsx)(s.Flex,{className:"woocommerce-tour-kit-step-controls",justify:"flex-end",children:(0,p.jsx)(s.Button,{className:"woocommerce-tour-kit-step-controls__close-btn",label:(0,d.__)("Close","woocommerce-subscriptions"),icon:(0,p.jsx)(s.Icon,{icon:a,viewBox:"6 4 12 14"}),iconSize:16,onClick:()=>v("close-btn")})})}),w&&(0,p.jsx)(s.CardMedia,{className:"woocommerce-tour-kit-step__header-image",children:(0,p.jsx)("img",{src:e+"/"+w,alt:(0,d.__)("Step image","woocommerce-subscriptions")})}),(0,p.jsxs)(s.CardBody,{className:"woocommerce-tour-kit-step__body",size:"small",children:[(0,p.jsx)("h2",{className:"woocommerce-tour-kit-step__heading",children:h}),(0,p.jsx)("p",{className:"woocommerce-tour-kit-step__description",dangerouslySetInnerHTML:{__html:_}})]}),(0,p.jsx)(s.CardFooter,{isBorderless:!0,size:"small",children:(0,p.jsx)("div",{className:"woocommerce-tour-kit-step-navigation",children:(0,p.jsxs)("div",{children:[(0,p.jsx)(s.Button,{className:"woocommerce-tour-kit-step-navigation__back-btn",variant:"tertiary",onClick:()=>v("close-btn"),children:g.text||(0,d.__)("Close","woocommerce-subscriptions")}),(0,p.jsx)(s.Button,{className:"woocommerce-tour-kit-step-navigation__next-btn",variant:"primary",disabled:b.isDisabled,onClick:()=>v("done-btn"),children:b.text||(0,d.__)("Done","woocommerce-subscriptions")})]})})})]})})}):null},w=(0,n.H)(document.getElementById("wcs-gifting-welcome-announcement-root")),h=(e=>{const o=e?(0,d.__)("Gifting is now part of WooCommerce Subscriptions","woocommerce-subscriptions"):(0,d.__)("Introducing subscription gifting","woocommerce-subscriptions"),t=e?`${(0,d.__)("No separate extension needed! The built-in gifting feature is fully compatible with product, cart and checkout blocks, plus you can now choose which subscription products can be gifted.","woocommerce-subscriptions")}\n\t\t\t
\n\t\t\t${(0,d.__)("The Gifting for WooCommerce Subscriptions extension can now be disabled via Plugins.","woocommerce-subscriptions")}`:(0,d.__)("Let your shoppers purchase subscriptions as gifts using the new built-in gifting feature in WooCommerce Subscriptions. It works seamlessly with product, cart and checkout blocks, and can be enabled storewide or managed per product.","woocommerce-subscriptions"),i=e?(0,d.__)("Go to plugins","woocommerce-subscriptions"):(0,d.__)("Set up gifting","woocommerce-subscriptions");return{name:"gifting-announcement",heading:o,headerImage:"gifting-modal-icon.svg",description:t,secondaryButton:{text:(0,d.__)("Maybe later","woocommerce-subscriptions")},primaryButton:{text:i}}})(window.wcsGiftingSettings.isStandaloneGiftingEnabled),{imagesPath:_,pluginsUrl:b,subscriptionsUrl:g,isStandaloneGiftingEnabled:f,isSubscriptionsListing:v}=window.wcsGiftingSettings;w.render((0,p.jsx)(u,{imagesPath:_,data:h,dismissOption:"woocommerce_subscriptions_gifting_is_welcome_announcement_dismissed",redirectUrl:f?b:g,isSubscriptionsListing:!!v}))},795:e=>{e.exports=window.ReactDOM},848:(e,o,t)=>{e.exports=t(20)}},t={};function i(e){var n=t[e];if(void 0!==n)return n.exports;var s=t[e]={exports:{}};return o[e](s,s.exports,i),s.exports}i.m=o,e=[],i.O=(o,t,n,s)=>{if(!t){var r=1/0;for(l=0;l=s)&&Object.keys(i.O).every((e=>i.O[e](t[a])))?t.splice(a--,1):(c=!1,s0&&e[l-1][2]>s;l--)e[l]=e[l-1];e[l]=[t,n,s]},i.n=e=>{var o=e&&e.__esModule?()=>e.default:()=>e;return i.d(o,{a:o}),o},i.d=(e,o)=>{for(var t in o)i.o(o,t)&&!i.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:o[t]})},i.o=(e,o)=>Object.prototype.hasOwnProperty.call(e,o),(()=>{var e={716:0,601:0};i.O.j=o=>0===e[o];var o=(o,t)=>{var n,s,[r,c,a]=t,d=0;if(r.some((o=>0!==e[o]))){for(n in c)i.o(c,n)&&(i.m[n]=c[n]);if(a)var l=a(i)}for(o&&o(t);di(736)));n=i.O(n)})();
\ No newline at end of file
diff --git a/build/index.asset.php b/build/index.asset.php
index 6edb859..6ad1087 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' => '841d6ab2fe0080f15a21');
+ array('react', 'wc-blocks-checkout', 'wc-blocks-data-store', 'wc-price-format', 'wc-settings', 'wp-data', 'wp-element', 'wp-i18n', 'wp-plugins'), 'version' => '32bfa486d902830f29c7');
diff --git a/build/index.js b/build/index.js
index 1e5456a..133e786 100644
--- a/build/index.js
+++ b/build/index.js
@@ -7,8 +7,8 @@
* translators: %1$s is the price of the product. %2$s is the separator used e.g "every" or "/",
* %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,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)(
+(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,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=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?
+(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"):
diff --git a/build/style-gifting-welcome-announcement-rtl.css b/build/style-admin-rtl.css
similarity index 100%
rename from build/style-gifting-welcome-announcement-rtl.css
rename to build/style-admin-rtl.css
diff --git a/build/style-gifting-welcome-announcement.css b/build/style-admin.css
similarity index 100%
rename from build/style-gifting-welcome-announcement.css
rename to build/style-admin.css
diff --git a/build/wcsg-blocks-integration.asset.php b/build/wcsg-blocks-integration.asset.php
index 75cd01c..789ee73 100644
--- a/build/wcsg-blocks-integration.asset.php
+++ b/build/wcsg-blocks-integration.asset.php
@@ -1 +1 @@
- array('react', 'react-dom', 'wc-blocks-checkout', 'wp-components', 'wp-data', 'wp-i18n', 'wp-plugins', 'wp-url'), 'version' => 'e7869a0a766c0f65ff8a');
+ array('react', 'react-dom', 'wc-blocks-checkout', 'wp-components', 'wp-data', 'wp-i18n', 'wp-plugins', 'wp-url'), 'version' => 'cf4dd71c0c7418b591fd');
diff --git a/build/wcsg-blocks-integration.js b/build/wcsg-blocks-integration.js
index 6c2d95c..5b58fe5 100644
--- a/build/wcsg-blocks-integration.js
+++ b/build/wcsg-blocks-integration.js
@@ -1 +1 @@
-(()=>{"use strict";var e={20:(e,t,n)=>{var o=n(609),i=Symbol.for("react.element"),c=Symbol.for("react.fragment"),r=Object.prototype.hasOwnProperty,s=o.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,a={key:!0,ref:!0,__self:!0,__source:!0};function l(e,t,n){var o,c={},l=null,u=null;for(o in void 0!==n&&(l=""+n),void 0!==t.key&&(l=""+t.key),void 0!==t.ref&&(u=t.ref),t)r.call(t,o)&&!a.hasOwnProperty(o)&&(c[o]=t[o]);if(e&&e.defaultProps)for(o in t=e.defaultProps)void 0===c[o]&&(c[o]=t[o]);return{$$typeof:i,type:e,key:l,ref:u,props:c,_owner:s.current}}t.Fragment=c,t.jsx=l,t.jsxs=l},338:(e,t,n)=>{var o=n(795);t.H=o.createRoot,o.hydrateRoot},609:e=>{e.exports=window.React},795:e=>{e.exports=window.ReactDOM},848:(e,t,n)=>{e.exports=n(20)}},t={};function n(o){var i=t[o];if(void 0!==i)return i.exports;var c=t[o]={exports:{}};return e[o](c,c.exports,n),c.exports}var o=n(338);const i=window.wp.plugins;var c=n(609);const r=window.wp.i18n,s=window.wp.components,a=window.wp.data,l=window.wp.url,u=window.wc.blocksCheckout;var d=n(848);const m=({lineItem:e,itemKey:t})=>{const[n,o]=(0,c.useState)(!1),[i,m]=(0,c.useState)(!1),[p,w]=(0,c.useState)(""),[_,f]=(0,c.useState)(!0),[g,b]=(0,c.useState)(!1),h=(0,c.useId)(),v="invalid-gifting-recipient",{setValidationErrors:k,clearValidationError:x}=(0,a.useDispatch)("wc/store/validation"),y=(0,a.useSelect)((e=>{if(!t)return null;const n=e("wc/store/cart");if(!n)return null;try{const e=n.getCartData();return e?.items?.find((e=>e.key===t))||null}catch(e){return null}}),[t]),S=(0,c.useMemo)((()=>{if(!y?.item_data)return null;const e=y.item_data.find((e=>"gifting_to_hidden"===e.name));return e?.value||null}),[y]);(0,c.useEffect)((()=>{null!==S&&(w(S),o(!0))}),[S]),(0,c.useEffect)((()=>{if(!e)return;const t=e.querySelector(".wc-block-components-product-details__gifting-to-hidden");if(!t)return;const n=t.querySelector(".wc-block-components-product-details__value");w(n?n.textContent:""),o(!0)}),[e]);const E=e=>{if(!e&&!(0,l.isEmail)(p))return f(!1),void k({[v]:{message:(0,r.__)("Please enter a valid email address","woocommerce-subscriptions"),hidden:!1}});m(!1),f(!0),x(v),(0,u.extensionCartUpdate)({namespace:"wcsg-cart",data:{recipient:!0===e?"":p,itemKey:t}}).then((()=>{m(!1)})).catch((()=>{f(!1),m(!0),k({[v]:{message:(0,r.__)("Please enter a valid email address","woocommerce-subscriptions"),hidden:!1}})}))};return(0,d.jsxs)("div",{className:"wcsg-gifting-to-container",children:[(!n||i)&&(0,d.jsx)(s.CheckboxControl,{"aria-label":window.wcSettings.wcsg_subscriptions_data.gifting_checkbox_text||(0,r.__)("This is a gift","woocommerce-subscriptions"),label:window.wcSettings.wcsg_subscriptions_data.gifting_checkbox_text||(0,r.__)("This is a gift","woocommerce-subscriptions"),checked:n,onChange:e=>{o(e),m(e)}}),n&&!i&&(0,d.jsxs)("div",{className:"wcsg-gifting-to-container-view",children:[(0,d.jsxs)("span",{title:(0,r.__)("Gifting to","woocommerce-subscriptions"),children:[(0,r.__)("Gifting to:","woocommerce-subscriptions")," ",!i&&p]}),(0,d.jsx)(s.Button,{variant:"link",onClick:()=>m(!0),children:(0,r.__)("Edit","woocommerce-subscriptions")}),(0,d.jsx)(s.Button,{variant:"link","aria-label":(0,r.__)("Remove gift recipient","woocommerce-subscriptions"),onClick:()=>(o(!1),w(""),void E(!0)),children:(0,r.__)("Remove","woocommerce-subscriptions")})]}),i&&(0,d.jsxs)("div",{className:"wcsg-gifting-to-container-editing wc-block-components-form",children:[(0,d.jsxs)("div",{className:`wc-block-components-text-input ${_?"":"has-error"} ${g||p?"is-active":""}`,children:[(0,d.jsx)("input",{type:"email",value:p,onChange:e=>w(e.target.value),onFocus:()=>{b(!0),f(!0)},onBlur:()=>{b(!1)},"aria-label":(0,r.__)("Gifting recipient","woocommerce-subscriptions"),id:h,"data-testid":"recipient-input"}),(0,d.jsx)("label",{htmlFor:h,children:(0,r.__)("Recipient's email address","woocommerce-subscriptions")})]}),(0,d.jsx)("button",{className:"wc-block-components-button wp-element-button gifting-update-button",onClick:()=>E(!1),name:"Update",children:(0,r.__)("Update","woocommerce-subscriptions")})]}),i&&!_&&(0,d.jsx)(u.ValidationInputError,{elementId:v,propertyName:v})]})},p=()=>{const{__internalSetUseShippingAsBilling:e,setEditingShippingAddress:t}=(0,a.useDispatch)("wc/store/checkout"),n=(0,a.useSelect)((e=>{const t=e("wc/store/cart");if(!t)return null;try{const e=t.getCartData();return e?.items?.find((e=>e.item_data.find((e=>"gifting_to_hidden"===e.name))))||null}catch(e){return null}}),[]);return(0,c.useEffect)((()=>{n&&(e(!1),t(!0))}),[n,e,t]),(0,d.jsx)(d.Fragment,{})};window.addEventListener("load",(function(){let e=null;function t(){const t=document.querySelectorAll(".wc-block-components-product-metadata");0!==t.length&&(t.length&&e&&e.disconnect(),t.forEach((function(e,t){const n=e?.querySelector(".wc-block-components-product-details__item-key .wc-block-components-product-details__value")?.textContent||"";if(!n)return;const i="gifting-recipient-"+t;if(!document.getElementById(i)){const t=document.createElement("div");t.className="wcsg-block-recipient-container",t.id=i,e.parentNode.insertBefore(t,e)}(0,o.H)(document.getElementById(i)).render((0,d.jsx)(m,{lineItem:e,itemKey:n}))})))}function n(){setTimeout((()=>{e=new MutationObserver(t),t(),e.observe(document.body,{childList:!0,subtree:!0})}),200)}null!==document.querySelector(".wp-block-woocommerce-checkout")?(0,i.registerPlugin)("woocommerce-checkout-gifting",{render:p,icon:"gift",title:"Checkout Gifting Features",scope:"woocommerce-checkout"}):(n(),document.addEventListener("click",(function(e){e.target.matches(".wc-block-mini-cart__button, .wc-block-mini-cart__button *")&&n()})))}))})();
\ No newline at end of file
+(()=>{"use strict";var e={20:(e,t,n)=>{var o=n(609),i=Symbol.for("react.element"),c=Symbol.for("react.fragment"),r=Object.prototype.hasOwnProperty,s=o.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,a={key:!0,ref:!0,__self:!0,__source:!0};function l(e,t,n){var o,c={},l=null,u=null;for(o in void 0!==n&&(l=""+n),void 0!==t.key&&(l=""+t.key),void 0!==t.ref&&(u=t.ref),t)r.call(t,o)&&!a.hasOwnProperty(o)&&(c[o]=t[o]);if(e&&e.defaultProps)for(o in t=e.defaultProps)void 0===c[o]&&(c[o]=t[o]);return{$$typeof:i,type:e,key:l,ref:u,props:c,_owner:s.current}}t.Fragment=c,t.jsx=l,t.jsxs=l},338:(e,t,n)=>{var o=n(795);t.H=o.createRoot,o.hydrateRoot},609:e=>{e.exports=window.React},795:e=>{e.exports=window.ReactDOM},848:(e,t,n)=>{e.exports=n(20)}},t={};function n(o){var i=t[o];if(void 0!==i)return i.exports;var c=t[o]={exports:{}};return e[o](c,c.exports,n),c.exports}var o=n(338);const i=window.wp.plugins;var c=n(609);const r=window.wp.i18n,s=window.wp.components,a=window.wp.data,l=window.wp.url,u=window.wc.blocksCheckout;var d=n(848);const m=({lineItem:e,itemKey:t})=>{const[n,o]=(0,c.useState)(!1),[i,m]=(0,c.useState)(!1),[p,w]=(0,c.useState)(""),[_,f]=(0,c.useState)(!0),[g,b]=(0,c.useState)(!1),h=(0,c.useId)(),v="invalid-gifting-recipient",{setValidationErrors:k,clearValidationError:x}=(0,a.useDispatch)("wc/store/validation"),y=(0,a.useSelect)(e=>{if(!t)return null;const n=e("wc/store/cart");if(!n)return null;try{const e=n.getCartData();return e?.items?.find(e=>e.key===t)||null}catch(e){return null}},[t]),S=(0,c.useMemo)(()=>{if(!y?.item_data)return null;const e=y.item_data.find(e=>"gifting_to_hidden"===e.name);return e?.value||null},[y]);(0,c.useEffect)(()=>{null!==S&&(w(S),o(!0))},[S]),(0,c.useEffect)(()=>{if(!e)return;const t=e.querySelector(".wc-block-components-product-details__gifting-to-hidden");if(!t)return;const n=t.querySelector(".wc-block-components-product-details__value");w(n?n.textContent:""),o(!0)},[e]);const E=e=>{if(!e&&!(0,l.isEmail)(p))return f(!1),void k({[v]:{message:(0,r.__)("Please enter a valid email address","woocommerce-subscriptions"),hidden:!1}});m(!1),f(!0),x(v),(0,u.extensionCartUpdate)({namespace:"wcsg-cart",data:{recipient:!0===e?"":p,itemKey:t}}).then(()=>{m(!1)}).catch(()=>{f(!1),m(!0),k({[v]:{message:(0,r.__)("Please enter a valid email address","woocommerce-subscriptions"),hidden:!1}})})};return(0,d.jsxs)("div",{className:"wcsg-gifting-to-container",children:[(!n||i)&&(0,d.jsx)(s.CheckboxControl,{"aria-label":window.wcSettings.wcsg_subscriptions_data.gifting_checkbox_text||(0,r.__)("This is a gift","woocommerce-subscriptions"),label:window.wcSettings.wcsg_subscriptions_data.gifting_checkbox_text||(0,r.__)("This is a gift","woocommerce-subscriptions"),checked:n,onChange:e=>{o(e),m(e)}}),n&&!i&&(0,d.jsxs)("div",{className:"wcsg-gifting-to-container-view",children:[(0,d.jsxs)("span",{title:(0,r.__)("Gifting to","woocommerce-subscriptions"),children:[(0,r.__)("Gifting to:","woocommerce-subscriptions")," ",!i&&p]}),(0,d.jsx)(s.Button,{variant:"link",onClick:()=>m(!0),children:(0,r.__)("Edit","woocommerce-subscriptions")}),(0,d.jsx)(s.Button,{variant:"link","aria-label":(0,r.__)("Remove gift recipient","woocommerce-subscriptions"),onClick:()=>(o(!1),w(""),void E(!0)),children:(0,r.__)("Remove","woocommerce-subscriptions")})]}),i&&(0,d.jsxs)("div",{className:"wcsg-gifting-to-container-editing wc-block-components-form",children:[(0,d.jsxs)("div",{className:`wc-block-components-text-input ${_?"":"has-error"} ${g||p?"is-active":""}`,children:[(0,d.jsx)("input",{type:"email",value:p,onChange:e=>w(e.target.value),onFocus:()=>{b(!0),f(!0)},onBlur:()=>{b(!1)},"aria-label":(0,r.__)("Gifting recipient","woocommerce-subscriptions"),id:h,"data-testid":"recipient-input"}),(0,d.jsx)("label",{htmlFor:h,children:(0,r.__)("Recipient's email address","woocommerce-subscriptions")})]}),(0,d.jsx)("button",{className:"wc-block-components-button wp-element-button gifting-update-button",onClick:()=>E(!1),name:"Update",children:(0,r.__)("Update","woocommerce-subscriptions")})]}),i&&!_&&(0,d.jsx)(u.ValidationInputError,{elementId:v,propertyName:v})]})},p=()=>{const{__internalSetUseShippingAsBilling:e,setEditingShippingAddress:t}=(0,a.useDispatch)("wc/store/checkout"),n=(0,a.useSelect)(e=>{const t=e("wc/store/cart");if(!t)return null;try{const e=t.getCartData();return e?.items?.find(e=>e.item_data.find(e=>"gifting_to_hidden"===e.name))||null}catch(e){return null}},[]);return(0,c.useEffect)(()=>{n&&(e(!1),t(!0))},[n,e,t]),(0,d.jsx)(d.Fragment,{})};window.addEventListener("load",function(){let e=null;function t(){const t=document.querySelectorAll(".wc-block-components-product-metadata");0!==t.length&&(t.length&&e&&e.disconnect(),t.forEach(function(e,t){const n=e?.querySelector(".wc-block-components-product-details__item-key .wc-block-components-product-details__value")?.textContent||"";if(!n)return;const i="gifting-recipient-"+t;if(!document.getElementById(i)){const t=document.createElement("div");t.className="wcsg-block-recipient-container",t.id=i,e.parentNode.insertBefore(t,e)}(0,o.H)(document.getElementById(i)).render((0,d.jsx)(m,{lineItem:e,itemKey:n}))}))}function n(){setTimeout(()=>{e=new MutationObserver(t),t(),e.observe(document.body,{childList:!0,subtree:!0})},200)}null!==document.querySelector(".wp-block-woocommerce-checkout")?(0,i.registerPlugin)("woocommerce-checkout-gifting",{render:p,icon:"gift",title:"Checkout Gifting Features",scope:"woocommerce-checkout"}):(n(),document.addEventListener("click",function(e){e.target.matches(".wc-block-mini-cart__button, .wc-block-mini-cart__button *")&&n()}))})})();
\ No newline at end of file
diff --git a/changelog.txt b/changelog.txt
index b1a8246..e700a9f 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,11 +1,17 @@
*** WooCommerce Subscriptions Changelog ***
+2026-01-06 - version 8.3.0
+* Add: Support for ajax add-to-cart flows from the single product page, when upgrading or downgrading subscriptions.
+* Add: Enhanced functionality from the WooCommerce Subscription Downloads plugin has now been integrated directly into WooCommerce Subscriptions.
+* Fix: Ensured the subject line for the "Customer Renewal Invoice" email correctly updates.
+
2025-12-22 - version 8.2.1
* Fix: Subscription pricing strings now correctly display singular and plural billing intervals like "$10 / month" and "$10 every 3 months" in cart and checkout totals.
* Fix: Display all recurring coupon names in the totals section of classic cart and checkout when multiple recurring coupons are applied.
* Fix: Payment method selector options on Subscriptions list page are now properly displaying the payment method name for gateways providing multiple payment methods.
* Fix: PayPal Standard plugin IPN renewal orders not being created for failed payment retries.
* Fix: Prevent fatal errors on shop pages when special characters like % are used in a product name.
+* Fix: Prevent an email validation error from briefly and inadvertently flashing on the screen when the "this is a gift" checkbox is used.
2025-12-10 - version 8.2.0
* Fix: Various accessibility issues.
diff --git a/includes/admin/class-wcs-admin-assets.php b/includes/admin/class-wcs-admin-assets.php
new file mode 100644
index 0000000..a91b501
--- /dev/null
+++ b/includes/admin/class-wcs-admin-assets.php
@@ -0,0 +1,59 @@
+get_plugin_directory( 'build/admin.asset.php' );
+ $script_asset = file_exists( $script_asset_path )
+ ? require $script_asset_path
+ : array(
+ 'dependencies' => array(
+ 'react',
+ 'wc-blocks-checkout',
+ 'wc-price-format',
+ 'wc-settings',
+ 'wp-element',
+ 'wp-i18n',
+ 'wp-plugins',
+ ),
+ 'version' => WC_Subscriptions::$version,
+ );
+
+ wp_enqueue_script(
+ 'wcs-admin',
+ plugins_url( '/build/admin.js', WC_Subscriptions::$plugin_file ),
+ $script_asset['dependencies'],
+ $script_asset['version'],
+ true
+ );
+
+ wp_enqueue_style(
+ 'wcs-admin',
+ plugins_url( '/build/style-admin.css', WC_Subscriptions::$plugin_file ),
+ array( 'wp-components' ),
+ $script_asset['version']
+ );
+
+ wp_set_script_translations(
+ 'wcs-admin',
+ 'woocommerce-subscriptions',
+ plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'languages'
+ );
+ }
+}
diff --git a/includes/api/class-wc-rest-subscriptions-settings-option-controller.php b/includes/api/class-wc-rest-subscriptions-settings-option-controller.php
index 598969e..f0cbcd0 100644
--- a/includes/api/class-wc-rest-subscriptions-settings-option-controller.php
+++ b/includes/api/class-wc-rest-subscriptions-settings-option-controller.php
@@ -24,6 +24,7 @@ class WC_REST_Subscriptions_Settings_Option_Controller extends WP_REST_Controlle
*/
private const ALLOWED_OPTIONS = [
'woocommerce_subscriptions_gifting_is_welcome_announcement_dismissed',
+ 'woocommerce_subscriptions_downloads_is_welcome_announcement_dismissed',
];
/**
diff --git a/includes/class-wc-subscriptions-plugin.php b/includes/class-wc-subscriptions-plugin.php
index 66f0b7c..dec8d1d 100644
--- a/includes/class-wc-subscriptions-plugin.php
+++ b/includes/class-wc-subscriptions-plugin.php
@@ -34,6 +34,7 @@ class WC_Subscriptions_Plugin extends WC_Subscriptions_Core_Plugin {
WCS_Call_To_Action_Button_Text_Manager::init();
WCS_Subscriber_Role_Manager::init();
WCS_Upgrade_Notice_Manager::init();
+ WCS_Admin_Assets::init();
$tracks_events = new WC_Tracks_Events();
$tracks_events->setup();
@@ -300,10 +301,16 @@ class WC_Subscriptions_Plugin extends WC_Subscriptions_Core_Plugin {
*/
public function init_downloads() {
if (
- ! defined( 'WCS_ALLOW_SUBSCRIPTION_DOWNLOADS' )
- || $this->is_plugin_being_activated( 'woocommerce-subscription-downloads' )
+ $this->is_plugin_being_activated( 'woocommerce-subscription-downloads' )
|| class_exists( WC_Subscription_Downloads::class, false )
) {
+ if ( class_exists( WC_Subscription_Downloads::class, false ) ) {
+ // Will show the welcome announcement if the standalone plugin is active and the welcome announcement has not been dismissed.
+ if ( ! WC_Subscription_Downloads_Admin_Welcome_Announcement::is_welcome_announcement_dismissed() ) {
+ WC_Subscription_Downloads_Admin_Welcome_Announcement::init();
+ }
+ }
+
return;
}
diff --git a/includes/class-wcs-autoloader.php b/includes/class-wcs-autoloader.php
index 5bbadf0..4a1119e 100644
--- a/includes/class-wcs-autoloader.php
+++ b/includes/class-wcs-autoloader.php
@@ -45,6 +45,7 @@ class WCS_Autoloader extends WCS_Core_Autoloader {
'wcs_webhooks' => true,
'wcs_auth' => true,
'wcs_upgrade_notice_manager' => true,
+ 'wcs_admin_assets' => true,
'wc_subscriptions_cli' => true,
);
diff --git a/includes/core/class-wcs-limiter.php b/includes/core/class-wcs-limiter.php
index b3f2590..ed1bde1 100644
--- a/includes/core/class-wcs-limiter.php
+++ b/includes/core/class-wcs-limiter.php
@@ -57,6 +57,43 @@ class WCS_Limiter {
do_action( 'woocommerce_subscriptions_product_options_advanced' );
}
+ /**
+ * Checks if the session contains a renewal for a given product.
+ * Used for the pay for order flow.
+ *
+ * @param WC_Product $product The product to check.
+ * @return bool
+ */
+ private static function session_contains_renewal( $product ) {
+ if ( ! empty( WC()->session->cart ) ) {
+ foreach ( WC()->session->cart as $cart_item_key => $cart_item ) {
+ if ( (int) $product->get_id() === (int) $cart_item['product_id'] && isset( $cart_item['subscription_renewal'] ) ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the session contains a resubscribe for a given product.
+ * Used for the pay for order flow with limited subscriptions products.
+ *
+ * @param WC_Product $product The product to check.
+ * @return bool
+ */
+ private static function session_contains_resubscribe( $product ) {
+ if ( ! empty( WC()->session->cart ) ) {
+ foreach ( WC()->session->cart as $cart_item_key => $cart_item ) {
+ if ( (int) $product->get_id() === (int) $cart_item['product_id'] && isset( $cart_item['subscription_resubscribe'] ) ) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
/**
* Canonical is_purchasable method to be called by product classes.
*
@@ -68,73 +105,62 @@ class WCS_Limiter {
*/
public static function is_purchasable( $purchasable, $product ) {
- // Prevents making a non purchasable product purchasable again.
- // This can happen if the product is disabled and limited and the customer is trying to renew the subscription for example.
- if ( ! $purchasable ) {
- return $purchasable;
+ // Check if product is private (for variations, also check parent product)
+ $is_private_product = 'private' === $product->get_status();
+ if ( $product->get_parent_id() > 0 ) {
+ $parent_product = wc_get_product( $product->get_parent_id() );
+ if ( $parent_product ) {
+ $is_private_product = 'private' === $parent_product->get_status();
+ }
}
- switch ( $product->get_type() ) {
- case 'subscription':
- case 'variable-subscription':
- // Checks if the product is limited.
- if ( false === self::is_product_limited( $purchasable, $product ) ) {
- // Product is limited, so it is not purchasable.
+ // Checks limits for variable subscription products.
+ if ( $product->get_type() === 'subscription_variation' && isset( $parent_product ) ) {
+
+ if ( $is_private_product ) {
+ $purchasable = false;
+
+ // Forces product to be available when processing a renewal order. Allowing people to renew private products.
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce validation is not required for this context.
+ if ( self::is_paying_for_failed_renewal_order( $parent_product ) || isset( $_GET['subscription_renewal'] ) || wcs_cart_contains_renewal() || self::session_contains_renewal( $parent_product ) ) {
+ $purchasable = true;
+ }
+ }
+
+ if ( 'no' !== wcs_get_product_limitation( $parent_product ) && ( ! empty( WC()->cart->cart_contents ) || self::session_contains_resubscribe( $parent_product ) ) && ! wcs_is_order_received_page() && ! wcs_is_paypal_api_page() ) {
+ // When mixed checkout is disabled, the variation is replaceable.
+ if ( 'yes' === get_option( WC_Subscriptions_Admin::$option_prefix . '_multiple_purchase', 'no' ) ) {
+ foreach ( WC()->cart->cart_contents as $cart_item ) {
+ // If the variable product is limited, it can't be purchased if it is the same variation
+ if ( $product->get_parent_id() === $cart_item['data']->get_parent_id() && $product->get_id() !== $cart_item['data']->get_id() ) {
+ $purchasable = false;
+ break;
+ }
+ }
+ }
+ }
+ } else { // Checks limits for simple subscription products.
+ if ( $is_private_product ) {
+ $purchasable = false;
+
+ // Forces product to be available when processing a renewal order. Allowing people to renew private products.
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce validation is not required for this context.
+ if ( self::is_paying_for_failed_renewal_order( $product ) || isset( $_GET['subscription_renewal'] ) || wcs_cart_contains_renewal() || self::session_contains_renewal( $product ) ) {
+ $purchasable = true;
+ }
+ }
+
+ // This actually means the product is limited when returning false.
+ if ( false === self::is_product_limited( $purchasable, $product ) ) {
+ $resubscribe_cart_item = wcs_cart_contains_resubscribe();
+ // Allows the product to be resubscribed but not purchased again.
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce validation is not required for this context.
+ if ( empty( $_GET['resubscribe'] ) && false === $resubscribe_cart_item && false === self::session_contains_resubscribe( $product ) ) {
$purchasable = false;
-
- // Unless it's resubscribing, renewing or restoring cart from session.
- $resubscribe_cart_item = wcs_cart_contains_resubscribe();
-
- // Resubscribe logic
- // phpcs:ignore WordPress.Security.NonceVerification.Recommended
- if ( ! empty( $_GET['resubscribe'] ) || false !== $resubscribe_cart_item ) {
- // phpcs:ignore WordPress.Security.NonceVerification.Recommended
- $subscription_id = ( isset( $_GET['resubscribe'] ) ) ? absint( $_GET['resubscribe'] ) : $resubscribe_cart_item['subscription_resubscribe']['subscription_id'];
- $subscription = wcs_get_subscription( $subscription_id );
-
- if ( $subscription && $subscription->has_product( $product->get_id() ) && wcs_can_user_resubscribe_to( $subscription ) ) {
- $purchasable = true;
- }
-
- // Renewal logic
- } elseif (
- // phpcs:ignore WordPress.Security.NonceVerification.Recommended
- isset( $_GET['subscription_renewal'] ) ||
- wcs_cart_contains_renewal()
- ) {
- $purchasable = true;
-
- // Restoring cart from session, so need to check the cart in the session (wcs_cart_contains_renewal() only checks the cart).
- } elseif ( ! empty( WC()->session->cart ) ) {
- foreach ( WC()->session->cart as $cart_item_key => $cart_item ) {
- if ( (int) $product->get_id() === (int) $cart_item['product_id'] && ( isset( $cart_item['subscription_renewal'] ) || isset( $cart_item['subscription_resubscribe'] ) ) ) {
- $purchasable = true;
- break;
- }
- }
- }
}
- break;
- case 'subscription_variation':
- $variable_product = wc_get_product( $product->get_parent_id() );
-
- if ( 'no' != wcs_get_product_limitation( $variable_product ) && ! empty( WC()->cart->cart_contents ) && ! wcs_is_order_received_page() && ! wcs_is_paypal_api_page() ) {
-
- // When mixed checkout is disabled, the variation is replaceable
- if ( 'no' === get_option( WC_Subscriptions_Admin::$option_prefix . '_multiple_purchase', 'no' ) ) {
- $purchasable = true;
- } else { // When mixed checkout is enabled
- foreach ( WC()->cart->cart_contents as $cart_item ) {
- // If the variable product is limited, it can't be purchased if it is the same variation
- if ( $product->get_parent_id() === $cart_item['data']->get_parent_id() && $product->get_id() !== $cart_item['data']->get_id() ) {
- $purchasable = false;
- break;
- }
- }
- }
- }
- break;
+ }
}
+
return $purchasable;
}
@@ -302,12 +328,73 @@ class WCS_Limiter {
return self::$order_awaiting_payment_for_product[ $product_id ];
}
+ /**
+ * Check if we're currently paying for a failed renewal order containing the product.
+ *
+ * @since 8.3.0 - Migrated from WooCommerce Subscriptions v2.1.0
+ * @param WC_Product $product The product to check.
+ * @return bool
+ */
+ protected static function is_paying_for_failed_renewal_order( $product ) {
+ global $wp;
+
+ // Check if we're on the pay for order page
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ if ( ! isset( $_GET['pay_for_order'] ) || ! isset( $_GET['key'] ) || ! isset( $wp->query_vars['order-pay'] ) ) {
+ // Also check if cart contains a failed renewal order payment
+ $failed_renewal_cart_item = wcs_cart_contains_failed_renewal_order_payment();
+ if ( false !== $failed_renewal_cart_item ) {
+ $cart_item_product_id = isset( $failed_renewal_cart_item['variation_id'] ) && $failed_renewal_cart_item['variation_id'] > 0
+ ? $failed_renewal_cart_item['variation_id']
+ : $failed_renewal_cart_item['product_id'];
+ // Check both the product ID and parent product ID (for variations)
+ if ( (int) $product->get_id() === (int) $cart_item_product_id || (int) $product->get_id() === (int) $failed_renewal_cart_item['product_id'] ) {
+ return true;
+ }
+ // Also check if product is a variation and matches the parent
+ if ( $product->get_parent_id() > 0 && (int) $product->get_parent_id() === (int) $failed_renewal_cart_item['product_id'] ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $order_key = isset( $_GET['key'] ) ? wc_clean( wp_unslash( $_GET['key'] ) ) : '';
+ $order_id = isset( $wp->query_vars['order-pay'] ) ? $wp->query_vars['order-pay'] : 0;
+ $order = wc_get_order( absint( $order_id ) );
+
+ if ( ! $order || $order->get_order_key() !== $order_key ) {
+ return false;
+ }
+
+ // Check if order is a failed renewal order
+ if ( ! $order->has_status( 'failed' ) && ! wcs_order_contains_renewal( $order ) ) {
+ return false;
+ }
+
+ // Check if the order contains the product
+ foreach ( $order->get_items() as $item ) {
+ $item_product_id = isset( $item['variation_id'] ) && $item['variation_id'] > 0 ? $item['variation_id'] : $item['product_id'];
+ // Check both the product ID and parent product ID (for variations)
+ if ( (int) $product->get_id() === (int) $item_product_id || (int) $product->get_id() === (int) $item['product_id'] ) {
+ return true;
+ }
+ // Also check if product is a variation and matches the parent
+ if ( $product->get_parent_id() > 0 && (int) $product->get_parent_id() === (int) $item['product_id'] ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
/**
* Filters the order statuses that enable the order again button and functionality.
*
* This function will return no statuses if the order contains non purchasable or limited products.
*
- * @since 1.0.0 - Migrated from WooCommerce Subscriptions v3.0.2
+ * @since 8.3.0 - Migrated from WooCommerce Subscriptions v3.0.2
*
* @param array $statuses The order statuses that enable the order again button.
* @return array $statuses An empty array if the order contains limited products, otherwise the default statuses are returned.
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 38f9589..9c9c5a2 100644
--- a/includes/core/emails/class-wcs-email-customer-renewal-invoice.php
+++ b/includes/core/emails/class-wcs-email-customer-renewal-invoice.php
@@ -124,7 +124,7 @@ class WCS_Email_Customer_Renewal_Invoice extends WC_Email_Customer_Invoice {
* @return string
*/
function get_subject() {
- return apply_filters( 'woocommerce_subscriptions_email_subject_new_renewal_order', parent::get_subject(), $this->object );
+ return apply_filters( 'woocommerce_subscriptions_email_subject_new_renewal_order', $this->format_string( $this->get_option( 'subject', $this->get_default_subject() ) ), $this->object );
}
/**
@@ -134,7 +134,7 @@ class WCS_Email_Customer_Renewal_Invoice extends WC_Email_Customer_Invoice {
* @return string
*/
function get_heading() {
- return apply_filters( 'woocommerce_email_heading_customer_renewal_order', parent::get_heading(), $this->object );
+ return apply_filters( 'woocommerce_email_heading_customer_renewal_order', $this->format_string( $this->get_option( 'heading', $this->get_default_heading() ) ), $this->object );
}
/**
diff --git a/includes/core/upgrades/class-wc-subscriptions-upgrader.php b/includes/core/upgrades/class-wc-subscriptions-upgrader.php
index 737b4ff..14b37c8 100644
--- a/includes/core/upgrades/class-wc-subscriptions-upgrader.php
+++ b/includes/core/upgrades/class-wc-subscriptions-upgrader.php
@@ -227,9 +227,8 @@ class WC_Subscriptions_Upgrader {
WCS_Plugin_Upgrade_7_8_0::check_gifting_plugin_is_enabled();
}
- if ( false && version_compare( self::$stored_plugin_version, '8.2.0', '<' ) ) {
- // TODO: remove false from the above conditional, once we are ready to make subscription downloads functionality available.
- WCS_Plugin_Upgrade_8_1_0::check_downloads_plugin_is_enabled();
+ if ( version_compare( self::$stored_plugin_version, '8.3.0', '<' ) ) {
+ WCS_Plugin_Upgrade_8_3_0::check_downloads_plugin_is_enabled();
}
}
diff --git a/includes/core/upgrades/class-wcs-plugin-upgrade-8-1-0.php b/includes/core/upgrades/class-wcs-plugin-upgrade-8-3-0.php
similarity index 96%
rename from includes/core/upgrades/class-wcs-plugin-upgrade-8-1-0.php
rename to includes/core/upgrades/class-wcs-plugin-upgrade-8-3-0.php
index 73af66c..d413f99 100644
--- a/includes/core/upgrades/class-wcs-plugin-upgrade-8-1-0.php
+++ b/includes/core/upgrades/class-wcs-plugin-upgrade-8-3-0.php
@@ -7,7 +7,7 @@ if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
-class WCS_Plugin_Upgrade_8_1_0 {
+class WCS_Plugin_Upgrade_8_3_0 {
/**
* Check if the Gifting plugin is enabled and update the settings.
diff --git a/includes/downloads/class-wc-subscription-downloads-admin-welcome-announcement.php b/includes/downloads/class-wc-subscription-downloads-admin-welcome-announcement.php
new file mode 100644
index 0000000..34797ba
--- /dev/null
+++ b/includes/downloads/class-wc-subscription-downloads-admin-welcome-announcement.php
@@ -0,0 +1,93 @@
+ plugins_url( '/assets/images', WC_Subscriptions::$plugin_file ),
+ 'pluginsUrl' => admin_url( 'plugins.php' ),
+ 'subscriptionsUrl' => WC_Subscriptions_Admin::settings_tab_url() . '#woocommerce_subscriptions_downloads_enable',
+ 'isStandaloneDownloadsEnabled' => is_plugin_active( 'woocommerce-subscription-downloads/woocommerce-subscription-downloads.php' ),
+ 'isSubscriptionsListing' => 'woocommerce_page_wc-orders--shop_subscription' === $screen->id,
+ )
+ );
+ }
+
+ /**
+ * Output the tour HTML in the admin footer
+ */
+ public static function output_tour() {
+ if ( ! self::is_woocommerce_admin_or_subscriptions_listing() ) {
+ return;
+ }
+
+ // Add a div for the tour to be rendered into
+ echo '';
+ }
+
+ /**
+ * Checks if the welcome tour has been dismissed.
+ *
+ * @return bool
+ */
+ public static function is_welcome_announcement_dismissed() {
+ return '1' === get_option(
+ 'woocommerce_subscriptions_downloads_is_welcome_announcement_dismissed',
+ ''
+ );
+ }
+
+ /**
+ * Checks if the current screen is WooCommerce Admin or subscriptions listing.
+ *
+ * @return bool
+ */
+ private static function is_woocommerce_admin_or_subscriptions_listing() {
+ $screen = get_current_screen();
+
+ if ( ! $screen ) {
+ return false;
+ }
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $action_param = isset( $_GET['action'] ) ? wc_clean( wp_unslash( $_GET['action'] ) ) : '';
+
+ $is_woocommerce_admin = 'woocommerce_page_wc-admin' === $screen->id;
+ $is_subscriptions_listing = 'woocommerce_page_wc-orders--shop_subscription' === $screen->id && empty( $action_param );
+
+ return $is_woocommerce_admin || $is_subscriptions_listing;
+ }
+}
diff --git a/includes/downloads/class-wc-subscription-downloads-ajax.php b/includes/downloads/class-wc-subscription-downloads-ajax.php
index b5d91d5..02c1374 100644
--- a/includes/downloads/class-wc-subscription-downloads-ajax.php
+++ b/includes/downloads/class-wc-subscription-downloads-ajax.php
@@ -17,6 +17,7 @@ class WC_Subscription_Downloads_Ajax {
*/
public function __construct() {
add_action( 'wp_ajax_wc_subscription_downloads_search', array( $this, 'search_subscriptions' ) );
+ add_action( 'wp_ajax_wc_subscription_linked_downloadable_products_search', array( $this, 'search_downloadable_products' ) );
}
/**
@@ -72,4 +73,39 @@ class WC_Subscription_Downloads_Ajax {
wp_send_json( $found_subscriptions );
}
+
+ /**
+ * Searches for downloadable products that are simple or variants.
+ *
+ * @return void
+ */
+ public function search_downloadable_products(): void {
+ $results = array();
+
+ // Prevent error noise from leaking.
+ ob_start();
+
+ if ( isset( $_GET['term'] ) && check_ajax_referer( 'search-products', 'security' ) ) {
+ $term = wc_clean( wp_unslash( $_GET['term'] ) );
+ }
+
+ if ( ! empty( $term ) ) {
+ $products = wc_get_products(
+ array(
+ 'downloadable' => true,
+ 'limit' => 100,
+ 's' => $term,
+ 'type' => array( 'simple', 'variation' ),
+ 'status' => 'any',
+ )
+ );
+
+ foreach ( $products as $product ) {
+ $results[ $product->get_id() ] = sanitize_text_field( $product->get_formatted_name() );
+ }
+ }
+
+ ob_clean();
+ wp_send_json( $results );
+ }
}
diff --git a/includes/downloads/class-wc-subscription-downloads-products.php b/includes/downloads/class-wc-subscription-downloads-products.php
index 6f6a115..1245a9f 100644
--- a/includes/downloads/class-wc-subscription-downloads-products.php
+++ b/includes/downloads/class-wc-subscription-downloads-products.php
@@ -7,52 +7,202 @@ if ( ! defined( 'ABSPATH' ) ) {
* WooCommerce Subscription Downloads Products.
*
* @package WC_Subscription_Downloads_Products
- * @category Products
- * @author WooThemes
*/
class WC_Subscription_Downloads_Products {
+ public const EDITOR_UPDATE = 'wcsubs_subscription_download_relationships';
+ public const RELATIONSHIP_DOWNLOAD_TO_SUB = 'download-to-sub';
+ public const RELATIONSHIP_VAR_DOWNLOAD_TO_SUB = 'var-download-to-sub';
+ public const RELATIONSHIP_SUB_TO_DOWNLOAD = 'sub-to-download';
+ public const RELATIONSHIP_VAR_SUB_TO_DOWNLOAD = 'var-sub-to-download';
/**
* Products actions.
*/
public function __construct() {
- add_action( 'woocommerce_product_options_downloads', array( $this, 'simple_write_panel_options' ), 10 );
+ add_action( 'woocommerce_product_options_downloads', array( $this, 'simple_write_panel_options' ) );
add_action( 'woocommerce_variation_options_download', array( $this, 'variable_write_panel_options' ), 10, 3 );
- add_action( 'admin_enqueue_scripts', array( $this, 'scripts' ) );
- add_action( 'woocommerce_process_product_meta_simple', array( $this, 'save_simple_product_data' ), 10 );
- add_action( 'woocommerce_save_product_variation', array( $this, 'save_variation_product_data' ), 10, 2 );
+ add_action( 'woocommerce_product_options_pricing', array( $this, 'subscription_product_editor_ui' ) );
+ add_action( 'woocommerce_variable_subscription_pricing', array( $this, 'variable_subscription_product_editor_ui' ), 10, 3 );
+
+ add_action( 'save_post_product', array( $this, 'handle_product_save' ) );
+ add_action( 'save_post_product_variation', array( $this, 'handle_product_variation_save' ) );
+ add_action( 'woocommerce_save_product_variation', array( $this, 'handle_product_variation_save' ) );
+ add_action( 'woocommerce_update_product', array( $this, 'handle_product_save' ) );
+ add_action( 'woocommerce_update_product_variation', array( $this, 'handle_product_variation_save' ) );
+
add_action( 'init', array( $this, 'init' ) );
}
public function init() {
- if ( version_compare( WC_VERSION, '3.0', '<' ) ) {
- add_action( 'woocommerce_duplicate_product', array( $this, 'save_subscriptions_when_duplicating_product' ), 10, 2 );
- } else {
- add_action( 'woocommerce_product_duplicate', array( $this, 'save_subscriptions_when_duplicating_product' ), 10, 2 );
+ add_action( 'woocommerce_product_duplicate', array( $this, 'save_subscriptions_when_duplicating_product' ), 10, 2 );
+ }
+
+ /**
+ * Handle product save - generic handler for all product updates.
+ *
+ * @param int $post_id Post ID.
+ *
+ * @return void
+ */
+ public function handle_product_save( $post_id ) {
+ // Bail if this is an autosave or revision.
+ if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
+ return;
+ }
+
+ if ( wp_is_post_revision( $post_id ) ) {
+ return;
+ }
+
+ $product = wc_get_product( $post_id );
+
+ if ( ! $product ) {
+ return;
+ }
+
+ $product_is_downloadable = $product->is_downloadable();
+ $product_is_subscription = $product->is_type( array( 'subscription', 'variable-subscription' ) );
+
+ // We do not allow downloadable subscription products to be linked with other subscription products; this is
+ // principally to avoid confusion (though it would be technically feasible).
+ if ( $product_is_subscription && ! $product_is_downloadable ) {
+ $this->handle_subscription_product_save( $post_id );
+ } elseif ( ! $product_is_subscription && $product_is_downloadable ) {
+ $this->handle_downloadable_product_save( $post_id );
}
}
/**
- * Product screen scripts.
+ * Handle product variation save - generic handler for all variation updates.
+ *
+ * @param int $post_id Post ID.
*
* @return void
*/
- public function scripts() {
- $screen = get_current_screen();
+ public function handle_product_variation_save( $post_id ) {
+ // Bail if this is an autosave or revision.
+ if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
+ return;
+ }
- if ( 'product' == $screen->id && version_compare( WC_VERSION, '2.3.0', '<' ) ) {
- $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+ if ( wp_is_post_revision( $post_id ) ) {
+ return;
+ }
- wp_enqueue_script( 'wc_subscription_downloads_writepanel', plugins_url( 'assets/js/admin/writepanel' . $suffix . '.js', plugin_dir_path( __FILE__ ) ), array( 'ajax-chosen', 'chosen' ), WC_Subscriptions::$version, true );
+ $variation = wc_get_product( $post_id );
+ if ( ! $variation || ! $variation->is_type( 'variation' ) ) {
+ return;
+ }
- wp_localize_script(
- 'wc_subscription_downloads_writepanel',
- 'wc_subscription_downloads_product',
- array(
- 'ajax_url' => admin_url( 'admin-ajax.php' ),
- 'security' => wp_create_nonce( 'search-products' ),
- )
- );
+ // Handle downloadable variations (they link TO subscriptions).
+ if ( $variation->is_downloadable() ) {
+ $this->handle_downloadable_product_save( $post_id );
+ }
+
+ // Handle subscription variations (they link TO downloadable products).
+ $parent = wc_get_product( $variation->get_parent_id() );
+
+ if ( $parent && $parent->is_type( 'variable-subscription' ) ) {
+ $this->handle_subscription_product_save( $post_id );
+ }
+ }
+
+ /**
+ * Handle save for downloadable products (simple or variation).
+ * These products link TO subscription products.
+ *
+ * @param int $product_id Product or variation ID.
+ *
+ * @return void
+ */
+ private function handle_downloadable_product_save( $product_id ) {
+ $product = wc_get_product( $product_id );
+
+ if ( ! $product ) {
+ return;
+ }
+
+ // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+ if (
+ isset( $_POST[ self::RELATIONSHIP_VAR_DOWNLOAD_TO_SUB . $product_id ] )
+ && wp_verify_nonce( $_POST[ self::RELATIONSHIP_VAR_DOWNLOAD_TO_SUB . $product_id ], self::EDITOR_UPDATE )
+ ) {
+ $subscription_ids = wc_clean( wp_unslash( $_POST['_variable_subscription_downloads_ids'][ $product_id ] ?? array() ) );
+ $subscription_ids = array_filter( (array) $subscription_ids );
+ $this->update_subscription_downloads( $product_id, $subscription_ids );
+ }
+
+ if (
+ isset( $_POST[ self::RELATIONSHIP_DOWNLOAD_TO_SUB ] )
+ && wp_verify_nonce( $_POST[ self::RELATIONSHIP_DOWNLOAD_TO_SUB ], self::EDITOR_UPDATE )
+ ) {
+ $subscription_ids = wc_clean( wp_unslash( $_POST['_subscription_downloads_ids'] ?? array() ) );
+ $subscription_ids = array_filter( (array) $subscription_ids );
+ $this->update_subscription_downloads( $product_id, $subscription_ids );
+ }
+
+ // Observe and act on product status changes (regardless of whether they were made from within the product
+ // editor, therefore we don't care about nonce checks here).
+ $this->assess_downloadable_product_status( $product_id );
+ // phpcs:enable
+ }
+
+ /**
+ * Handle save for subscription products (simple subscription or variation).
+ * These products link TO downloadable products.
+ *
+ * @param int $product_id Subscription product or variation ID.
+ *
+ * @return void
+ */
+ private function handle_subscription_product_save( $product_id ) {
+ // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+ if (
+ isset( $_POST[ self::RELATIONSHIP_VAR_SUB_TO_DOWNLOAD . $product_id ] )
+ && wp_verify_nonce( $_POST[ self::RELATIONSHIP_VAR_SUB_TO_DOWNLOAD . $product_id ], self::EDITOR_UPDATE )
+ ) {
+ $product_ids = wc_clean( wp_unslash( $_POST[ '_subscription_linked_downloadable_products_' . $product_id ] ?? array() ) );
+ $product_ids = array_filter( (array) $product_ids );
+ $this->update_subscription_products( $product_id, $product_ids );
+ return;
+ }
+
+ if (
+ isset( $_POST[ self::RELATIONSHIP_SUB_TO_DOWNLOAD ] )
+ && wp_verify_nonce( $_POST[ self::RELATIONSHIP_SUB_TO_DOWNLOAD ], self::EDITOR_UPDATE )
+ ) {
+ $product_ids = wc_clean( wp_unslash( (array) $_POST['_subscription_linked_downloadable_products'] ?? array() ) );
+ $product_ids = array_filter( (array) $product_ids );
+ $this->update_subscription_products( $product_id, $product_ids );
+ }
+ // phpcs:enable
+ }
+
+ /**
+ * Assess downloadable product status and adjust permissions accordingly.
+ * Called when no form data is available (e.g., status change, REST API update, file changes).
+ *
+ * @param int $product_id Product ID.
+ *
+ * @return void
+ */
+ private function assess_downloadable_product_status( $product_id ) {
+ $product = wc_get_product( $product_id );
+
+ if ( ! $product || ! $product->is_downloadable() ) {
+ return;
+ }
+
+ $status_object = get_post_status_object( $product->get_status() );
+ $is_public = $status_object && $status_object->public;
+
+ // Always revoke existing permissions first to ensure clean state.
+ // This handles file changes and status transitions.
+ $this->revoke_permissions_for_product( $product_id );
+
+ // Grant fresh permissions only if product is public.
+ if ( $is_public ) {
+ $this->grant_permissions_for_product( $product_id );
}
}
@@ -62,8 +212,8 @@ class WC_Subscription_Downloads_Products {
public function simple_write_panel_options() {
global $post;
?>
-
-
+
+
-
+
+
-
-
+
+
-
warning(
+ 'Unable to add the downloadable products selector to the product editor (global post object is unavailable).',
+ array( 'backtrace' => true )
+ );
+
+ return;
+ }
+
+ $description = esc_html__( 'Select simple and variable downloadable products that will be included with this subscription product.', 'woocommerce-subscriptions' );
+ $label = esc_html__( 'Linked downloadable products', 'woocommerce-subscriptions' );
+ $linked_products = '';
+ $nonce_field = wp_nonce_field( self::EDITOR_UPDATE, self::RELATIONSHIP_SUB_TO_DOWNLOAD, false, false );
+ $placeholder = esc_attr__( 'Select products', 'woocommerce-subscriptions' );
+
+ foreach ( WC_Subscription_Downloads::get_downloadable_products( $post->ID ) as $product_id ) {
+ $product_id = absint( $product_id );
+ $product = wc_get_product( $product_id );
+
+ if ( $product ) {
+ $product_name = esc_html( wp_strip_all_tags( $product->get_formatted_name() ) );
+ $linked_products .= "";
+ }
+ }
+
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- variables are escaped above.
+ echo "
+