Compare commits
5 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
56e03c0544 | |
|
|
bfe7fb609d | |
|
|
c7e30d354e | |
|
|
dda5b19e32 | |
|
|
c8ec766bf2 |
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@
|
|||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.woocommerce-cart-form__cart-item .wcsg_add_recipient_fields_container label {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.wcsg_add_recipient_fields_container .wcsg_add_recipient_fields .woocommerce_subscriptions_gifting_recipient_email {
|
||||
padding: 0;
|
||||
margin-bottom: 20px;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
'<span id="sale-price-period" style="display: none;"></span>'
|
||||
);
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = $( '<a href="#" style="display:none;"></a>' ).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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
<?php return array('dependencies' => array('react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-i18n', 'wp-primitives'), 'version' => '8bad0ce18409676b15d1');
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
<?php return array('dependencies' => array('react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => 'b52cf873a54e347c8f9e');
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,2 +1,3 @@
|
|||
.wcs-recurring-totals-panel{padding:1em 0 0;position:relative}.wcs-recurring-totals-panel:after{border-style:solid;border-width:1px 0;bottom:0;content:"";display:block;right:0;opacity:.3;pointer-events:none;position:absolute;left:0;top:0}.wcs-recurring-totals-panel+.wcs-recurring-totals-panel:after{border-top-width:0}.wcs-recurring-totals-panel .wc-block-components-panel .wc-block-components-totals-item{padding-right:0;padding-left:0}.wcs-recurring-totals-panel .wc-block-components-totals-item__label:first-letter{text-transform:capitalize}.wcs-recurring-totals-panel .wcs-recurring-totals-panel__title .wc-block-components-totals-item__label{font-weight:700}.wcs-recurring-totals-panel__title{margin:0}.wcs-recurring-totals-panel__details .wc-block-components-panel__button,.wcs-recurring-totals-panel__details .wc-block-components-panel__button:focus,.wcs-recurring-totals-panel__details .wc-block-components-panel__button:hover{font-size:.875em}.wcs-recurring-totals-panel__details .wc-block-components-panel__content>.wc-block-components-totals-item:first-child{margin-top:0}.wcs-recurring-totals-panel__details .wc-block-components-panel__content>.wc-block-components-totals-item:last-child{margin-bottom:0}.wcs-recurring-totals-panel__details .wcs-recurring-totals-panel__details-total .wc-block-components-totals-item__label{font-weight:700}.wcs-recurring-totals__subscription-length{float:left}
|
||||
.wcs-recurring-totals-panel{padding:1em 0 0;position:relative}.wcs-recurring-totals-panel:after{border-style:solid;border-width:1px 0;bottom:0;content:"";display:block;right:0;opacity:.3;pointer-events:none;position:absolute;left:0;top:0}.wcs-recurring-totals-panel+.wcs-recurring-totals-panel:after{border-top-width:0}.wcs-recurring-totals-panel .wc-block-components-panel .wc-block-components-totals-item{padding-right:0;padding-left:0}.wcs-recurring-totals-panel .wc-block-components-totals-item__label:first-letter{text-transform:capitalize}.wcs-recurring-totals-panel .wcs-recurring-totals-panel__title .wc-block-components-totals-item__label{font-weight:500}.wcs-recurring-totals-panel__title{margin:0}.wc-block-components-main .wcs-recurring-totals-panel__details{padding:0 16px}.wcs-recurring-totals-panel__details .wc-block-components-panel__button,.wcs-recurring-totals-panel__details .wc-block-components-panel__button:focus,.wcs-recurring-totals-panel__details .wc-block-components-panel__button:hover{font-size:.875em}.wcs-recurring-totals-panel__details .wc-block-components-panel__content>.wc-block-components-totals-item:first-child{margin-top:0}.wcs-recurring-totals-panel__details .wc-block-components-panel__content>.wc-block-components-totals-item:last-child{margin-bottom:0}.wcs-recurring-totals-panel__details .wcs-recurring-totals-panel__details-total .wc-block-components-totals-item__label{font-weight:700}.wcs-recurring-totals__subscription-length{float:left}
|
||||
.wc-block-components-local-pickup-rates-control .wc-block-components-local-pickup-select:not(:last-child){margin-bottom:16px}
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
<?php return array('dependencies' => array('react', 'wc-blocks-checkout', 'wc-blocks-data-store', 'wc-price-format', 'wc-settings', 'wp-data', 'wp-element', 'wp-i18n', 'wp-plugins'), 'version' => '76dd98c6a2badbfb9781');
|
||||
<?php return array('dependencies' => array('react', 'wc-blocks-checkout', 'wc-blocks-data-store', 'wc-price-format', 'wc-settings', 'wp-data', 'wp-element', 'wp-i18n', 'wp-plugins'), 'version' => '32bfa486d902830f29c7');
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
.wcs-recurring-totals-panel{padding:1em 0 0;position:relative}.wcs-recurring-totals-panel:after{border-style:solid;border-width:1px 0;bottom:0;content:"";display:block;left:0;opacity:.3;pointer-events:none;position:absolute;right:0;top:0}.wcs-recurring-totals-panel+.wcs-recurring-totals-panel:after{border-top-width:0}.wcs-recurring-totals-panel .wc-block-components-panel .wc-block-components-totals-item{padding-left:0;padding-right:0}.wcs-recurring-totals-panel .wc-block-components-totals-item__label:first-letter{text-transform:capitalize}.wcs-recurring-totals-panel .wcs-recurring-totals-panel__title .wc-block-components-totals-item__label{font-weight:700}.wcs-recurring-totals-panel__title{margin:0}.wcs-recurring-totals-panel__details .wc-block-components-panel__button,.wcs-recurring-totals-panel__details .wc-block-components-panel__button:focus,.wcs-recurring-totals-panel__details .wc-block-components-panel__button:hover{font-size:.875em}.wcs-recurring-totals-panel__details .wc-block-components-panel__content>.wc-block-components-totals-item:first-child{margin-top:0}.wcs-recurring-totals-panel__details .wc-block-components-panel__content>.wc-block-components-totals-item:last-child{margin-bottom:0}.wcs-recurring-totals-panel__details .wcs-recurring-totals-panel__details-total .wc-block-components-totals-item__label{font-weight:700}.wcs-recurring-totals__subscription-length{float:right}
|
||||
.wcs-recurring-totals-panel{padding:1em 0 0;position:relative}.wcs-recurring-totals-panel:after{border-style:solid;border-width:1px 0;bottom:0;content:"";display:block;left:0;opacity:.3;pointer-events:none;position:absolute;right:0;top:0}.wcs-recurring-totals-panel+.wcs-recurring-totals-panel:after{border-top-width:0}.wcs-recurring-totals-panel .wc-block-components-panel .wc-block-components-totals-item{padding-left:0;padding-right:0}.wcs-recurring-totals-panel .wc-block-components-totals-item__label:first-letter{text-transform:capitalize}.wcs-recurring-totals-panel .wcs-recurring-totals-panel__title .wc-block-components-totals-item__label{font-weight:500}.wcs-recurring-totals-panel__title{margin:0}.wc-block-components-main .wcs-recurring-totals-panel__details{padding:0 16px}.wcs-recurring-totals-panel__details .wc-block-components-panel__button,.wcs-recurring-totals-panel__details .wc-block-components-panel__button:focus,.wcs-recurring-totals-panel__details .wc-block-components-panel__button:hover{font-size:.875em}.wcs-recurring-totals-panel__details .wc-block-components-panel__content>.wc-block-components-totals-item:first-child{margin-top:0}.wcs-recurring-totals-panel__details .wc-block-components-panel__content>.wc-block-components-totals-item:last-child{margin-bottom:0}.wcs-recurring-totals-panel__details .wcs-recurring-totals-panel__details-total .wc-block-components-totals-item__label{font-weight:700}.wcs-recurring-totals__subscription-length{float:right}
|
||||
.wc-block-components-local-pickup-rates-control .wc-block-components-local-pickup-select:not(:last-child){margin-bottom:16px}
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
.wcsg-gifting-to-container-editing{display:flex;gap:5px;margin-top:12px}.wcsg-gifting-to-container-editing .wc-block-components-text-input{flex-grow:1;margin-top:0}.wcsg-gifting-to-container-editing .wp-element-button.gifting-update-button:not(.is-link){min-height:unset;padding:0 var(--xs,20px)}.wcsg-gifting-to-container-editing .components-base-control__field{margin-bottom:0}.wcsg-gifting-to-container-editing .wc-block-components-text-input input:-webkit-autofill{padding-top:.5em}.wcsg-gifting-to-container-editing .has-error .components-text-control__input{color:var(--wc-red,#cc1818)}.wp-block-woocommerce-mini-cart-contents .wcsg-gifting-to-container-editing{flex-direction:column}.wp-block-woocommerce-mini-cart-contents .gifting-update-button{height:40px}.wcsg-gifting-to-container-view{align-items:center;display:flex;flex-wrap:wrap;gap:5px;max-width:100%}.wcsg-gifting-to-container-view span{align-items:center;display:inline-flex;max-width:100%;word-break:break-all;word-wrap:break-word;gap:5px}.wcsg-gifting-to-container-view .components-button.is-link{color:var(--wp--preset--color--contrast);flex-shrink:0;font-size:medium}.wc-block-checkout,.wc-block-components-product-details__gifting-to{align-items:center;display:flex;flex-wrap:wrap;max-width:100%}.wc-block-checkout .wc-block-components-product-details__name:after,.wc-block-components-product-details__gifting-to .wc-block-components-product-details__name:after{content:" "}.wc-block-checkout .wc-block-components-product-details__value,.wc-block-components-product-details__gifting-to .wc-block-components-product-details__value{align-items:center;display:inline-flex;max-width:100%;word-break:break-all;word-wrap:break-word}.wcsg-block-recipient-container .components-checkbox-control__label{font-size:medium}.wc-block-cart .wc-block-components-product-details__gifting-to,.wc-block-cart .wc-block-components-product-details__gifting-to-hidden,.wc-block-cart .wc-block-components-product-details__item-key,.wc-block-checkout .wc-block-components-product-details__gifting-to-hidden,.wc-block-checkout .wc-block-components-product-details__item-key,.wp-block-woocommerce-mini-cart-contents .wc-block-components-product-details__gifting-to,.wp-block-woocommerce-mini-cart-contents .wc-block-components-product-details__gifting-to-hidden,.wp-block-woocommerce-mini-cart-contents .wc-block-components-product-details__item-key{display:none}
|
||||
.wcsg-gifting-to-container-editing{display:flex;gap:5px;margin-top:10px;min-width:210px}.wcsg-gifting-to-container-editing .wc-block-components-text-input{flex-grow:1;margin-top:0}.wcsg-gifting-to-container-editing .wp-element-button.gifting-update-button:not(.is-link){min-height:unset;padding:0 var(--xs,20px)}.wcsg-gifting-to-container-editing .components-base-control__field{margin-bottom:0}.wcsg-gifting-to-container-editing .wc-block-components-text-input input:-webkit-autofill{padding-top:.5em}.wcsg-gifting-to-container-editing .has-error .components-text-control__input{color:var(--wc-red,#cc1818)}.wp-block-woocommerce-mini-cart-contents .wcsg-gifting-to-container-editing{flex-direction:column}.wp-block-woocommerce-mini-cart-contents .gifting-update-button{height:40px}.wcsg-gifting-to-container-view{align-items:center;display:flex;flex-wrap:wrap;gap:5px;max-width:100%}.wcsg-gifting-to-container-view span{align-items:center;display:inline-flex;max-width:100%;word-break:break-all;word-wrap:break-word;gap:5px}.wcsg-gifting-to-container-view .components-button.is-link{color:var(--wp--preset--color--contrast);flex-shrink:0;font-size:medium}.wc-block-checkout,.wc-block-components-product-details__gifting-to{align-items:center;display:flex;flex-wrap:wrap;max-width:100%}.wc-block-checkout .wc-block-components-product-details__name:after,.wc-block-components-product-details__gifting-to .wc-block-components-product-details__name:after{content:" "}.wc-block-checkout .wc-block-components-product-details__value,.wc-block-components-product-details__gifting-to .wc-block-components-product-details__value{align-items:center;display:inline-flex;max-width:100%;word-break:break-all;word-wrap:break-word}.wcsg-block-recipient-container{font-size:var(--wp--preset--font-size--small,14px);line-height:20px}.wcsg-block-recipient-container .components-checkbox-control__label{font-size:var(--wp--preset--font-size--small,14px);margin-bottom:0}.wc-block-cart .wc-block-components-product-details__gifting-to,.wc-block-cart .wc-block-components-product-details__gifting-to-hidden,.wc-block-cart .wc-block-components-product-details__item-key,.wc-block-checkout .wc-block-components-product-details__gifting-to-hidden,.wc-block-checkout .wc-block-components-product-details__item-key,.wp-block-woocommerce-mini-cart-contents .wc-block-components-product-details__gifting-to,.wp-block-woocommerce-mini-cart-contents .wc-block-components-product-details__gifting-to-hidden,.wp-block-woocommerce-mini-cart-contents .wc-block-components-product-details__item-key{display:none}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
<?php return array('dependencies' => array('react', 'react-dom', 'wc-blocks-checkout', 'wp-components', 'wp-data', 'wp-i18n', 'wp-plugins', 'wp-url'), 'version' => 'e5022699093de48ed96e');
|
||||
<?php return array('dependencies' => array('react', 'react-dom', 'wc-blocks-checkout', 'wp-components', 'wp-data', 'wp-i18n', 'wp-plugins', 'wp-url'), 'version' => 'cf4dd71c0c7418b591fd');
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
.wcsg-gifting-to-container-editing{display:flex;gap:5px;margin-top:12px}.wcsg-gifting-to-container-editing .wc-block-components-text-input{flex-grow:1;margin-top:0}.wcsg-gifting-to-container-editing .wp-element-button.gifting-update-button:not(.is-link){min-height:unset;padding:0 var(--xs,20px)}.wcsg-gifting-to-container-editing .components-base-control__field{margin-bottom:0}.wcsg-gifting-to-container-editing .wc-block-components-text-input input:-webkit-autofill{padding-top:.5em}.wcsg-gifting-to-container-editing .has-error .components-text-control__input{color:var(--wc-red,#cc1818)}.wp-block-woocommerce-mini-cart-contents .wcsg-gifting-to-container-editing{flex-direction:column}.wp-block-woocommerce-mini-cart-contents .gifting-update-button{height:40px}.wcsg-gifting-to-container-view{align-items:center;display:flex;flex-wrap:wrap;gap:5px;max-width:100%}.wcsg-gifting-to-container-view span{align-items:center;display:inline-flex;max-width:100%;word-break:break-all;word-wrap:break-word;gap:5px}.wcsg-gifting-to-container-view .components-button.is-link{color:var(--wp--preset--color--contrast);flex-shrink:0;font-size:medium}.wc-block-checkout,.wc-block-components-product-details__gifting-to{align-items:center;display:flex;flex-wrap:wrap;max-width:100%}.wc-block-checkout .wc-block-components-product-details__name:after,.wc-block-components-product-details__gifting-to .wc-block-components-product-details__name:after{content:" "}.wc-block-checkout .wc-block-components-product-details__value,.wc-block-components-product-details__gifting-to .wc-block-components-product-details__value{align-items:center;display:inline-flex;max-width:100%;word-break:break-all;word-wrap:break-word}.wcsg-block-recipient-container .components-checkbox-control__label{font-size:medium}.wc-block-cart .wc-block-components-product-details__gifting-to,.wc-block-cart .wc-block-components-product-details__gifting-to-hidden,.wc-block-cart .wc-block-components-product-details__item-key,.wc-block-checkout .wc-block-components-product-details__gifting-to-hidden,.wc-block-checkout .wc-block-components-product-details__item-key,.wp-block-woocommerce-mini-cart-contents .wc-block-components-product-details__gifting-to,.wp-block-woocommerce-mini-cart-contents .wc-block-components-product-details__gifting-to-hidden,.wp-block-woocommerce-mini-cart-contents .wc-block-components-product-details__item-key{display:none}
|
||||
.wcsg-gifting-to-container-editing{display:flex;gap:5px;margin-top:10px;min-width:210px}.wcsg-gifting-to-container-editing .wc-block-components-text-input{flex-grow:1;margin-top:0}.wcsg-gifting-to-container-editing .wp-element-button.gifting-update-button:not(.is-link){min-height:unset;padding:0 var(--xs,20px)}.wcsg-gifting-to-container-editing .components-base-control__field{margin-bottom:0}.wcsg-gifting-to-container-editing .wc-block-components-text-input input:-webkit-autofill{padding-top:.5em}.wcsg-gifting-to-container-editing .has-error .components-text-control__input{color:var(--wc-red,#cc1818)}.wp-block-woocommerce-mini-cart-contents .wcsg-gifting-to-container-editing{flex-direction:column}.wp-block-woocommerce-mini-cart-contents .gifting-update-button{height:40px}.wcsg-gifting-to-container-view{align-items:center;display:flex;flex-wrap:wrap;gap:5px;max-width:100%}.wcsg-gifting-to-container-view span{align-items:center;display:inline-flex;max-width:100%;word-break:break-all;word-wrap:break-word;gap:5px}.wcsg-gifting-to-container-view .components-button.is-link{color:var(--wp--preset--color--contrast);flex-shrink:0;font-size:medium}.wc-block-checkout,.wc-block-components-product-details__gifting-to{align-items:center;display:flex;flex-wrap:wrap;max-width:100%}.wc-block-checkout .wc-block-components-product-details__name:after,.wc-block-components-product-details__gifting-to .wc-block-components-product-details__name:after{content:" "}.wc-block-checkout .wc-block-components-product-details__value,.wc-block-components-product-details__gifting-to .wc-block-components-product-details__value{align-items:center;display:inline-flex;max-width:100%;word-break:break-all;word-wrap:break-word}.wcsg-block-recipient-container{font-size:var(--wp--preset--font-size--small,14px);line-height:20px}.wcsg-block-recipient-container .components-checkbox-control__label{font-size:var(--wp--preset--font-size--small,14px);margin-bottom:0}.wc-block-cart .wc-block-components-product-details__gifting-to,.wc-block-cart .wc-block-components-product-details__gifting-to-hidden,.wc-block-cart .wc-block-components-product-details__item-key,.wc-block-checkout .wc-block-components-product-details__gifting-to-hidden,.wc-block-checkout .wc-block-components-product-details__item-key,.wp-block-woocommerce-mini-cart-contents .wc-block-components-product-details__gifting-to,.wp-block-woocommerce-mini-cart-contents .wc-block-components-product-details__gifting-to-hidden,.wp-block-woocommerce-mini-cart-contents .wc-block-components-product-details__item-key{display:none}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,12 +1,63 @@
|
|||
*** 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.
|
||||
* 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.
|
||||
* Fix: Add admin notice and debug tool to recreate the payment retry database table when it's missing.
|
||||
* Fix: Prevent resubscribe and reactivate buttons from showing for subscriptions containing disabled, deleted, draft, or limited products.
|
||||
* Fix: Prevent error that blocks the subscriptions list from loading when gifted subscriptions are purchased with certain payment methods.
|
||||
* Fix: Ensure gift recipient email address is displayed in the order confirmation email sent to the purchaser.
|
||||
* Fix: Prevent pickup location options from displaying incorrectly alongside shipping methods on the Blocks Checkout page.
|
||||
* Fix: Fix invalid HTML tags that cause fatal errors when using themes with WordPress Interactivity API.
|
||||
* Fix: Add password reset links to the new account email template for gifted subscription recipients.
|
||||
* Tweak: Improve the gift recipient email input field on Blocks Cart and Mini Cart to match the style of other checkout fields.
|
||||
* Dev: Improve housekeeping of output buffers when handling requests to change subscription payment methods.
|
||||
* Dev: Improve type safety in relation to the `wcs_get_subscription()` function.
|
||||
|
||||
2025-10-15 - version 8.0.0
|
||||
* Add: Blocks Checkout now displays a single shipping method selection for both initial and recurring carts, similar to Classic Checkout. This can be disabled using the `wcs_cart_totals_shipping_html_price_only` filter.
|
||||
* Update: Exclude orders with a "processing" status from the "subscription_orders" telemetry data.
|
||||
* Fix: Reactivating limited subscriptions from product page is now supported.
|
||||
* Fix: Gifting email previews.
|
||||
* Fix: Shipping label on blocks checkout no more shows up as "Free" when there's no shipping method available.
|
||||
* Dev: Opt-in tracking data. Data sent to WooCommerce:
|
||||
* Fix: Resubscribing to a limited subscription from the product page will now reactivate the existing subscription.
|
||||
* Fix: "New initial order - Recipient" email template preview no longer causes critical error.
|
||||
* Fix: Shipping value on blocks checkout no longer shows as "Free" when no shipping method is available.
|
||||
* Dev: Improved data telemetry which is sent to WooCommerce:
|
||||
* WooCommerce Subscriptions setting values
|
||||
* Order totals of subscription-related orders by order type and payment gateway, aggregated by month
|
||||
* Number of subscription-related orders by order type and payment gateway, aggregated by month
|
||||
|
|
@ -18,7 +69,7 @@
|
|||
* Number of gifted subscriptions
|
||||
* Number of subscribers
|
||||
* Purchase event for subscription products
|
||||
* To disable this tracking, opt-out of WooCommerce tracking, see https://woocommerce.com/usage-tracking/
|
||||
* Exclude orders with a "processing" status from the "subscription_orders" telemetry data.
|
||||
|
||||
2025-09-16 - version 7.9.0
|
||||
* Add: REST API endpoint to retrieve subscriptions associated with a specific order ID.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
/**
|
||||
* WCS_Admin_Assets Class
|
||||
*
|
||||
* Handles admin assets (scripts and styles) for WooCommerce Subscriptions.
|
||||
*
|
||||
* @package WooCommerce Subscriptions/Admin
|
||||
*/
|
||||
class WCS_Admin_Assets {
|
||||
|
||||
/**
|
||||
* Initialize the tour handler
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue required scripts and styles
|
||||
*/
|
||||
public static function enqueue_scripts() {
|
||||
$script_asset_path = \WC_Subscriptions_Plugin::instance()->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'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -83,6 +83,10 @@ class WC_REST_Subscriptions_V1_Controller extends WC_REST_Orders_V1_Controller {
|
|||
if ( ! empty( $post->post_type ) && ! empty( $post->ID ) && 'shop_subscription' == $post->post_type ) {
|
||||
$subscription = wcs_get_subscription( $post->ID );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response->data['billing_period'] = $subscription->get_billing_period();
|
||||
$response->data['billing_interval'] = $subscription->get_billing_interval();
|
||||
|
||||
|
|
@ -292,8 +296,13 @@ class WC_REST_Subscriptions_V1_Controller extends WC_REST_Orders_V1_Controller {
|
|||
return new WP_Error( 'woocommerce_rest_invalid_shop_subscription_id', __( 'Invalid subscription id.', 'woocommerce-subscriptions' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$this->post_type = 'shop_order';
|
||||
$subscription = wcs_get_subscription( $id );
|
||||
$this->post_type = 'shop_order';
|
||||
$subscription = wcs_get_subscription( $id );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
return new WP_Error( 'woocommerce_rest_invalid_shop_subscription_id', __( 'Invalid subscription id.', 'woocommerce-subscriptions' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$subscription_orders = $subscription->get_related_orders();
|
||||
|
||||
$orders = array();
|
||||
|
|
@ -371,6 +380,10 @@ class WC_REST_Subscriptions_V1_Controller extends WC_REST_Orders_V1_Controller {
|
|||
// In particular, the update_post_meta() called while _stripe_card_id is updated to _stripe_source_id
|
||||
$subscription = wcs_get_subscription( $subscription->get_id() );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
throw new WC_REST_Exception( 'woocommerce_rest_payment_update_failed', __( 'Subscription payment method could not be set updated due to technical issues.', 'woocommerce-subscriptions' ), 500 );
|
||||
}
|
||||
|
||||
if ( isset( $payment_method_meta[ $payment_method ] ) ) {
|
||||
$payment_method_meta = $payment_method_meta[ $payment_method ];
|
||||
|
||||
|
|
|
|||
|
|
@ -702,12 +702,16 @@ class WC_REST_Subscriptions_V2_Controller extends WC_REST_Orders_V2_Controller {
|
|||
$subscription->add_item( $item );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Fetch a fresh instance of the subscription because the current instance has an empty line item cache generated before we had copied the line items.
|
||||
* Fetching a new instance will ensure the line items are used when calculating totals.
|
||||
*/
|
||||
$subscription = wcs_get_subscription( $subscription->get_id() );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
throw new Exception( __( 'There was a problem completing this request. The subscription may have been deleted by another process.', 'woocommerce-subscriptions' ) );
|
||||
}
|
||||
|
||||
$subscription->calculate_totals();
|
||||
|
||||
/**
|
||||
|
|
@ -719,7 +723,13 @@ class WC_REST_Subscriptions_V2_Controller extends WC_REST_Orders_V2_Controller {
|
|||
*/
|
||||
do_action( "woocommerce_rest_insert_{$this->post_type}_object", $subscription, $request, true );
|
||||
|
||||
$response = $this->prepare_object_for_response( wcs_get_subscription( $subscription->get_id() ), $request );
|
||||
$fresh_subscription = wcs_get_subscription( $subscription->get_id() );
|
||||
|
||||
if ( ! $fresh_subscription ) {
|
||||
throw new Exception( __( 'There was a problem completing this request. The subscription may have been deleted by another process.', 'woocommerce-subscriptions' ) );
|
||||
}
|
||||
|
||||
$response = $this->prepare_object_for_response( $fresh_subscription, $request );
|
||||
$subscriptions[] = $this->prepare_response_for_collection( $response );
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
|
|
|
|||
|
|
@ -117,6 +117,13 @@ class WC_Subscriptions_Dependency_Manager {
|
|||
|
||||
$this->wc_version_cached = true;
|
||||
|
||||
// Try to get version from transient first
|
||||
$this->wc_active_version = get_transient( 'wcs_woocommerce_active_version' );
|
||||
|
||||
if ( false !== $this->wc_active_version ) {
|
||||
return $this->wc_active_version;
|
||||
}
|
||||
|
||||
// Load plugin.php if it's not already loaded.
|
||||
if ( ! function_exists( 'is_plugin_active' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
|
@ -139,9 +146,15 @@ class WC_Subscriptions_Dependency_Manager {
|
|||
|
||||
if ( $is_woocommerce && is_plugin_active( $plugin_slug ) ) {
|
||||
$this->wc_active_version = $plugin_data['Version'];
|
||||
break; // Found it, no need to continue looping
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the result in a transient for 1 hour
|
||||
if ( ! empty( $this->wc_active_version ) ) {
|
||||
set_transient( 'wcs_woocommerce_active_version', $this->wc_active_version, HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
return $this->wc_active_version;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
@ -44,6 +45,8 @@ 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' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -286,6 +289,34 @@ class WC_Subscriptions_Plugin extends WC_Subscriptions_Core_Plugin {
|
|||
WCS_Gifting::init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to initialize additional downloads functionality.
|
||||
*
|
||||
* This functionality makes it possible to link downloadable products with a subscription
|
||||
* product. Purchasers of the subscription product then automatically get access to the files associated with downloadable product.
|
||||
*
|
||||
* Previously, this functionality existed as a standalone plugin (WooCommerce Subscription Downnloads) and so,
|
||||
* before initializing, we try to determine if the standalone plugin is active and has already loaded (if it is
|
||||
* active, we do not proceed).
|
||||
*/
|
||||
public function init_downloads() {
|
||||
if (
|
||||
$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;
|
||||
}
|
||||
|
||||
WC_Subscription_Downloads::setup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to determine if the specified plugin is being activated.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class WCS_Call_To_Action_Button_Text_Manager {
|
|||
public static function add_settings( $settings ) {
|
||||
$button_text_settings = array(
|
||||
array(
|
||||
'name' => __( 'Button Text', 'woocommerce-subscriptions' ),
|
||||
'name' => __( 'Button text', 'woocommerce-subscriptions' ),
|
||||
'type' => 'title',
|
||||
'desc' => '',
|
||||
'id' => WC_Subscriptions_Admin::$option_prefix . '_button_text',
|
||||
|
|
|
|||
|
|
@ -357,7 +357,12 @@ class WCS_Limited_Recurring_Coupon_Manager {
|
|||
$has_limited_coupon = false;
|
||||
|
||||
if ( $change_payment && isset( $_GET['_wpnonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) ) ) {
|
||||
$subscription = wcs_get_subscription( $change_payment );
|
||||
$subscription = wcs_get_subscription( $change_payment );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
return $gateways;
|
||||
}
|
||||
|
||||
$has_limited_coupon = self::order_has_limited_recurring_coupon( $subscription );
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,10 @@ class WCS_Subscriber_Role_Manager {
|
|||
),
|
||||
);
|
||||
|
||||
WC_Subscriptions_Admin::insert_setting_after( $settings, WC_Subscriptions_Admin::$option_prefix . '_button_text', $role_settings, 'multiple_settings', 'sectionend' );
|
||||
if ( ! WC_Subscriptions_Admin::insert_setting_after( $settings, WC_Subscriptions_Admin::$option_prefix . '_button_text', $role_settings, 'multiple_settings', 'sectionend' ) ) {
|
||||
$settings = array_merge( $settings, $role_settings );
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,18 @@ abstract class WCS_Table_Maker {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the schema option and recreates the tables.
|
||||
*
|
||||
* This forces the table schema to be regenerated by removing the stored
|
||||
* schema version and triggering the table registration process.
|
||||
*/
|
||||
public function recreate_tables() {
|
||||
delete_option( $this->get_schema_option_name() );
|
||||
|
||||
$this->register_tables();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $table The name of the table
|
||||
*
|
||||
|
|
|
|||
|
|
@ -519,7 +519,7 @@ class WCS_Admin_Post_Types {
|
|||
'wcs-unknown-order-info-wrapper',
|
||||
esc_url( 'https://woocommerce.com/document/subscriptions/store-manager-guide/#section-19' ),
|
||||
// translators: Placeholder is a <br> HTML tag.
|
||||
wcs_help_tip( sprintf( __( "This subscription couldn't be loaded from the database. %s Click to learn more.", 'woocommerce-subscriptions' ), '</br>' ) ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
wcs_help_tip( sprintf( __( "This subscription couldn't be loaded from the database. %s Click to learn more.", 'woocommerce-subscriptions' ), '<br>' ) ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -711,12 +711,12 @@ class WCS_Admin_Post_Types {
|
|||
$tooltip_classes = 'woocommerce-help-tip';
|
||||
|
||||
if ( $datetime->getTimestamp() < time() ) {
|
||||
$tooltip_message .= __( '<b>Subscription payment overdue.</b></br>', 'woocommerce-subscriptions' );
|
||||
$tooltip_message .= '<b>' . esc_html__( 'Subscription payment overdue.', 'woocommerce-subscriptions' ) . '</b><br>';
|
||||
$tooltip_classes .= ' wcs-payment-overdue';
|
||||
}
|
||||
|
||||
if ( $subscription->payment_method_supports( 'gateway_scheduled_payments' ) && ! $subscription->is_manual() ) {
|
||||
$tooltip_message .= __( 'This date should be treated as an estimate only. The payment gateway for this subscription controls when payments are processed.</br>', 'woocommerce-subscriptions' );
|
||||
$tooltip_message .= esc_html__( 'This date should be treated as an estimate only. The payment gateway for this subscription controls when payments are processed.', 'woocommerce-subscriptions' ) . '<br>';
|
||||
$tooltip_classes .= ' wcs-offsite-renewal';
|
||||
}
|
||||
|
||||
|
|
@ -1171,7 +1171,7 @@ class WCS_Admin_Post_Types {
|
|||
|
||||
// @phpstan-ignore property.notFound
|
||||
foreach ( WC()->payment_gateways->get_available_payment_gateways() as $gateway_id => $gateway ) {
|
||||
echo '<option value="' . esc_attr( $gateway_id ) . '"' . ( $selected_gateway_id === $gateway_id ? 'selected' : '' ) . '>' . esc_html( $gateway->title ) . '</option>';
|
||||
echo '<option value="' . esc_attr( $gateway_id ) . '"' . ( $selected_gateway_id === $gateway_id ? 'selected' : '' ) . '>' . esc_html( method_exists( $gateway, 'get_title' ) ? $gateway->get_title() : $gateway->title ) . '</option>';
|
||||
}
|
||||
echo '<option value="_manual_renewal">' . esc_html__( 'Manual Renewal', 'woocommerce-subscriptions' ) . '</option>';
|
||||
?>
|
||||
|
|
@ -1503,9 +1503,20 @@ class WCS_Admin_Post_Types {
|
|||
|
||||
foreach ( $subscription_ids as $subscription_id ) {
|
||||
$subscription = wcs_get_subscription( $subscription_id );
|
||||
$note = _x( 'Subscription status changed by bulk edit:', 'Used in order note. Reason why status changed.', 'woocommerce-subscriptions' );
|
||||
|
||||
$note = _x( 'Subscription status changed by bulk edit:', 'Used in order note. Reason why status changed.', 'woocommerce-subscriptions' );
|
||||
|
||||
try {
|
||||
if ( ! $subscription ) {
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
// Translators: 1: subscription ID.
|
||||
__( 'Subscription with ID %1$d does not exist and could not be updated.', 'woocommerce-subscriptions' ),
|
||||
$subscription_id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'cancelled' === $new_status ) {
|
||||
$subscription->cancel_order( $note );
|
||||
} else {
|
||||
|
|
@ -1542,6 +1553,11 @@ class WCS_Admin_Post_Types {
|
|||
|
||||
foreach ( $subscription_ids as $id ) {
|
||||
$subscription = wcs_get_subscription( $id );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$subscription->delete( $force_delete );
|
||||
$updated_subscription = wcs_get_subscription( $id );
|
||||
|
||||
|
|
@ -1571,7 +1587,13 @@ class WCS_Admin_Post_Types {
|
|||
|
||||
foreach ( $subscription_ids as $id ) {
|
||||
if ( $use_crud_method ) {
|
||||
$data_store->untrash_order( wcs_get_subscription( $id ) );
|
||||
$subscription = wcs_get_subscription( $id );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data_store->untrash_order( $subscription );
|
||||
} else {
|
||||
wp_untrash_post( $id );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
sprintf( // Translators: The %1 placeholder is the translated order relationship ("Parent Order"), %2 placeholder is a <br> HTML tag.
|
||||
__( 'This %1$s couldn\'t be loaded from the database. %1$s Click to learn more.', 'woocommerce-subscriptions' ),
|
||||
esc_html( $relationship ),
|
||||
'</br>'
|
||||
'<br>'
|
||||
)
|
||||
);
|
||||
?>
|
||||
|
|
|
|||
|
|
@ -17,24 +17,6 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
|
||||
class WC_Product_Subscription_Variation extends WC_Product_Variation {
|
||||
|
||||
/**
|
||||
* A way to access the old array property.
|
||||
*/
|
||||
protected $subscription_variation_level_meta_data;
|
||||
|
||||
/**
|
||||
* Create a simple subscription product object.
|
||||
*
|
||||
* @access public
|
||||
* @param mixed $product
|
||||
*/
|
||||
public function __construct( $product = 0 ) {
|
||||
|
||||
parent::__construct( $product );
|
||||
|
||||
$this->subscription_variation_level_meta_data = new WCS_Array_Property_Post_Meta_Black_Magic( $this->get_id() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic __get method for backwards compatibility. Map legacy vars to WC_Subscriptions_Product getters.
|
||||
*
|
||||
|
|
@ -43,19 +25,11 @@ class WC_Product_Subscription_Variation extends WC_Product_Variation {
|
|||
*/
|
||||
public function __get( $key ) {
|
||||
|
||||
if ( 'subscription_variation_level_meta_data' === $key ) {
|
||||
$value = wcs_product_deprecated_property_handler( $key, $this );
|
||||
|
||||
wcs_deprecated_argument( __CLASS__ . '::$' . $key, '2.2.0', 'Product properties should not be accessed directly with WooCommerce 3.0+. Use the getter in WC_Subscriptions_Product instead.' );
|
||||
|
||||
$value = $this->subscription_variation_level_meta_data; // Behold, the horror that is the magic of WCS_Array_Property_Post_Meta_Black_Magic
|
||||
} else {
|
||||
|
||||
$value = wcs_product_deprecated_property_handler( $key, $this );
|
||||
|
||||
// No matching property found in wcs_product_deprecated_property_handler()
|
||||
if ( is_null( $value ) ) {
|
||||
$value = parent::__get( $key );
|
||||
}
|
||||
// No matching property found in wcs_product_deprecated_property_handler()
|
||||
if ( is_null( $value ) ) {
|
||||
$value = parent::__get( $key );
|
||||
}
|
||||
|
||||
return $value;
|
||||
|
|
@ -89,7 +63,6 @@ class WC_Product_Subscription_Variation extends WC_Product_Variation {
|
|||
/**
|
||||
* Get the add to cart button text
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function add_to_cart_text() {
|
||||
|
|
@ -106,7 +79,6 @@ class WC_Product_Subscription_Variation extends WC_Product_Variation {
|
|||
/**
|
||||
* Get the add to cart button text for the single page
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function single_add_to_cart_text() {
|
||||
|
|
@ -116,7 +88,6 @@ class WC_Product_Subscription_Variation extends WC_Product_Variation {
|
|||
/**
|
||||
* Checks if the variable product this variation belongs to is purchasable.
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function is_purchasable() {
|
||||
|
|
@ -128,12 +99,11 @@ class WC_Product_Subscription_Variation extends WC_Product_Variation {
|
|||
* Checks the product type to see if it is either this product's type or the parent's
|
||||
* product type.
|
||||
*
|
||||
* @access public
|
||||
* @param mixed $type Array or string of types
|
||||
* @return bool
|
||||
*/
|
||||
public function is_type( $type ) {
|
||||
if ( 'variation' == $type || ( is_array( $type ) && in_array( 'variation', $type ) ) ) {
|
||||
if ( 'variation' === $type || ( is_array( $type ) && in_array( 'variation', $type, true ) ) ) {
|
||||
return true;
|
||||
} else {
|
||||
return parent::is_type( $type );
|
||||
|
|
|
|||
|
|
@ -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', $text, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the add to cart button text for the single page
|
||||
*
|
||||
|
|
|
|||
|
|
@ -44,6 +44,14 @@ class WC_Subscription extends WC_Order {
|
|||
*/
|
||||
private $editable;
|
||||
|
||||
/**
|
||||
* Stores if the subscription is in the payment completed flow.
|
||||
* Allowing subscriptions to be renewed even if they have limited products.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $is_payment_completed_flow = false;
|
||||
|
||||
/**
|
||||
* Extra data for this object. Name value pairs (name + default value). Used to add additional information to parent.
|
||||
*
|
||||
|
|
@ -340,7 +348,9 @@ class WC_Subscription extends WC_Order {
|
|||
break;
|
||||
case 'completed': // core WC order status mapped internally to avoid exceptions
|
||||
case 'active':
|
||||
if ( $this->payment_method_supports( 'subscription_reactivation' ) && $this->has_status( 'on-hold' ) ) {
|
||||
if ( ! $this->is_payment_completed_flow && $this->contains_unavailable_product() ) {
|
||||
$can_be_updated = false;
|
||||
} elseif ( $this->payment_method_supports( 'subscription_reactivation' ) && $this->has_status( 'on-hold' ) ) {
|
||||
// If the subscription's end date is in the past, it cannot be reactivated.
|
||||
$end_time = $this->get_time( 'end' );
|
||||
if ( 0 !== $end_time && $end_time < gmdate( 'U' ) ) {
|
||||
|
|
@ -415,6 +425,48 @@ class WC_Subscription extends WC_Order {
|
|||
return apply_filters( 'woocommerce_can_subscription_be_updated_to_' . $new_status, $can_be_updated, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates status of the subscription
|
||||
*
|
||||
|
|
@ -1996,6 +2048,7 @@ class WC_Subscription extends WC_Order {
|
|||
* @param WC_Order $last_order
|
||||
*/
|
||||
public function payment_complete_for_order( $last_order ) {
|
||||
$this->is_payment_completed_flow = true;
|
||||
|
||||
// Clear the cached renewal payment counts
|
||||
if ( isset( $this->cached_payment_count['completed'] ) ) {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1517,7 +1517,7 @@ class WC_Subscriptions_Cart {
|
|||
*/
|
||||
public static function pre_get_refreshed_fragments() {
|
||||
wcs_deprecated_function( __METHOD__, '2.5.0' );
|
||||
if ( defined( 'DOING_AJAX' ) && true === DOING_AJAX && ! defined( 'WOOCOMMERCE_CART' ) ) {
|
||||
if ( wp_doing_ajax() && ! defined( 'WOOCOMMERCE_CART' ) ) {
|
||||
define( 'WOOCOMMERCE_CART', true );
|
||||
WC()->cart->calculate_totals();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,6 +139,11 @@ class WC_Subscriptions_Change_Payment_Gateway {
|
|||
$subscription = wcs_get_subscription( absint( $wp->query_vars['order-pay'] ) );
|
||||
$subscription_key = isset( $_GET['key'] ) ? wc_clean( $_GET['key'] ) : '';
|
||||
|
||||
if ( ! $subscription ) {
|
||||
wc_print_notice( __( 'There was an unexpected problem with your request. Please try again.', 'woocommerce-subscriptions' ), 'error' );
|
||||
return;
|
||||
}
|
||||
|
||||
do_action( 'before_woocommerce_pay' );
|
||||
|
||||
/**
|
||||
|
|
@ -276,19 +281,27 @@ class WC_Subscriptions_Change_Payment_Gateway {
|
|||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.4
|
||||
*/
|
||||
public static function change_payment_method_via_pay_shortcode() {
|
||||
|
||||
if ( ! isset( $_POST['_wcsnonce'] ) || ! wp_verify_nonce( wc_clean( wp_unslash( $_POST['_wcsnonce'] ) ), 'wcs_change_payment_method' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subscription_id = absint( wc_clean( wp_unslash( $_POST['woocommerce_change_payment'] ) ) );
|
||||
$subscription = wcs_get_subscription( $subscription_id );
|
||||
$subscription = wcs_get_subscription( $subscription_id );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
wc_add_notice( __( 'There was an unexpected problem with your request. Please try again.', 'woocommerce-subscriptions' ), 'error' );
|
||||
return;
|
||||
}
|
||||
|
||||
do_action( 'woocommerce_subscription_change_payment_method_via_pay_shortcode', $subscription );
|
||||
|
||||
ob_start();
|
||||
if ( ! $subscription instanceof WC_Subscription || $subscription->get_order_key() !== wc_clean( wp_unslash( $_GET['key'] ?? '' ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $subscription->get_order_key() == wc_clean( wp_unslash( $_GET['key'] ) ) ) {
|
||||
try {
|
||||
// We open an output buffer to suppress any error noise that might break the redirect.
|
||||
$output_buffer_opened = ob_start();
|
||||
|
||||
$subscription_billing_country = $subscription->get_billing_country();
|
||||
$subscription_billing_state = $subscription->get_billing_state();
|
||||
|
|
@ -359,6 +372,11 @@ class WC_Subscriptions_Change_Payment_Gateway {
|
|||
*/
|
||||
$subscription = wcs_get_subscription( $subscription->get_id() );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
wc_add_notice( __( 'There was an unexpected problem with your request. Please try again.', 'woocommerce-subscriptions' ), 'error' );
|
||||
return;
|
||||
}
|
||||
|
||||
$subscription->set_requires_manual_renewal( false );
|
||||
$subscription->save();
|
||||
|
||||
|
|
@ -383,9 +401,13 @@ class WC_Subscriptions_Change_Payment_Gateway {
|
|||
wp_safe_redirect( $result['redirect'] );
|
||||
exit;
|
||||
}
|
||||
} finally {
|
||||
// Close the output buffer (if we exited, this will have happened automatically, so this is primarily here
|
||||
// to ensure we clean-up in the event that we returned early).
|
||||
if ( $output_buffer_opened ) {
|
||||
ob_clean();
|
||||
}
|
||||
}
|
||||
|
||||
ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -595,7 +617,12 @@ class WC_Subscriptions_Change_Payment_Gateway {
|
|||
*/
|
||||
if ( ! $is_change_payment_method_request && $cart_contains_failed_renewal ) {
|
||||
$subscription = wcs_get_subscription( $renewal_order_cart_item['subscription_renewal']['subscription_id'] );
|
||||
$cart_contains_failed_manual_renewal = $subscription->is_manual();
|
||||
|
||||
// If the subscription has been deleted (which is a reason $subscription may be false), we probably do not
|
||||
// need to concern ourselves with a failed manual renewal.
|
||||
if ( $subscription ) {
|
||||
$cart_contains_failed_manual_renewal = $subscription->is_manual();
|
||||
}
|
||||
}
|
||||
|
||||
if ( apply_filters( 'wcs_payment_gateways_change_payment_method', $is_change_payment_method_request || ( $cart_contains_failed_renewal && ! $cart_contains_failed_manual_renewal ) ) ) {
|
||||
|
|
@ -728,6 +755,7 @@ class WC_Subscriptions_Change_Payment_Gateway {
|
|||
}
|
||||
|
||||
$subscription = wcs_get_subscription( absint( $wp->query_vars['order-pay'] ) );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
return $title;
|
||||
}
|
||||
|
|
@ -823,19 +851,19 @@ class WC_Subscriptions_Change_Payment_Gateway {
|
|||
|
||||
$subscription = wcs_get_subscription( absint( $wp->query_vars['order-pay'] ) );
|
||||
|
||||
if ( $subscription ) {
|
||||
wc_add_notice( __( 'Please log in to your account below to choose a new payment method for your subscription.', 'woocommerce-subscriptions' ), 'notice' );
|
||||
|
||||
ob_start();
|
||||
woocommerce_login_form( array(
|
||||
'redirect' => $subscription->get_change_payment_method_url(),
|
||||
'message' => wc_print_notices( true ),
|
||||
) );
|
||||
|
||||
$content = ob_get_clean();
|
||||
if ( ! $subscription ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
return $content;
|
||||
wc_add_notice( __( 'Please log in to your account below to choose a new payment method for your subscription.', 'woocommerce-subscriptions' ), 'notice' );
|
||||
|
||||
ob_start();
|
||||
woocommerce_login_form( array(
|
||||
'redirect' => $subscription->get_change_payment_method_url(),
|
||||
'message' => wc_print_notices( true ),
|
||||
) );
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/** Deprecated Functions **/
|
||||
|
|
|
|||
|
|
@ -542,6 +542,9 @@ class WC_Subscriptions_Core_Plugin {
|
|||
update_option( WC_Subscriptions_Admin::$option_prefix . WC_Subscriptions_Email_Notifications::$switch_setting_string, 'yes' );
|
||||
}
|
||||
|
||||
// Setup downloads table.
|
||||
WC_Subscription_Downloads::install();
|
||||
|
||||
update_option( WC_Subscriptions_Admin::$option_prefix . '_is_active', true );
|
||||
|
||||
set_transient( $this->get_activation_transient(), true, 60 * 60 );
|
||||
|
|
|
|||
|
|
@ -756,7 +756,18 @@ class WC_Subscriptions_Coupon {
|
|||
* @return bool Whether the current page is the coupon edit page.
|
||||
*/
|
||||
public static function is_coupon_edit_page() {
|
||||
$current_post_type = $_GET['post_type'] ?? ''; // phpcs:ignore
|
||||
global $pagenow;
|
||||
|
||||
$current_post_type = '';
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
||||
if ( 'post.php' === $pagenow && isset( $_GET['post'] ) ) {
|
||||
$current_post_type = get_post_type( $_GET['post'] );
|
||||
} elseif ( isset( $_GET['post_type'] ) ) {
|
||||
$current_post_type = $_GET['post_type'];
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
||||
|
||||
return 'shop_coupon' === $current_post_type;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -156,6 +156,11 @@ class WC_Subscriptions_Email_Notifications {
|
|||
switch ( current_action() ) {
|
||||
case 'woocommerce_scheduled_subscription_customer_notification_renewal':
|
||||
$subscription = wcs_get_subscription( $subscription_id );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ( $subscription->get_total() <= 0 ) {
|
||||
break;
|
||||
}
|
||||
|
|
@ -167,6 +172,11 @@ class WC_Subscriptions_Email_Notifications {
|
|||
break;
|
||||
case 'woocommerce_scheduled_subscription_customer_notification_trial_expiration':
|
||||
$subscription = wcs_get_subscription( $subscription_id );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ( $subscription->is_manual() ) {
|
||||
$notification = $emails['WCS_Email_Customer_Notification_Manual_Trial_Expiration'];
|
||||
} else {
|
||||
|
|
@ -268,7 +278,7 @@ class WC_Subscriptions_Email_Notifications {
|
|||
|
||||
$notification_settings = [
|
||||
[
|
||||
'name' => __( 'Customer Notifications', 'woocommerce-subscriptions' ),
|
||||
'name' => __( 'Customer notifications', 'woocommerce-subscriptions' ),
|
||||
'type' => 'title',
|
||||
'id' => WC_Subscriptions_Admin::$option_prefix . '_customer_notifications',
|
||||
/* translators: Link to WC Settings > Email. */
|
||||
|
|
@ -304,7 +314,9 @@ class WC_Subscriptions_Email_Notifications {
|
|||
],
|
||||
];
|
||||
|
||||
WC_Subscriptions_Admin::insert_setting_after( $settings, WC_Subscriptions_Admin::$option_prefix . '_miscellaneous', $notification_settings, 'multiple_settings', 'sectionend' );
|
||||
if ( ! WC_Subscriptions_Admin::insert_setting_after( $settings, WC_Subscriptions_Admin::$option_prefix . '_miscellaneous', $notification_settings, 'multiple_settings', 'sectionend' ) ) {
|
||||
$settings = array_merge( $settings, $notification_settings );
|
||||
}
|
||||
return $settings;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -275,9 +275,10 @@ class WC_Subscriptions_Extend_Store_Endpoint {
|
|||
// Add extra package data to array.
|
||||
if ( count( $packages ) ) {
|
||||
$packages = array_map(
|
||||
function( $key, $package, $index ) use ( $cart ) {
|
||||
function ( $key, $package, $index ) use ( $cart ) {
|
||||
$package['package_id'] = isset( $package['package_id'] ) ? $package['package_id'] : $key;
|
||||
$package['package_name'] = isset( $package['package_name'] ) ? $package['package_name'] : self::get_shipping_package_name( $package, $cart );
|
||||
$package['rates'] = apply_filters( 'woocommerce_package_rates', $package['rates'], $package );
|
||||
return $package;
|
||||
},
|
||||
array_keys( $packages ),
|
||||
|
|
|
|||
|
|
@ -130,11 +130,10 @@ class WC_Subscriptions_Manager {
|
|||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.12
|
||||
*/
|
||||
public static function process_renewal( $subscription_id, $required_status, $order_note ) {
|
||||
|
||||
$subscription = wcs_get_subscription( $subscription_id );
|
||||
|
||||
// If the subscription is using manual payments, the gateway isn't active or it manages scheduled payments
|
||||
if ( ! empty( $subscription ) && $subscription->has_status( $required_status ) && ( 0 == $subscription->get_total() || $subscription->is_manual() || '' == $subscription->get_payment_method() || ! $subscription->payment_method_supports( 'gateway_scheduled_payments' ) ) ) {
|
||||
if ( $subscription instanceof WC_Subscription && $subscription->has_status( $required_status ) && ( 0 === absint( $subscription->get_total() ) || $subscription->is_manual() || '' === $subscription->get_payment_method() || ! $subscription->payment_method_supports( 'gateway_scheduled_payments' ) ) ) {
|
||||
|
||||
// Always put the subscription on hold in case something goes wrong while trying to process renewal
|
||||
$subscription->update_status( 'on-hold', $order_note );
|
||||
|
|
@ -2075,6 +2074,11 @@ class WC_Subscriptions_Manager {
|
|||
*/
|
||||
public static function maybe_process_failed_renewal_for_repair( $subscription_id ) {
|
||||
$subscription = wcs_get_subscription( $subscription_id );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'true' === $subscription->get_meta( '_wcs_repaired_2_0_2_needs_failed_payment', true ) ) {
|
||||
// Always put the subscription on hold in case something goes wrong while trying to process renewal
|
||||
$subscription->update_status( 'on-hold', _x( 'Subscription renewal payment due:', 'used in order note as reason for why subscription status changed', 'woocommerce-subscriptions' ) );
|
||||
|
|
|
|||
|
|
@ -1281,18 +1281,6 @@ class WC_Subscriptions_Product {
|
|||
return $button_text;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a product is being marked as not purchasable because it is limited and the customer has a subscription,
|
||||
* but the current request is to resubscribe to the subscription, then mark it as purchasable.
|
||||
*
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_purchasable( $is_purchasable, $product ) {
|
||||
_deprecated_function( __METHOD__, '2.1', 'WCS_Limiter::is_purchasable_product' );
|
||||
return WCS_Limiter::is_purchasable_product( $is_purchasable, $product );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current session has an order awaiting payment for a subscription to a specific product line item.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ class WC_Subscriptions_Synchroniser {
|
|||
public static function add_settings( $settings ) {
|
||||
$synchronisation_settings = array(
|
||||
array(
|
||||
'name' => __( 'Synchronisation', 'woocommerce-subscriptions' ),
|
||||
'name' => __( 'Synchronization', 'woocommerce-subscriptions' ),
|
||||
'type' => 'title',
|
||||
// translators: placeholders are opening and closing link tags
|
||||
'desc' => sprintf( _x( 'Align subscription renewal to a specific day of the week, month or year. For example, the first day of the month. %1$sLearn more%2$s.', 'used in the general subscription options page', 'woocommerce-subscriptions' ), '<a href="' . esc_url( 'https://woocommerce.com/document/subscriptions/renewal-synchronisation/' ) . '">', '</a>' ),
|
||||
|
|
|
|||
|
|
@ -237,7 +237,12 @@ class WCS_Cached_Data_Manager extends WCS_Cache_Manager {
|
|||
protected function purge_subscription_user_cache( $subscription_id ) {
|
||||
wcs_deprecated_argument( __METHOD__, '2.3.0', sprintf( __( 'Customer subscription caching is now handled by %1$s and %2$s.', 'woocommerce-subscriptions' ), 'WCS_Customer_Store_Cached_CPT', 'WCS_Post_Meta_Cache_Manager' ) ); // phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment
|
||||
|
||||
$subscription = wcs_get_subscription( $subscription_id );
|
||||
$subscription = wcs_get_subscription( $subscription_id );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subscription_user_id = $subscription->get_user_id();
|
||||
$this->log( sprintf(
|
||||
'Clearing cache for user ID %1$s on %2$s hook.',
|
||||
|
|
|
|||
|
|
@ -604,6 +604,10 @@ class WCS_Cart_Renewal {
|
|||
|
||||
$subscription = wcs_get_subscription( $cart_renewal_item[ $this->cart_item_key ]['subscription_id'] );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
return $update_customer_data;
|
||||
}
|
||||
|
||||
$billing_address = array();
|
||||
if ( $checkout_object->checkout_fields['billing'] ) {
|
||||
foreach ( array_keys( $checkout_object->checkout_fields['billing'] ) as $field ) {
|
||||
|
|
@ -627,19 +631,6 @@ class WCS_Cart_Renewal {
|
|||
return $update_customer_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a product is being marked as not purchasable because it is limited and the customer has a subscription,
|
||||
* but the current request is to resubscribe to the subscription, then mark it as purchasable.
|
||||
*
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
|
||||
* @return bool
|
||||
*/
|
||||
public function is_purchasable( $is_purchasable, $product ) {
|
||||
_deprecated_function( __METHOD__, '2.1', 'WCS_Limiter::is_purchasable_renewal' );
|
||||
return WCS_Limiter::is_purchasable_renewal( $is_purchasable, $product );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag payment of manual renewal orders via an extra URL param.
|
||||
*
|
||||
|
|
@ -1195,7 +1186,12 @@ class WCS_Cart_Renewal {
|
|||
$cart_renewal_item = $this->cart_contains();
|
||||
|
||||
if ( false !== $cart_renewal_item ) {
|
||||
$subscription = wcs_get_subscription( $cart_renewal_item[ $this->cart_item_key ]['subscription_id'] );
|
||||
$subscription = wcs_get_subscription( $cart_renewal_item[ $this->cart_item_key ]['subscription_id'] );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subscription_updated = false;
|
||||
|
||||
foreach ( [ 'billing', 'shipping' ] as $address_type ) {
|
||||
|
|
@ -1231,6 +1227,10 @@ class WCS_Cart_Renewal {
|
|||
if ( false !== $cart_renewal_item ) {
|
||||
$subscription = wcs_get_subscription( $cart_renewal_item[ $this->cart_item_key ]['subscription_id'] );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Billing address is a required field.
|
||||
foreach ( $request['billing_address'] as $key => $value ) {
|
||||
if ( is_callable( [ $customer, "set_billing_$key" ] ) ) {
|
||||
|
|
|
|||
|
|
@ -186,19 +186,6 @@ class WCS_Cart_Resubscribe extends WCS_Cart_Renewal {
|
|||
return $cart_item_session_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a product is being marked as not purchasable because it is limited and the customer has a subscription,
|
||||
* but the current request is to resubscribe to the subscription, then mark it as purchasable.
|
||||
*
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
|
||||
* @return bool
|
||||
*/
|
||||
public function is_purchasable( $is_purchasable, $product ) {
|
||||
_deprecated_function( __METHOD__, '2.1', 'WCS_Limiter::is_purchasable_renewal' );
|
||||
return WCS_Limiter::is_purchasable_renewal( $is_purchasable, $product );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the cart to see if it contains a subscription resubscribe item.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -23,8 +23,6 @@ class WCS_Limiter {
|
|||
if ( wcs_is_frontend_request() || wcs_is_checkout_blocks_api_request() ) {
|
||||
add_filter( 'woocommerce_subscription_is_purchasable', __CLASS__ . '::is_purchasable_switch', 12, 2 );
|
||||
add_filter( 'woocommerce_subscription_variation_is_purchasable', __CLASS__ . '::is_purchasable_switch', 12, 2 );
|
||||
add_filter( 'woocommerce_subscription_is_purchasable', __CLASS__ . '::is_purchasable_renewal', 12, 2 );
|
||||
add_filter( 'woocommerce_subscription_variation_is_purchasable', __CLASS__ . '::is_purchasable_renewal', 12, 2 );
|
||||
add_filter( 'woocommerce_valid_order_statuses_for_order_again', array( __CLASS__, 'filter_order_again_statuses_for_limited_subscriptions' ) );
|
||||
}
|
||||
}
|
||||
|
|
@ -59,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.
|
||||
*
|
||||
|
|
@ -69,37 +104,66 @@ class WCS_Limiter {
|
|||
* @return bool
|
||||
*/
|
||||
public static function is_purchasable( $purchasable, $product ) {
|
||||
switch ( $product->get_type() ) {
|
||||
case 'subscription':
|
||||
case 'variable-subscription':
|
||||
if ( true === $purchasable && false === self::is_purchasable_product( $purchasable, $product ) ) {
|
||||
$purchasable = false;
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $purchasable;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If a product is limited and the customer already has a subscription, mark it as not purchasable.
|
||||
*
|
||||
|
|
@ -107,6 +171,17 @@ class WCS_Limiter {
|
|||
* @return bool
|
||||
*/
|
||||
public static function is_purchasable_product( $is_purchasable, $product ) {
|
||||
_deprecated_function( __METHOD__, '8.0.0', 'WCS_Limiter::is_product_limited' );
|
||||
return self::is_product_limited( $is_purchasable, $product );
|
||||
}
|
||||
|
||||
/**
|
||||
* If a product is limited and the customer already has a subscription, mark it as not purchasable.
|
||||
*
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.1, Moved from WC_Subscriptions_Product
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_product_limited( $is_purchasable, $product ) {
|
||||
|
||||
// Set up cache
|
||||
if ( ! isset( self::$is_purchasable_cache[ $product->get_id() ] ) ) {
|
||||
|
|
@ -197,33 +272,9 @@ class WCS_Limiter {
|
|||
* @return bool
|
||||
*/
|
||||
public static function is_purchasable_renewal( $is_purchasable, $product ) {
|
||||
if ( false === $is_purchasable && false === self::is_purchasable_product( $is_purchasable, $product ) ) {
|
||||
$resubscribe_cart_item = wcs_cart_contains_resubscribe();
|
||||
_deprecated_function( __METHOD__, '8.0.0', 'WCS_Limiter::is_product_limited' );
|
||||
|
||||
// Resubscribe logic
|
||||
if ( isset( $_GET['resubscribe'] ) || false !== $resubscribe_cart_item ) {
|
||||
$subscription_id = ( isset( $_GET['resubscribe'] ) ) ? absint( $_GET['resubscribe'] ) : $resubscribe_cart_item['subscription_resubscribe']['subscription_id'];
|
||||
$subscription = wcs_get_subscription( $subscription_id );
|
||||
|
||||
if ( false != $subscription && $subscription->has_product( $product->get_id() ) && wcs_can_user_resubscribe_to( $subscription ) ) {
|
||||
$is_purchasable = true;
|
||||
}
|
||||
|
||||
// Renewal logic
|
||||
} elseif ( isset( $_GET['subscription_renewal'] ) || wcs_cart_contains_renewal() ) {
|
||||
$is_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 ( $product->get_id() == $cart_item['product_id'] && ( isset( $cart_item['subscription_renewal'] ) || isset( $cart_item['subscription_resubscribe'] ) ) ) {
|
||||
$is_purchasable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $is_purchasable;
|
||||
return ! self::is_product_limited( $is_purchasable, $product );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -277,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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -187,14 +187,20 @@ class WCS_My_Account_Payment_Methods {
|
|||
return;
|
||||
}
|
||||
|
||||
// translators: 1: token display name, 2: opening link tag, 4: closing link tag, 3: opening link tag.
|
||||
$notice = sprintf( esc_html__( 'Would you like to update your subscriptions to use this new payment method - %1$s?%2$sYes%4$s | %3$sNo%4$s', 'woocommerce-subscriptions' ),
|
||||
$notice = sprintf(
|
||||
// translators: 1: token display name, 2: opening link tag, 4: closing link tag, 3: opening link tag.
|
||||
esc_html__( 'Would you like to update your subscriptions to use this new payment method - %1$s?%2$sYes%4$s | %3$sNo%4$s', 'woocommerce-subscriptions' ),
|
||||
$default_token->get_display_name(),
|
||||
'</br><a href="' . esc_url( add_query_arg( array(
|
||||
'update-subscription-tokens' => 'true',
|
||||
'token-id' => $default_token_id,
|
||||
'_wcsnonce' => wp_create_nonce( 'wcs-update-subscription-tokens' ),
|
||||
), wc_get_account_endpoint_url( 'payment-methods' ) ) ) . '"><strong>',
|
||||
'<br><a href="' . esc_url(
|
||||
add_query_arg(
|
||||
array(
|
||||
'update-subscription-tokens' => 'true',
|
||||
'token-id' => $default_token_id,
|
||||
'_wcsnonce' => wp_create_nonce( 'wcs-update-subscription-tokens' ),
|
||||
),
|
||||
wc_get_account_endpoint_url( 'payment-methods' )
|
||||
)
|
||||
) . '"><strong>',
|
||||
'<a href=""><strong>',
|
||||
'</strong></a>'
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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' ) );
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ class WCS_Staging {
|
|||
if ( 'subscriptions_install' === $source ) {
|
||||
$site_url = self::get_live_site_url();
|
||||
} elseif ( ! is_multisite() && defined( 'WP_SITEURL' ) ) {
|
||||
$site_url = WP_SITEURL;
|
||||
$site_url = WP_SITEURL; // @phpstan-ignore phpstanWP.wpConstant.fetch (Using constant for performance and staging-specific behavior)
|
||||
} else {
|
||||
$site_url = get_site_url();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -127,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 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -137,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 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -266,14 +266,14 @@ class WC_Subscriptions_Core_Payment_Gateways {
|
|||
}
|
||||
|
||||
$status_html .= '<span class="payment-method-features-info tips" data-tip="';
|
||||
$status_html .= esc_attr( '<strong><u>' . __( 'Supported features:', 'woocommerce-subscriptions' ) . '</u></strong></br>' . implode( '<br />', str_replace( '_', ' ', $core_features ) ) );
|
||||
$status_html .= esc_attr( '<strong><u>' . __( 'Supported features:', 'woocommerce-subscriptions' ) . '</u></strong><br>' . implode( '<br>', str_replace( '_', ' ', $core_features ) ) );
|
||||
|
||||
if ( ! empty( $subscription_features ) ) {
|
||||
$status_html .= esc_attr( '</br><strong><u>' . __( 'Subscription features:', 'woocommerce-subscriptions' ) . '</u></strong></br>' . implode( '<br />', str_replace( '_', ' ', $subscription_features ) ) );
|
||||
$status_html .= esc_attr( '<br><strong><u>' . __( 'Subscription features:', 'woocommerce-subscriptions' ) . '</u></strong><br>' . implode( '<br>', str_replace( '_', ' ', $subscription_features ) ) );
|
||||
}
|
||||
|
||||
if ( ! empty( $change_payment_method_features ) ) {
|
||||
$status_html .= esc_attr( '</br><strong><u>' . __( 'Change payment features:', 'woocommerce-subscriptions' ) . '</u></strong></br>' . implode( '<br />', str_replace( '_', ' ', $change_payment_method_features ) ) );
|
||||
$status_html .= esc_attr( '<br><strong><u>' . __( 'Change payment features:', 'woocommerce-subscriptions' ) . '</u></strong><br>' . implode( '<br>', str_replace( '_', ' ', $change_payment_method_features ) ) );
|
||||
}
|
||||
|
||||
$status_html .= '"></span>';
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ class WCS_PayPal_Standard_IPN_Handler extends WC_Gateway_Paypal_IPN_Handler {
|
|||
$transaction_order = wc_get_order( substr( $transaction_details['invoice'], strrpos( $transaction_details['invoice'], '-' ) + 1 ) );
|
||||
|
||||
// check if the failed signup has been previously recorded
|
||||
if ( wcs_get_objects_property( $transaction_order, 'id' ) !== $subscription->get_meta( '_paypal_failed_sign_up_recorded', true ) ) {
|
||||
if ( wcs_get_objects_property( $transaction_order, 'id' ) !== (int) $subscription->get_meta( '_paypal_failed_sign_up_recorded', true ) ) {
|
||||
$is_renewal_sign_up_after_failure = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,79 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Simple Subscription Product Legacy Class
|
||||
*
|
||||
* Extends WC_Product_Subscription to provide compatibility methods when running WooCommerce < 3.0.
|
||||
*
|
||||
* @class WC_Product_Subscription_Legacy
|
||||
* @package WooCommerce Subscriptions
|
||||
* @category Class
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0
|
||||
*
|
||||
*/
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class WC_Product_Subscription_Legacy extends WC_Product_Subscription {
|
||||
|
||||
var $subscription_price;
|
||||
|
||||
var $subscription_period;
|
||||
|
||||
var $subscription_period_interval;
|
||||
|
||||
var $subscription_length;
|
||||
|
||||
var $subscription_trial_length;
|
||||
|
||||
var $subscription_trial_period;
|
||||
|
||||
var $subscription_sign_up_fee;
|
||||
|
||||
/**
|
||||
* Create a simple subscription product object.
|
||||
*
|
||||
* @access public
|
||||
* @param mixed $product
|
||||
*/
|
||||
public function __construct( $product ) {
|
||||
parent::__construct( $product );
|
||||
$this->product_type = 'subscription';
|
||||
|
||||
// Load all meta fields
|
||||
$this->product_custom_fields = get_post_meta( $this->id );
|
||||
|
||||
// Convert selected subscription meta fields for easy access
|
||||
if ( ! empty( $this->product_custom_fields['_subscription_price'][0] ) ) {
|
||||
$this->subscription_price = $this->product_custom_fields['_subscription_price'][0];
|
||||
}
|
||||
|
||||
if ( ! empty( $this->product_custom_fields['_subscription_sign_up_fee'][0] ) ) {
|
||||
$this->subscription_sign_up_fee = $this->product_custom_fields['_subscription_sign_up_fee'][0];
|
||||
}
|
||||
|
||||
if ( ! empty( $this->product_custom_fields['_subscription_period'][0] ) ) {
|
||||
$this->subscription_period = $this->product_custom_fields['_subscription_period'][0];
|
||||
}
|
||||
|
||||
if ( ! empty( $this->product_custom_fields['_subscription_period_interval'][0] ) ) {
|
||||
$this->subscription_period_interval = $this->product_custom_fields['_subscription_period_interval'][0];
|
||||
}
|
||||
|
||||
if ( ! empty( $this->product_custom_fields['_subscription_length'][0] ) ) {
|
||||
$this->subscription_length = $this->product_custom_fields['_subscription_length'][0];
|
||||
}
|
||||
|
||||
if ( ! empty( $this->product_custom_fields['_subscription_trial_length'][0] ) ) {
|
||||
$this->subscription_trial_length = $this->product_custom_fields['_subscription_trial_length'][0];
|
||||
}
|
||||
|
||||
if ( ! empty( $this->product_custom_fields['_subscription_trial_period'][0] ) ) {
|
||||
$this->subscription_trial_period = $this->product_custom_fields['_subscription_trial_period'][0];
|
||||
}
|
||||
|
||||
$this->subscription_payment_sync_date = ( ! isset( $this->product_custom_fields['_subscription_payment_sync_date'][0] ) ) ? 0 : maybe_unserialize( $this->product_custom_fields['_subscription_payment_sync_date'][0] );
|
||||
$this->subscription_one_time_shipping = ( ! isset( $this->product_custom_fields['_subscription_one_time_shipping'][0] ) ) ? 'no' : $this->product_custom_fields['_subscription_one_time_shipping'][0];
|
||||
$this->subscription_limit = ( ! isset( $this->product_custom_fields['_subscription_limit'][0] ) ) ? 'no' : $this->product_custom_fields['_subscription_limit'][0];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Subscription Product Variation Legacy Class
|
||||
*
|
||||
* Extends WC_Product_Subscription_Variation to provide compatibility methods when running WooCommerce < 3.0.
|
||||
*
|
||||
* @class WC_Product_Subscription_Variation_Legacy
|
||||
* @package WooCommerce Subscriptions
|
||||
* @category Class
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0
|
||||
*
|
||||
*/
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class WC_Product_Subscription_Variation_Legacy extends WC_Product_Subscription_Variation {
|
||||
|
||||
/**
|
||||
* Set default array value for WC 3.0's data property.
|
||||
* @var array
|
||||
*/
|
||||
protected $data = array();
|
||||
|
||||
/**
|
||||
* Create a simple subscription product object.
|
||||
*
|
||||
* @access public
|
||||
* @param mixed $product
|
||||
*/
|
||||
public function __construct( $product, $args = array() ) {
|
||||
|
||||
parent::__construct( $product, $args = array() );
|
||||
|
||||
$this->parent_product_type = $this->product_type;
|
||||
|
||||
$this->product_type = 'subscription_variation';
|
||||
|
||||
$this->subscription_variation_level_meta_data = array(
|
||||
'subscription_price' => 0,
|
||||
'subscription_period' => '',
|
||||
'subscription_period_interval' => 'day',
|
||||
'subscription_length' => 0,
|
||||
'subscription_trial_length' => 0,
|
||||
'subscription_trial_period' => 'day',
|
||||
'subscription_sign_up_fee' => 0,
|
||||
'subscription_payment_sync_date' => 0,
|
||||
);
|
||||
|
||||
$this->variation_level_meta_data = array_merge( $this->variation_level_meta_data, $this->subscription_variation_level_meta_data );
|
||||
}
|
||||
|
||||
/* Copied from WC 2.6 WC_Product_Variation */
|
||||
|
||||
/**
|
||||
* __isset function.
|
||||
*
|
||||
* @param mixed $key
|
||||
* @return bool
|
||||
*/
|
||||
public function __isset( $key ) {
|
||||
if ( in_array( $key, array( 'variation_data', 'variation_has_stock' ) ) ) {
|
||||
return true;
|
||||
} elseif ( in_array( $key, array_keys( $this->variation_level_meta_data ) ) ) {
|
||||
return metadata_exists( 'post', $this->variation_id, '_' . $key );
|
||||
} elseif ( in_array( $key, array_keys( $this->variation_inherited_meta_data ) ) ) {
|
||||
return metadata_exists( 'post', $this->variation_id, '_' . $key ) || metadata_exists( 'post', $this->id, '_' . $key );
|
||||
} else {
|
||||
return metadata_exists( 'post', $this->id, '_' . $key );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method returns variation meta data if set, otherwise in most cases the data from the parent.
|
||||
*
|
||||
* We need to use the WC_Product_Variation's __get() method, not the one in WC_Product_Subscription_Variation,
|
||||
* which handles deprecation notices.
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get( $key ) {
|
||||
return WC_Product_Variation::__get( $key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a WC 3.0 method for variations.
|
||||
*
|
||||
* WC < 3.0 products have a get_parent() method, but this is not equivalent to the get_parent_id() method
|
||||
* introduced in WC 3.0, because it derives the parent from $this->post->post_parent, but for variations,
|
||||
* $this->post refers to the parent variable object's post, so $this->post->post_parent will be 0 under
|
||||
* normal circumstances. Because of that, we can rely on wcs_get_objects_property( $this, 'parent_id' )
|
||||
* and define this get_parent_id() method for variations even when WC 3.0 is not active.
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_parent_id() {
|
||||
return $this->id; // When WC < 3.0 is active, the ID property is the parent variable product's ID
|
||||
}
|
||||
}
|
||||
|
|
@ -1,441 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Variable Subscription Product Legacy Class
|
||||
*
|
||||
* Extends WC_Product_Variable_Subscription to provide compatibility methods when running WooCommerce < 3.0.
|
||||
*
|
||||
* @class WC_Product_Variable_Subscription_Legacy
|
||||
* @package WooCommerce Subscriptions
|
||||
* @category Class
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0
|
||||
*
|
||||
*/
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class WC_Product_Variable_Subscription_Legacy extends WC_Product_Variable_Subscription {
|
||||
|
||||
var $subscription_price;
|
||||
|
||||
var $subscription_period;
|
||||
|
||||
var $max_variation_period;
|
||||
|
||||
var $subscription_period_interval;
|
||||
|
||||
var $max_variation_period_interval;
|
||||
|
||||
var $product_type;
|
||||
|
||||
protected $prices_array;
|
||||
|
||||
/**
|
||||
* Create a simple subscription product object.
|
||||
*
|
||||
* @access public
|
||||
* @param mixed $product
|
||||
*/
|
||||
public function __construct( $product ) {
|
||||
|
||||
parent::__construct( $product );
|
||||
|
||||
$this->parent_product_type = $this->product_type;
|
||||
|
||||
$this->product_type = 'variable-subscription';
|
||||
|
||||
// Load all meta fields
|
||||
$this->product_custom_fields = get_post_meta( $this->id );
|
||||
|
||||
// Convert selected subscription meta fields for easy access
|
||||
if ( ! empty( $this->product_custom_fields['_subscription_price'][0] ) ) {
|
||||
$this->subscription_price = $this->product_custom_fields['_subscription_price'][0];
|
||||
}
|
||||
|
||||
if ( ! empty( $this->product_custom_fields['_subscription_sign_up_fee'][0] ) ) {
|
||||
$this->subscription_sign_up_fee = $this->product_custom_fields['_subscription_sign_up_fee'][0];
|
||||
}
|
||||
|
||||
if ( ! empty( $this->product_custom_fields['_subscription_period'][0] ) ) {
|
||||
$this->subscription_period = $this->product_custom_fields['_subscription_period'][0];
|
||||
}
|
||||
|
||||
if ( ! empty( $this->product_custom_fields['_subscription_period_interval'][0] ) ) {
|
||||
$this->subscription_period_interval = $this->product_custom_fields['_subscription_period_interval'][0];
|
||||
}
|
||||
|
||||
if ( ! empty( $this->product_custom_fields['_subscription_length'][0] ) ) {
|
||||
$this->subscription_length = $this->product_custom_fields['_subscription_length'][0];
|
||||
}
|
||||
|
||||
if ( ! empty( $this->product_custom_fields['_subscription_trial_length'][0] ) ) {
|
||||
$this->subscription_trial_length = $this->product_custom_fields['_subscription_trial_length'][0];
|
||||
}
|
||||
|
||||
if ( ! empty( $this->product_custom_fields['_subscription_trial_period'][0] ) ) {
|
||||
$this->subscription_trial_period = $this->product_custom_fields['_subscription_trial_period'][0];
|
||||
}
|
||||
|
||||
$this->subscription_payment_sync_date = 0;
|
||||
$this->subscription_one_time_shipping = ( ! isset( $this->product_custom_fields['_subscription_one_time_shipping'][0] ) ) ? 'no' : $this->product_custom_fields['_subscription_one_time_shipping'][0];
|
||||
$this->subscription_limit = ( ! isset( $this->product_custom_fields['_subscription_limit'][0] ) ) ? 'no' : $this->product_custom_fields['_subscription_limit'][0];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the min or max variation (active) price.
|
||||
*
|
||||
* This is a copy of WooCommerce < 2.4's get_variation_price() method, because 2.4.0 introduced a new
|
||||
* transient caching system which assumes asort() on prices yields correct results for min/max prices
|
||||
* (which it does for prices alone, but that's not the full story for subscription prices). Unfortunately,
|
||||
* the new caching system is also hard to hook into so we'll just use the old system instead as the
|
||||
* @see self::variable_product_sync() uses the old method also.
|
||||
*
|
||||
* @param string $min_or_max - min or max
|
||||
* @param boolean $display Whether the value is going to be displayed
|
||||
* @return string
|
||||
*/
|
||||
public function get_variation_price( $min_or_max = 'min', $display = false ) {
|
||||
$variation_id = $this->get_meta( '_' . $min_or_max . '_price_variation_id', true );
|
||||
|
||||
if ( $display ) {
|
||||
if ( $variation = wc_get_product( $variation_id ) ) {
|
||||
if ( 'incl' == get_option( 'woocommerce_tax_display_shop' ) ) {
|
||||
$price = wcs_get_price_including_tax( $variation );
|
||||
} else {
|
||||
$price = wcs_get_price_excluding_tax( $variation );
|
||||
}
|
||||
} else {
|
||||
$price = '';
|
||||
}
|
||||
} else {
|
||||
$price = $this->get_meta( '_price', true );
|
||||
}
|
||||
|
||||
return apply_filters( 'woocommerce_get_variation_price', $price, $this, $min_or_max, $display );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all sale and regular prices from all variations re-ordered after WC has done a standard sort, to reflect subscription terms.
|
||||
* The first and last element for each price type is the least and most expensive, respectively.
|
||||
*
|
||||
* @see WC_Product_Variable::get_variation_prices()
|
||||
* @param bool $include_taxes Should taxes be included in the prices.
|
||||
* @return array() Array of RAW prices, regular prices, and sale prices with keys set to variation ID.
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0
|
||||
*/
|
||||
public function get_variation_prices( $display = false ) {
|
||||
|
||||
$price_hash = $this->get_price_hash( $this, $display );
|
||||
|
||||
$this->prices_array[ $price_hash ] = parent::get_variation_prices( $display );
|
||||
|
||||
$children = array_keys( $this->prices_array[ $price_hash ]['price'] );
|
||||
sort( $children );
|
||||
|
||||
$min_max_data = $this->get_min_and_max_variation_data( $children );
|
||||
|
||||
$min_variation_id = $min_max_data['min']['variation_id'];
|
||||
$max_variation_id = $min_max_data['max']['variation_id'];
|
||||
|
||||
// Reorder the variable price arrays to reflect the min and max values so that WooCommerce will find them in the correct order
|
||||
foreach ( $this->prices_array as $price_hash => $prices ) {
|
||||
|
||||
// Loop over sale_price, regular_price & price values to update them on main array
|
||||
foreach ( $prices as $price_key => $variation_prices ) {
|
||||
|
||||
$min_price = $prices[ $price_key ][ $min_variation_id ];
|
||||
$max_price = $prices[ $price_key ][ $max_variation_id ];
|
||||
|
||||
unset( $prices[ $price_key ][ $min_variation_id ] );
|
||||
unset( $prices[ $price_key ][ $max_variation_id ] );
|
||||
|
||||
// append the minimum variation and prepend the maximum variation
|
||||
$prices[ $price_key ] = array( $min_variation_id => $min_price ) + $prices[ $price_key ];
|
||||
$prices[ $price_key ] += array( $max_variation_id => $max_price );
|
||||
|
||||
$this->prices_array[ $price_hash ][ $price_key ] = $prices[ $price_key ];
|
||||
}
|
||||
}
|
||||
|
||||
$this->subscription_price = $min_max_data['min']['price'];
|
||||
$this->subscription_period = $min_max_data['min']['period'];
|
||||
$this->subscription_period_interval = $min_max_data['min']['interval'];
|
||||
|
||||
$this->max_variation_price = $min_max_data['max']['price'];
|
||||
$this->max_variation_period = $min_max_data['max']['period'];
|
||||
$this->max_variation_period_interval = $min_max_data['max']['interval'];
|
||||
|
||||
$this->min_variation_price = $min_max_data['min']['price'];
|
||||
$this->min_variation_regular_price = $min_max_data['min']['regular_price'];
|
||||
|
||||
return $this->prices_array[ $price_hash ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create unique cache key based on the tax location (affects displayed/cached prices), product version and active price filters.
|
||||
* DEVELOPERS should filter this hash if offering conditional pricing to keep it unique.
|
||||
*
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0
|
||||
* @param WC_Product
|
||||
* @param bool $display Are prices for display? If so, taxes will be calculated.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_price_hash( $display = false ) {
|
||||
global $wp_filter;
|
||||
|
||||
if ( $display ) {
|
||||
$price_hash = array( get_option( 'woocommerce_tax_display_shop', 'excl' ), WC_Tax::get_rates() );
|
||||
} else {
|
||||
$price_hash = array( false );
|
||||
}
|
||||
|
||||
$filter_names = array( 'woocommerce_variation_prices_price', 'woocommerce_variation_prices_regular_price', 'woocommerce_variation_prices_sale_price' );
|
||||
|
||||
foreach ( $filter_names as $filter_name ) {
|
||||
if ( ! empty( $wp_filter[ $filter_name ] ) ) {
|
||||
$price_hash[ $filter_name ] = array();
|
||||
|
||||
foreach ( $wp_filter[ $filter_name ] as $priority => $callbacks ) {
|
||||
$price_hash[ $filter_name ][] = array_values( wp_list_pluck( $callbacks, 'function' ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$price_hash = md5( json_encode( apply_filters( 'woocommerce_get_variation_prices_hash', $price_hash, $this, $display ) ) );
|
||||
|
||||
return $price_hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync variable product prices with the children lowest/highest prices.
|
||||
*
|
||||
* @param int $product_id The ID of the product.
|
||||
* @return void
|
||||
*/
|
||||
public function variable_product_sync( $product_id = 0 ) {
|
||||
|
||||
WC_Product_Variable::variable_product_sync( $product_id );
|
||||
|
||||
$child_variation_ids = $this->get_children( true );
|
||||
|
||||
if ( $child_variation_ids ) {
|
||||
|
||||
$min_max_data = wcs_get_min_max_variation_data( $this, $child_variation_ids );
|
||||
|
||||
$this->set_min_and_max_variation_data( $min_max_data, $child_variation_ids );
|
||||
|
||||
update_post_meta( $this->id, '_min_price_variation_id', $min_max_data['min']['variation_id'] );
|
||||
update_post_meta( $this->id, '_max_price_variation_id', $min_max_data['max']['variation_id'] );
|
||||
|
||||
update_post_meta( $this->id, '_price', $min_max_data['min']['price'] );
|
||||
update_post_meta( $this->id, '_min_variation_price', $min_max_data['min']['price'] );
|
||||
update_post_meta( $this->id, '_max_variation_price', $min_max_data['max']['price'] );
|
||||
update_post_meta( $this->id, '_min_variation_regular_price', $min_max_data['min']['regular_price'] );
|
||||
update_post_meta( $this->id, '_max_variation_regular_price', $min_max_data['max']['regular_price'] );
|
||||
update_post_meta( $this->id, '_min_variation_sale_price', $min_max_data['min']['sale_price'] );
|
||||
update_post_meta( $this->id, '_max_variation_sale_price', $min_max_data['max']['sale_price'] );
|
||||
|
||||
update_post_meta( $this->id, '_min_variation_period', $min_max_data['min']['period'] );
|
||||
update_post_meta( $this->id, '_max_variation_period', $min_max_data['max']['period'] );
|
||||
update_post_meta( $this->id, '_min_variation_period_interval', $min_max_data['min']['interval'] );
|
||||
update_post_meta( $this->id, '_max_variation_period_interval', $min_max_data['max']['interval'] );
|
||||
|
||||
update_post_meta( $this->id, '_subscription_price', $min_max_data['min']['price'] );
|
||||
update_post_meta( $this->id, '_subscription_sign_up_fee', $min_max_data['subscription']['signup-fee'] );
|
||||
update_post_meta( $this->id, '_subscription_period', $min_max_data['min']['period'] );
|
||||
update_post_meta( $this->id, '_subscription_period_interval', $min_max_data['min']['interval'] );
|
||||
update_post_meta( $this->id, '_subscription_trial_period', $min_max_data['subscription']['trial_period'] );
|
||||
update_post_meta( $this->id, '_subscription_trial_length', $min_max_data['subscription']['trial_length'] );
|
||||
update_post_meta( $this->id, '_subscription_length', $min_max_data['subscription']['length'] );
|
||||
|
||||
$this->subscription_price = $min_max_data['min']['price'];
|
||||
$this->subscription_period = $min_max_data['min']['period'];
|
||||
$this->subscription_period_interval = $min_max_data['min']['interval'];
|
||||
$this->subscription_sign_up_fee = $min_max_data['subscription']['signup-fee'];
|
||||
$this->subscription_trial_period = $min_max_data['subscription']['trial_period'];
|
||||
$this->subscription_trial_length = $min_max_data['subscription']['trial_length'];
|
||||
$this->subscription_length = $min_max_data['subscription']['length'];
|
||||
|
||||
if ( function_exists( 'wc_delete_product_transients' ) ) {
|
||||
wc_delete_product_transients( $this->id );
|
||||
} else {
|
||||
WC()->clear_product_transients( $this->id );
|
||||
}
|
||||
} else { // No variations yet
|
||||
|
||||
$this->subscription_price = '';
|
||||
$this->subscription_sign_up_fee = '';
|
||||
$this->subscription_period = 'day';
|
||||
$this->subscription_period_interval = 1;
|
||||
$this->subscription_trial_period = 'day';
|
||||
$this->subscription_trial_length = 1;
|
||||
$this->subscription_length = 0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the price in html format.
|
||||
*
|
||||
* @access public
|
||||
* @param string $price (default: '')
|
||||
* @return string
|
||||
*/
|
||||
public function get_price_html( $price = '' ) {
|
||||
|
||||
if ( ! isset( $this->subscription_period ) || ! isset( $this->subscription_period_interval ) || ! isset( $this->max_variation_period ) || ! isset( $this->max_variation_period_interval ) ) {
|
||||
$this->variable_product_sync();
|
||||
}
|
||||
|
||||
// Only create the subscription price string when a price has been set
|
||||
if ( $this->subscription_price !== '' ) {
|
||||
|
||||
$price = '';
|
||||
|
||||
if ( $this->is_on_sale() && isset( $this->min_variation_price ) && $this->min_variation_regular_price !== $this->get_price() ) {
|
||||
|
||||
if ( ! $this->min_variation_price || $this->min_variation_price !== $this->max_variation_price ) {
|
||||
$price .= wcs_get_price_html_from_text( $this );
|
||||
}
|
||||
|
||||
$variation_id = $this->get_meta( '_min_price_variation_id', true );
|
||||
$variation = wc_get_product( $variation_id );
|
||||
$tax_display_mode = get_option( 'woocommerce_tax_display_shop' );
|
||||
|
||||
$sale_price_args = array(
|
||||
'qty' => 1,
|
||||
'price' => $variation->get_sale_price(),
|
||||
);
|
||||
$regular_price_args = array(
|
||||
'qty' => 1,
|
||||
'price' => $variation->get_regular_price(),
|
||||
);
|
||||
|
||||
if ( 'incl' == $tax_display_mode ) {
|
||||
$sale_price = wcs_get_price_including_tax( $variation, $sale_price_args );
|
||||
$regular_price = wcs_get_price_including_tax( $variation, $regular_price_args );
|
||||
} else {
|
||||
$sale_price = wcs_get_price_excluding_tax( $variation, $sale_price_args );
|
||||
$regular_price = wcs_get_price_excluding_tax( $variation, $regular_price_args );
|
||||
}
|
||||
|
||||
$price .= $this->get_price_html_from_to( $regular_price, $sale_price );
|
||||
|
||||
} else {
|
||||
|
||||
if ( $this->min_variation_price !== $this->max_variation_price ) {
|
||||
$price .= wcs_get_price_html_from_text( $this );
|
||||
}
|
||||
|
||||
$price .= wc_price( $this->get_variation_price( 'min', true ) );
|
||||
|
||||
}
|
||||
|
||||
// Make sure the price contains "From:" when billing schedule differs between variations
|
||||
if ( false === strpos( $price, wcs_get_price_html_from_text( $this ) ) ) {
|
||||
if ( $this->subscription_period !== $this->max_variation_period ) {
|
||||
$price = wcs_get_price_html_from_text( $this ) . $price;
|
||||
} elseif ( $this->subscription_period_interval !== $this->max_variation_period_interval ) {
|
||||
$price = wcs_get_price_html_from_text( $this ) . $price;
|
||||
}
|
||||
}
|
||||
|
||||
$price .= $this->get_price_suffix();
|
||||
|
||||
$price = WC_Subscriptions_Product::get_price_string( $this, array( 'price' => $price ) );
|
||||
}
|
||||
|
||||
return apply_filters( 'woocommerce_variable_subscription_price_html', $price, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the WC_Data::get_meta() function when WC < 3.0 is active.
|
||||
*
|
||||
* @param string $meta_key
|
||||
* @param bool $single
|
||||
* @param string $context
|
||||
* @return object WC_Product_Subscription or WC_Product_Subscription_Variation
|
||||
*/
|
||||
function get_meta( $meta_key = '', $single = true, $context = 'view' ) {
|
||||
return $this->get_meta( $meta_key, $single );
|
||||
}
|
||||
|
||||
/**
|
||||
* get_child function.
|
||||
*
|
||||
* @access public
|
||||
* @param mixed $child_id
|
||||
* @return object WC_Product_Subscription or WC_Product_Subscription_Variation
|
||||
*/
|
||||
public function get_child( $child_id ) {
|
||||
return wc_get_product( $child_id, array(
|
||||
'product_type' => 'Subscription_Variation',
|
||||
'parent_id' => $this->id,
|
||||
'parent' => $this,
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default attributes.
|
||||
*
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0
|
||||
* @param string $context
|
||||
* @return array
|
||||
*/
|
||||
public function get_default_attributes( $context = 'view' ) {
|
||||
return $this->get_variation_default_attributes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the product's min and max variation data.
|
||||
*
|
||||
* @param array $min_and_max_data The min and max variation data returned by @see wcs_get_min_max_variation_data(). Optional.
|
||||
* @param array $variation_ids The visible child variation IDs. Optional. By default this value be generated by @see WC_Product_Variable->get_children( true ).
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
|
||||
*/
|
||||
public function set_min_and_max_variation_data( $min_and_max_data = array(), $variation_ids = array() ) {
|
||||
|
||||
if ( empty( $variation_ids ) ) {
|
||||
$variation_ids = $this->get_children( true );
|
||||
}
|
||||
|
||||
if ( empty( $min_and_max_data ) ) {
|
||||
$min_and_max_data = wcs_get_min_max_variation_data( $this, $variation_ids );
|
||||
}
|
||||
|
||||
update_post_meta( $this->id, '_min_max_variation_data', $min_and_max_data, true );
|
||||
update_post_meta( $this->id, '_min_max_variation_ids_hash', $this->get_variation_ids_hash( $variation_ids ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the min and max variation data.
|
||||
*
|
||||
* This is a wrapper for @see wcs_get_min_max_variation_data() but to avoid calling
|
||||
* that resource intensive function multiple times per request, check the value
|
||||
* stored in meta or cached in memory before calling that function.
|
||||
*
|
||||
* @param array $variation_ids An array of variation IDs.
|
||||
* @return array The variable product's min and max variation data.
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0
|
||||
*/
|
||||
public function get_min_and_max_variation_data( $variation_ids ) {
|
||||
$variation_ids_hash = $this->get_variation_ids_hash( $variation_ids );
|
||||
|
||||
// If this variable product has no min and max variation data, set it.
|
||||
if ( ! metadata_exists( 'post', $this->id, '_min_max_variation_ids_hash' ) ) {
|
||||
$this->set_min_and_max_variation_data();
|
||||
}
|
||||
|
||||
if ( $variation_ids_hash === $this->get_meta( '_min_max_variation_ids_hash', true ) ) {
|
||||
$min_and_max_variation_data = $this->get_meta( '_min_max_variation_data', true );
|
||||
} elseif ( ! empty( $this->min_max_variation_data[ $variation_ids_hash ] ) ) {
|
||||
$min_and_max_variation_data = $this->min_max_variation_data[ $variation_ids_hash ];
|
||||
} else {
|
||||
$min_and_max_variation_data = wcs_get_min_max_variation_data( $this, $variation_ids );
|
||||
$this->min_max_variation_data[ $variation_ids_hash ] = $min_and_max_variation_data;
|
||||
}
|
||||
|
||||
return $min_and_max_variation_data;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,776 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Subscription Legacy Object
|
||||
*
|
||||
* Extends WC_Subscription to provide WC 3.0 methods when running WooCommerce < 3.0.
|
||||
*
|
||||
* @class WC_Subscription_Legacy
|
||||
* @version 1.0.0 - Migrated from WooCommerce Subscriptions v2.1
|
||||
* @package WooCommerce Subscriptions/Classes
|
||||
* @category Class
|
||||
* @author Brent Shepherd
|
||||
*/
|
||||
|
||||
class WC_Subscription_Legacy extends WC_Subscription {
|
||||
|
||||
protected $schedule;
|
||||
|
||||
protected $status_transition = false;
|
||||
|
||||
/**
|
||||
* Whether the object has been read. Pre WC 3.0 subscription objects are always read by default.
|
||||
* Provides an accessible variable equivalent to WC_Data::$object_read pre WC 3.0.
|
||||
*
|
||||
* @protected boolean
|
||||
*/
|
||||
protected $object_read = true;
|
||||
|
||||
/**
|
||||
* Initialize the subscription object.
|
||||
*
|
||||
* @param int|WC_Subscription $subscription
|
||||
*/
|
||||
public function __construct( $subscription ) {
|
||||
|
||||
parent::__construct( $subscription );
|
||||
|
||||
$this->order_type = 'shop_subscription';
|
||||
|
||||
$this->schedule = new stdClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates a subscription from the loaded post data.
|
||||
*
|
||||
* @param mixed $result
|
||||
*/
|
||||
public function populate( $result ) {
|
||||
parent::populate( $result );
|
||||
|
||||
if ( $this->post->post_parent > 0 ) {
|
||||
$this->order = wc_get_order( $this->post->post_parent );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique ID for this object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_id() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parent order ID.
|
||||
*
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0
|
||||
* @return int
|
||||
*/
|
||||
public function get_parent_id() {
|
||||
return $this->post->post_parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets order currency.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_currency() {
|
||||
return $this->get_order_currency();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get customer_note.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_customer_note( $context = 'view' ) {
|
||||
return $this->customer_note;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prices_include_tax.
|
||||
*
|
||||
* @param string $context
|
||||
* @return bool
|
||||
*/
|
||||
public function get_prices_include_tax( $context = 'view' ) {
|
||||
return $this->prices_include_tax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the payment method.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_payment_method( $context = 'view' ) {
|
||||
return $this->payment_method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the payment method's title.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_payment_method_title( $context = 'view' ) {
|
||||
return $this->payment_method_title;
|
||||
}
|
||||
|
||||
/** Address Getters **/
|
||||
|
||||
/**
|
||||
* Get billing_first_name.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_billing_first_name( $context = 'view' ) {
|
||||
return $this->billing_first_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get billing_last_name.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_billing_last_name( $context = 'view' ) {
|
||||
return $this->billing_last_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get billing_company.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_billing_company( $context = 'view' ) {
|
||||
return $this->billing_company;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get billing_address_1.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_billing_address_1( $context = 'view' ) {
|
||||
return $this->billing_address_1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get billing_address_2.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string $value
|
||||
*/
|
||||
public function get_billing_address_2( $context = 'view' ) {
|
||||
return $this->billing_address_2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get billing_city.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string $value
|
||||
*/
|
||||
public function get_billing_city( $context = 'view' ) {
|
||||
return $this->billing_city;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get billing_state.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_billing_state( $context = 'view' ) {
|
||||
return $this->billing_state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get billing_postcode.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_billing_postcode( $context = 'view' ) {
|
||||
return $this->billing_postcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get billing_country.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_billing_country( $context = 'view' ) {
|
||||
return $this->billing_country;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get billing_email.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_billing_email( $context = 'view' ) {
|
||||
return $this->billing_email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get billing_phone.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_billing_phone( $context = 'view' ) {
|
||||
return $this->billing_phone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shipping_first_name.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_shipping_first_name( $context = 'view' ) {
|
||||
return $this->shipping_first_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shipping_last_name.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_shipping_last_name( $context = 'view' ) {
|
||||
return $this->shipping_last_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shipping_company.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_shipping_company( $context = 'view' ) {
|
||||
return $this->shipping_company;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shipping_address_1.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_shipping_address_1( $context = 'view' ) {
|
||||
return $this->shipping_address_1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shipping_address_2.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_shipping_address_2( $context = 'view' ) {
|
||||
return $this->shipping_address_2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shipping_city.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_shipping_city( $context = 'view' ) {
|
||||
return $this->shipping_city;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shipping_state.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_shipping_state( $context = 'view' ) {
|
||||
return $this->shipping_state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shipping_postcode.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_shipping_postcode( $context = 'view' ) {
|
||||
return $this->shipping_postcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shipping_country.
|
||||
*
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_shipping_country( $context = 'view' ) {
|
||||
return $this->shipping_country;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get order key.
|
||||
*
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0
|
||||
* @param string $context
|
||||
* @return string
|
||||
*/
|
||||
public function get_order_key( $context = 'view' ) {
|
||||
return $this->order_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get date_created.
|
||||
*
|
||||
* Used by parent::get_date()
|
||||
*
|
||||
* @throws WC_Data_Exception
|
||||
* @return DateTime|NULL object if the date is set or null if there is no date.
|
||||
*/
|
||||
public function get_date_created( $context = 'view' ) {
|
||||
|
||||
if ( '0000-00-00 00:00:00' != $this->post->post_date_gmt ) {
|
||||
$datetime = new WC_DateTime( $this->post->post_date_gmt, new DateTimeZone( 'UTC' ) );
|
||||
$datetime->setTimezone( new DateTimeZone( wc_timezone_string() ) );
|
||||
} else {
|
||||
$datetime = new WC_DateTime( $this->post->post_date, new DateTimeZone( wc_timezone_string() ) );
|
||||
}
|
||||
|
||||
// Cache it in $this->schedule for backward compatibility
|
||||
if ( ! isset( $this->schedule->start ) ) {
|
||||
$this->schedule->start = wcs_get_datetime_utc_string( $datetime );
|
||||
}
|
||||
|
||||
return $datetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get date_modified.
|
||||
*
|
||||
* Used by parent::get_date()
|
||||
*
|
||||
* @throws WC_Data_Exception
|
||||
* @return DateTime|NULL object if the date is set or null if there is no date.
|
||||
*/
|
||||
public function get_date_modified( $context = 'view' ) {
|
||||
|
||||
if ( '0000-00-00 00:00:00' != $this->post->post_modified_gmt ) {
|
||||
$datetime = new WC_DateTime( $this->post->post_modified_gmt, new DateTimeZone( 'UTC' ) );
|
||||
$datetime->setTimezone( new DateTimeZone( wc_timezone_string() ) );
|
||||
} else {
|
||||
$datetime = new WC_DateTime( $this->post->post_modified, new DateTimeZone( wc_timezone_string() ) );
|
||||
}
|
||||
|
||||
return $datetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given line item on the subscription had a sign-up fee, and if so, return the value of the sign-up fee.
|
||||
*
|
||||
* The single quantity sign-up fee will be returned instead of the total sign-up fee paid. For example, if 3 x a product
|
||||
* with a 10 BTC sign-up fee was purchased, a total 30 BTC was paid as the sign-up fee but this function will return 10 BTC.
|
||||
*
|
||||
* @param array|int Either an order item (in the array format returned by self::get_items()) or the ID of an order item.
|
||||
* @param string $tax_inclusive_or_exclusive Whether or not to adjust sign up fee if prices inc tax - ensures that the sign up fee paid amount includes the paid tax if inc
|
||||
* @return bool
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
|
||||
*/
|
||||
public function get_items_sign_up_fee( $line_item, $tax_inclusive_or_exclusive = 'exclusive_of_tax' ) {
|
||||
|
||||
if ( ! is_array( $line_item ) ) {
|
||||
$line_item = wcs_get_order_item( $line_item, $this );
|
||||
}
|
||||
|
||||
$parent_order = $this->get_parent();
|
||||
|
||||
// If there was no original order, nothing was paid up-front which means no sign-up fee
|
||||
if ( false == $parent_order ) {
|
||||
|
||||
$sign_up_fee = 0;
|
||||
|
||||
} else {
|
||||
|
||||
$original_order_item = '';
|
||||
|
||||
// Find the matching item on the order
|
||||
foreach ( $parent_order->get_items() as $order_item ) {
|
||||
if ( wcs_get_canonical_product_id( $line_item ) == wcs_get_canonical_product_id( $order_item ) ) {
|
||||
$original_order_item = $order_item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// No matching order item, so this item wasn't purchased in the original order
|
||||
if ( empty( $original_order_item ) ) {
|
||||
|
||||
$sign_up_fee = 0;
|
||||
|
||||
} elseif ( isset( $line_item['item_meta']['_has_trial'] ) ) {
|
||||
|
||||
// Sign up is total amount paid for this item on original order when item has a free trial
|
||||
$sign_up_fee = $original_order_item['line_total'] / $original_order_item['qty'];
|
||||
} elseif ( isset( $original_order_item['item_meta']['_synced_sign_up_fee'] ) ) {
|
||||
$sign_up_fee = $original_order_item['item_meta']['_synced_sign_up_fee'] / $original_order_item['qty'];
|
||||
|
||||
// The synced sign up fee meta contains the raw product sign up fee, if the subscription totals are inclusive of tax, we need to adjust the synced sign up fee to match tax inclusivity.
|
||||
if ( $this->get_prices_include_tax() ) {
|
||||
$line_item_total = $original_order_item['line_total'] + $original_order_item['line_tax'];
|
||||
$signup_fee_portion = $sign_up_fee / $line_item_total;
|
||||
$sign_up_fee = $original_order_item['line_total'] * $signup_fee_portion;
|
||||
}
|
||||
} else {
|
||||
|
||||
// Sign-up fee is any amount on top of recurring amount
|
||||
$sign_up_fee = max( $original_order_item['line_total'] / $original_order_item['qty'] - $line_item['line_total'] / $line_item['qty'], 0 );
|
||||
}
|
||||
|
||||
// If prices don't inc tax, ensure that the sign up fee amount includes the tax.
|
||||
if ( 'inclusive_of_tax' === $tax_inclusive_or_exclusive && ! empty( $original_order_item ) && ! empty( $sign_up_fee ) ) {
|
||||
$sign_up_fee_proportion = $sign_up_fee / ( $original_order_item['line_total'] / $original_order_item['qty'] );
|
||||
$sign_up_fee_tax = $original_order_item['line_tax'] * $sign_up_fee_proportion;
|
||||
|
||||
$sign_up_fee += $sign_up_fee_tax;
|
||||
$sign_up_fee = wc_format_decimal( $sign_up_fee, wc_get_price_decimals() );
|
||||
}
|
||||
}
|
||||
|
||||
return apply_filters( 'woocommerce_subscription_items_sign_up_fee', $sign_up_fee, $line_item, $this, $tax_inclusive_or_exclusive );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to make sure when WC_Subscription calls get_prop() from
|
||||
* it's new getters that the property is both retrieved from the legacy class
|
||||
* property and done so from post meta.
|
||||
*
|
||||
* For inherited dates props, like date_created, date_modified, date_paid,
|
||||
* date_completed, we want to use our own get_date() function rather simply
|
||||
* getting the stored value. Otherwise, we either get the prop set in memory
|
||||
* or post meta if it's not set yet, because __get() in WC < 3.0 would fallback
|
||||
* to post meta.
|
||||
*
|
||||
* @param string
|
||||
* @param string
|
||||
* @return mixed
|
||||
*/
|
||||
protected function get_prop( $prop, $context = 'view' ) {
|
||||
|
||||
if ( 'switch_data' == $prop ) {
|
||||
$prop = 'subscription_switch_data';
|
||||
}
|
||||
|
||||
// The requires manual renewal prop uses boolean values but is stored as a string so needs special handling, it also needs to be handled before the checks on $this->$prop to avoid triggering __isset() & __get() magic methods for $this->requires_manual_renewal
|
||||
if ( 'requires_manual_renewal' === $prop ) {
|
||||
$value = $this->get_meta( '_' . $prop, true );
|
||||
|
||||
if ( 'false' === $value || '' === $value ) {
|
||||
$value = false;
|
||||
} else {
|
||||
$value = true;
|
||||
}
|
||||
} elseif ( ! isset( $this->$prop ) || empty( $this->$prop ) ) {
|
||||
$value = $this->get_meta( '_' . $prop, true );
|
||||
} else {
|
||||
$value = $this->$prop;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stored date for a specific schedule.
|
||||
*
|
||||
* @param string $date_type 'date_created', 'trial_end', 'next_payment', 'last_order_date_created' or 'end'
|
||||
*/
|
||||
protected function get_date_prop( $date_type ) {
|
||||
|
||||
$datetime = parent::get_date_prop( $date_type );
|
||||
|
||||
// Cache the string equalivent of it in $this->schedule for backward compatibility
|
||||
if ( ! isset( $this->schedule->{$date_type} ) ) {
|
||||
if ( ! is_object( $datetime ) ) {
|
||||
$this->schedule->{$date_type} = 0;
|
||||
} else {
|
||||
$this->schedule->{$date_type} = wcs_get_datetime_utc_string( $datetime );
|
||||
}
|
||||
}
|
||||
|
||||
return wcs_get_datetime_from( wcs_date_to_time( $datetime ) );
|
||||
}
|
||||
|
||||
/*** Setters *****************************************************/
|
||||
|
||||
/**
|
||||
* Set the unique ID for this object.
|
||||
*
|
||||
* @param int
|
||||
*/
|
||||
public function set_id( $id ) {
|
||||
$this->id = absint( $id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set parent order ID. We don't use WC_Abstract_Order::set_parent_id() because we want to allow false
|
||||
* parent IDs, like 0.
|
||||
*
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0
|
||||
* @param int $value
|
||||
*/
|
||||
public function set_parent_id( $value ) {
|
||||
// Update the parent in the database
|
||||
wp_update_post( array(
|
||||
'ID' => $this->id,
|
||||
'post_parent' => $value,
|
||||
) );
|
||||
|
||||
// And update the parent in memory
|
||||
$this->post->post_parent = $value;
|
||||
$this->order = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set subscription status.
|
||||
*
|
||||
* @param string $new_status Status to change the order to. No internal wc- prefix is required.
|
||||
* @return array details of change
|
||||
*/
|
||||
public function set_status( $new_status, $note = '', $manual_update = false ) {
|
||||
|
||||
$old_status = $this->get_status();
|
||||
$prefix = substr( $new_status, 0, 3 );
|
||||
$new_status = 'wc-' === $prefix ? substr( $new_status, 3 ) : $new_status;
|
||||
|
||||
wp_update_post(
|
||||
array(
|
||||
'ID' => $this->get_id(),
|
||||
'post_status' => wcs_maybe_prefix_key( $new_status, 'wc-' ),
|
||||
)
|
||||
);
|
||||
$this->post_status = $this->post->post_status = wcs_maybe_prefix_key( $new_status, 'wc-' );
|
||||
|
||||
if ( $old_status !== $new_status ) {
|
||||
$this->status_transition = array(
|
||||
'from' => ! empty( $this->status_transition['from'] ) ? $this->status_transition['from'] : $old_status,
|
||||
'to' => $new_status,
|
||||
'note' => $note,
|
||||
'manual' => (bool) $manual_update,
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'from' => $old_status,
|
||||
'to' => $new_status,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to make sure when WC_Subscription calls set_prop() that property is
|
||||
* both set in the legacy class property and saved in post meta immediately.
|
||||
*
|
||||
* @param string $prop
|
||||
* @param mixed $value
|
||||
*/
|
||||
protected function set_prop( $prop, $value ) {
|
||||
|
||||
if ( 'switch_data' == $prop ) {
|
||||
$prop = 'subscription_switch_data';
|
||||
}
|
||||
|
||||
$this->$prop = $value;
|
||||
|
||||
// The requires manual renewal prop uses boolean values but it stored as a string
|
||||
if ( 'requires_manual_renewal' === $prop ) {
|
||||
if ( false === $value || '' === $value ) {
|
||||
$value = 'false';
|
||||
} else {
|
||||
$value = 'true';
|
||||
}
|
||||
}
|
||||
|
||||
update_post_meta( $this->get_id(), '_' . $prop, $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the stored date for a specific schedule.
|
||||
*
|
||||
* @param string $date_type 'trial_end', 'next_payment', 'cancelled', 'payment_retry' or 'end'
|
||||
* @param int $value UTC timestamp
|
||||
*/
|
||||
protected function set_date_prop( $date_type, $value ) {
|
||||
$datetime = wcs_get_datetime_from( $value );
|
||||
$date = ! is_null( $datetime ) ? wcs_get_datetime_utc_string( $datetime ) : 0;
|
||||
|
||||
$this->set_prop( $this->get_date_prop_key( $date_type ), $date );
|
||||
$this->schedule->{$date_type} = $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a certain date type for the last order on the subscription.
|
||||
*
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0
|
||||
* @param string $date_type
|
||||
* @param string|integer|object
|
||||
* @return WC_DateTime|NULL object if the date is set or null if there is no date.
|
||||
*/
|
||||
protected function set_last_order_date( $date_type, $date = null ) {
|
||||
|
||||
$last_order = $this->get_last_order( 'all' );
|
||||
|
||||
if ( $last_order ) {
|
||||
|
||||
$datetime = wcs_get_datetime_from( $date );
|
||||
|
||||
switch ( $date_type ) {
|
||||
case 'date_paid':
|
||||
update_post_meta( $last_order->id, '_paid_date', ! is_null( $date ) ? $datetime->date( 'Y-m-d H:i:s' ) : '' );
|
||||
// Preemptively set the UTC timestamp for WC 3.0+ also to avoid incorrect values when the site's timezone is changed between now and upgrading to WC 3.0
|
||||
update_post_meta( $last_order->id, '_date_paid', ! is_null( $date ) ? $datetime->getTimestamp() : '' );
|
||||
break;
|
||||
|
||||
case 'date_completed':
|
||||
update_post_meta( $last_order->id, '_completed_date', ! is_null( $date ) ? $datetime->date( 'Y-m-d H:i:s' ) : '' );
|
||||
// Preemptively set the UTC timestamp for WC 3.0+ also to avoid incorrect values when the site's timezone is changed between now and upgrading to WC 3.0
|
||||
update_post_meta( $last_order->id, '_date_completed', ! is_null( $date ) ? $datetime->getTimestamp() : '' );
|
||||
break;
|
||||
|
||||
case 'date_modified':
|
||||
wp_update_post( array(
|
||||
'ID' => $last_order->id,
|
||||
'post_modified' => $datetime->date( 'Y-m-d H:i:s' ),
|
||||
'post_modified_gmt' => wcs_get_datetime_utc_string( $datetime ),
|
||||
) );
|
||||
break;
|
||||
|
||||
case 'date_created':
|
||||
wp_update_post( array(
|
||||
'ID' => $last_order->id,
|
||||
'post_date' => $datetime->date( 'Y-m-d H:i:s' ),
|
||||
'post_date_gmt' => wcs_get_datetime_utc_string( $datetime ),
|
||||
) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set date_created.
|
||||
*
|
||||
* Used by parent::update_dates()
|
||||
*
|
||||
* @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if their is no date.
|
||||
* @throws WC_Data_Exception
|
||||
*/
|
||||
public function set_date_created( $date = null ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! is_null( $date ) ) {
|
||||
|
||||
$datetime_string = wcs_get_datetime_utc_string( wcs_get_datetime_from( $date ) );
|
||||
|
||||
// Don't use wp_update_post() to avoid infinite loops here
|
||||
$wpdb->query( $wpdb->prepare( "UPDATE $wpdb->posts SET post_date = %s, post_date_gmt = %s WHERE ID = %d", get_date_from_gmt( $datetime_string ), $datetime_string, $this->get_id() ) );
|
||||
|
||||
$this->post->post_date = get_date_from_gmt( $datetime_string );
|
||||
$this->post->post_date_gmt = $datetime_string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set discount_total.
|
||||
*
|
||||
* @param string $value
|
||||
* @throws WC_Data_Exception
|
||||
*/
|
||||
public function set_discount_total( $value ) {
|
||||
$this->set_total( $value, 'cart_discount' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set discount_tax.
|
||||
*
|
||||
* @param string $value
|
||||
* @throws WC_Data_Exception
|
||||
*/
|
||||
public function set_discount_tax( $value ) {
|
||||
$this->set_total( $value, 'cart_discount_tax' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set shipping_total.
|
||||
*
|
||||
* @param string $value
|
||||
* @throws WC_Data_Exception
|
||||
*/
|
||||
public function set_shipping_total( $value ) {
|
||||
$this->set_total( $value, 'shipping' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set shipping_tax.
|
||||
*
|
||||
* @param string $value
|
||||
* @throws WC_Data_Exception
|
||||
*/
|
||||
public function set_shipping_tax( $value ) {
|
||||
$this->set_total( $value, 'shipping_tax' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cart tax.
|
||||
*
|
||||
* @param string $value
|
||||
* @throws WC_Data_Exception
|
||||
*/
|
||||
public function set_cart_tax( $value ) {
|
||||
$this->set_total( $value, 'tax' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save data to the database. Nothing to do here as it's all done separately when calling @see this->set_prop().
|
||||
*
|
||||
* @return int order ID
|
||||
*/
|
||||
public function save() {
|
||||
$this->status_transition();
|
||||
return $this->get_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update meta data by key or ID, if provided.
|
||||
*
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @param int $meta_id
|
||||
*/
|
||||
public function update_meta_data( $key, $value, $meta_id = '' ) {
|
||||
if ( ! empty( $meta_id ) ) {
|
||||
update_metadata_by_mid( 'post', $meta_id, $value, $key );
|
||||
} else {
|
||||
update_post_meta( $this->get_id(), $key, $value );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save subscription date changes to the database.
|
||||
* Nothing to do here as all date properties are saved when calling @see $this->set_prop().
|
||||
*
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.6
|
||||
*/
|
||||
public function save_dates() {
|
||||
// Nothing to do here.
|
||||
}
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* There is "magic" in PHP, and then there is this.
|
||||
*
|
||||
* Story time: once upon a time, in a land not too far away, WooCommerce 3.0 deprecated accessing
|
||||
* all properties on objects. A conventicle of wizards known as __get(), __set() and __isset() came
|
||||
* together to make sure that properties on Subscriptions products could still be used, despite not being
|
||||
* accessible. However, a dark cloud hung over properties which were arrays. None of the conventicle
|
||||
* new of magic powerful enough to deal with such a problem. Enter Cesar, who summoned the dark arts
|
||||
* to call upon the ArrayAccess incantation.
|
||||
*
|
||||
* In other words, this class is used to access specific items on an array from within the magic methods
|
||||
* of other objects, like WC_Product_Subscription_Variation::__get() for the property formerly known as
|
||||
* $subscription_variation_level_meta_data.
|
||||
*
|
||||
* @package WooCommerce Subscriptions
|
||||
* @category Class
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0
|
||||
*
|
||||
*/
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class WCS_Array_Property_Post_Meta_Black_Magic implements ArrayAccess {
|
||||
|
||||
/**
|
||||
* Store the ID this class is being used against so that we use it for post meta calls.
|
||||
*/
|
||||
protected $product_id;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param mixed $product
|
||||
*/
|
||||
public function __construct( $product_id ) {
|
||||
$this->product_id = $product_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* offsetGet
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet( $key ) {
|
||||
return get_post_meta( $this->product_id, $this->maybe_prefix_meta_key( $key ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* offsetSet
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetSet( $key, $value ) {
|
||||
update_post_meta( $this->product_id, $this->maybe_prefix_meta_key( $key ), $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* offsetExists
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetExists( $key ) {
|
||||
return metadata_exists( 'post', $this->product_id, $this->maybe_prefix_meta_key( $key ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Nothing to do here as we access post meta directly.
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetUnset( $key ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* We only work with post meta data that has meta keys prefixed with an underscore, so
|
||||
* add a prefix if it is not already set.
|
||||
*/
|
||||
protected function maybe_prefix_meta_key( $key ) {
|
||||
if ( '_' != substr( $key, 0, 1 ) ) {
|
||||
$key = '_' . $key;
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Legacy Subscription Product Handler
|
||||
*
|
||||
* Ensures subscription products work with versions of WooCommerce prior to 3.0 by loading
|
||||
* legacy classes to provide CRUD methods only added with WC 3.0.
|
||||
*
|
||||
* @package WooCommerce Subscriptions
|
||||
* @subpackage WCS_Product_Legacy
|
||||
* @category Class
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0
|
||||
*/
|
||||
class WCS_Product_Legacy {
|
||||
|
||||
/**
|
||||
* Set up the class, including it's hooks & filters, when the file is loaded.
|
||||
*
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0
|
||||
**/
|
||||
public static function init() {
|
||||
|
||||
// Use our legacy product classes when WC 3.0+ is not active
|
||||
add_filter( 'woocommerce_product_class', __CLASS__ . '::set_product_class', 100, 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Use legacy classes for WC < 3.0
|
||||
*
|
||||
* @return string $classname The name of the WC_Product_* class which should be instantiated to create an instance of this product.
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0
|
||||
*/
|
||||
public static function set_product_class( $classname, $product_type, $post_type, $product_id ) {
|
||||
|
||||
if ( wcs_is_woocommerce_pre( '3.0' ) && in_array( $classname, array( 'WC_Product_Subscription', 'WC_Product_Variable_Subscription', 'WC_Product_Subscription_Variation' ) ) ) {
|
||||
$classname .= '_Legacy';
|
||||
}
|
||||
|
||||
return $classname;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -226,6 +226,10 @@ class WC_Subscriptions_Upgrader {
|
|||
if ( version_compare( self::$stored_plugin_version, '7.8.0', '<' ) ) {
|
||||
WCS_Plugin_Upgrade_7_8_0::check_gifting_plugin_is_enabled();
|
||||
}
|
||||
|
||||
if ( version_compare( self::$stored_plugin_version, '8.3.0', '<' ) ) {
|
||||
WCS_Plugin_Upgrade_8_3_0::check_downloads_plugin_is_enabled();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
/**
|
||||
* Upgrade script for version 8.1.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class WCS_Plugin_Upgrade_8_3_0 {
|
||||
|
||||
/**
|
||||
* Check if the Gifting plugin is enabled and update the settings.
|
||||
*
|
||||
* @since 8.1.0
|
||||
*/
|
||||
public static function check_downloads_plugin_is_enabled() {
|
||||
|
||||
WCS_Upgrade_Logger::add( 'Checking if the Downloads plugin is enabled...' );
|
||||
|
||||
$active_plugins = get_option( 'active_plugins', array() );
|
||||
$is_downloads_plugin_active = false;
|
||||
|
||||
foreach ( $active_plugins as $plugin ) {
|
||||
if ( strpos( $plugin, 'woocommerce-subscription-downloads.php' ) !== false ) {
|
||||
$is_downloads_plugin_active = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $is_downloads_plugin_active ) {
|
||||
WCS_Upgrade_Logger::add( 'Downloads plugin is not enabled via active_plugins, skipping...' );
|
||||
return;
|
||||
}
|
||||
|
||||
WCS_Upgrade_Logger::add( 'Downloads plugin is enabled, updating Downloads settings...' );
|
||||
|
||||
update_option( WC_Subscriptions_Admin::$option_prefix . '_enable_downloadable_file_linking', 'yes' );
|
||||
}
|
||||
}
|
||||
|
|
@ -224,19 +224,19 @@ class WCS_Upgrade_1_2 {
|
|||
// Upgrading with WC 1.x
|
||||
if ( is_callable( array( $item_meta, 'add' ) ) ) {
|
||||
|
||||
$item_meta->add( '_subscription_period', $order_subscription_periods[ $product_id ] );
|
||||
$item_meta->add( '_subscription_interval', $subscription_interval );
|
||||
$item_meta->add( '_subscription_length', $subscription_length );
|
||||
$item_meta->add( '_subscription_trial_length', $subscription_trial_length );
|
||||
$item_meta->add( '_subscription_period', $order_subscription_periods[ $product_id ] ); // @phpstan-ignore method.notFound (Legacy upgrade code with proper defensive checks)
|
||||
$item_meta->add( '_subscription_interval', $subscription_interval ); // @phpstan-ignore method.notFound (Legacy upgrade code with proper defensive checks)
|
||||
$item_meta->add( '_subscription_length', $subscription_length ); // @phpstan-ignore method.notFound (Legacy upgrade code with proper defensive checks)
|
||||
$item_meta->add( '_subscription_trial_length', $subscription_trial_length ); // @phpstan-ignore method.notFound (Legacy upgrade code with proper defensive checks)
|
||||
|
||||
$item_meta->add( '_subscription_recurring_amount', $order_item['line_subtotal'] ); // WC_Subscriptions_Product::get_price() would return a price without filters applied
|
||||
$item_meta->add( '_subscription_sign_up_fee', $subscription_sign_up_fee );
|
||||
$item_meta->add( '_subscription_recurring_amount', $order_item['line_subtotal'] ); // @phpstan-ignore method.notFound (Legacy upgrade code with proper defensive checks)
|
||||
$item_meta->add( '_subscription_sign_up_fee', $subscription_sign_up_fee ); // @phpstan-ignore method.notFound (Legacy upgrade code with proper defensive checks)
|
||||
|
||||
// Set recurring amounts for the item
|
||||
$item_meta->add( '_recurring_line_total', $order_item['line_total'] );
|
||||
$item_meta->add( '_recurring_line_tax', $order_item['line_tax'] );
|
||||
$item_meta->add( '_recurring_line_subtotal', $order_item['line_subtotal'] );
|
||||
$item_meta->add( '_recurring_line_subtotal_tax', $order_item['line_subtotal_tax'] );
|
||||
$item_meta->add( '_recurring_line_total', $order_item['line_total'] ); // @phpstan-ignore method.notFound (Legacy upgrade code with proper defensive checks)
|
||||
$item_meta->add( '_recurring_line_tax', $order_item['line_tax'] ); // @phpstan-ignore method.notFound (Legacy upgrade code with proper defensive checks)
|
||||
$item_meta->add( '_recurring_line_subtotal', $order_item['line_subtotal'] ); // @phpstan-ignore method.notFound (Legacy upgrade code with proper defensive checks)
|
||||
$item_meta->add( '_recurring_line_subtotal_tax', $order_item['line_subtotal_tax'] ); // @phpstan-ignore method.notFound (Legacy upgrade code with proper defensive checks)
|
||||
|
||||
$order_item['item_meta'] = $item_meta->meta;
|
||||
|
||||
|
|
|
|||
|
|
@ -516,7 +516,7 @@ function wcs_is_checkout_blocks_api_request( $endpoint = '' ) {
|
|||
* @return bool True if it's a WordPress cron request, false otherwise.
|
||||
*/
|
||||
function wcs_doing_cron() {
|
||||
return function_exists( 'wp_doing_cron' ) ? wp_doing_cron() : defined( 'DOING_CRON' ) && DOING_CRON;
|
||||
return function_exists( 'wp_doing_cron' ) ? wp_doing_cron() : defined( 'DOING_CRON' ) && DOING_CRON; // @phpstan-ignore phpstanWP.wpConstant.fetch (Compatibility fallback for older WordPress versions)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -527,7 +527,7 @@ function wcs_doing_cron() {
|
|||
* @return bool True if it's a WordPress Ajax request, false otherwise.
|
||||
*/
|
||||
function wcs_doing_ajax() {
|
||||
return function_exists( 'wp_doing_ajax' ) ? wp_doing_ajax() : defined( 'DOING_AJAX' ) && DOING_AJAX;
|
||||
return function_exists( 'wp_doing_ajax' ) ? wp_doing_ajax() : defined( 'DOING_AJAX' ) && DOING_AJAX; // @phpstan-ignore phpstanWP.wpConstant.fetch (Compatibility fallback for older WordPress versions)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -73,13 +73,24 @@ function wcs_do_subscriptions_exist() {
|
|||
/**
|
||||
* Main function for returning subscriptions. Wrapper for the wc_get_order() method.
|
||||
*
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
|
||||
* @since 2.0.0 Introduced.
|
||||
* @since 8.1.0 Type safety improved.
|
||||
*
|
||||
* @param mixed $the_subscription Post object or post ID of the order.
|
||||
*
|
||||
* @return WC_Subscription|false The subscription object, or false if it cannot be found.
|
||||
*/
|
||||
function wcs_get_subscription( $the_subscription ) {
|
||||
|
||||
if ( empty( $the_subscription ) ) {
|
||||
wc_get_logger()->debug(
|
||||
'Subscription could not be loaded, as an empty value was supplied.',
|
||||
array(
|
||||
'backtrace' => true,
|
||||
'requested_subscription' => $the_subscription,
|
||||
'requested_subscription_type' => gettype( $the_subscription ),
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -87,13 +98,52 @@ function wcs_get_subscription( $the_subscription ) {
|
|||
$the_subscription = $the_subscription->get_id();
|
||||
}
|
||||
|
||||
/** @var WC_Subscription $subscription */
|
||||
$subscription = WC()->order_factory->get_order( $the_subscription );
|
||||
|
||||
if ( ! wcs_is_subscription( $subscription ) ) {
|
||||
$subscription = false;
|
||||
}
|
||||
|
||||
return apply_filters( 'wcs_get_subscription', $subscription );
|
||||
/**
|
||||
* The subscription object being supplied to the caller, or else (bool) false if it could not be loaded.
|
||||
*
|
||||
* If a callback turns $subscription into some other type, that value will be rejected and (bool) false
|
||||
* will ultimately be returned instead.
|
||||
*
|
||||
* @since 2.0.0 Introduced.
|
||||
* @since 8.1.0 Type safety improved.
|
||||
*
|
||||
* @param WC_Subscription|false $subscription The subscription object being supplied to the caller, or (bool) false.
|
||||
*/
|
||||
$return_val = apply_filters( 'wcs_get_subscription', $subscription );
|
||||
|
||||
if ( false === $return_val ) {
|
||||
wc_get_logger()->debug(
|
||||
'Subscription could not be loaded.',
|
||||
array(
|
||||
'backtrace' => true,
|
||||
'requested_subscription' => $the_subscription,
|
||||
'requested_subscription_type' => is_object( $the_subscription ) ? get_class( $the_subscription ) : gettype( $the_subscription ),
|
||||
)
|
||||
);
|
||||
|
||||
return false;
|
||||
} elseif ( ! $return_val instanceof WC_Subscription ) {
|
||||
wc_get_logger()->warning(
|
||||
'Subscriptions obtained via wcs_get_subscription() must be instances of WC_Subscription.',
|
||||
array(
|
||||
'backtrace' => true,
|
||||
'filtered_type' => is_object( $return_val ) ? get_class( $return_val ) : gettype( $return_val ),
|
||||
'requested_subscription' => $the_subscription,
|
||||
'requested_subscription_type' => is_object( $the_subscription ) ? get_class( $the_subscription ) : gettype( $the_subscription ),
|
||||
)
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $return_val;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -675,7 +725,12 @@ function wcs_get_subscriptions_for_product( $product_ids, $fields = 'ids', $args
|
|||
function wcs_get_line_items_with_a_trial( $subscription_id ) {
|
||||
/** @var WC_Subscription $subscription */
|
||||
$subscription = ( is_object( $subscription_id ) ) ? $subscription_id : wcs_get_subscription( $subscription_id );
|
||||
$trial_items = array();
|
||||
|
||||
if ( ! $subscription ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$trial_items = array();
|
||||
|
||||
foreach ( $subscription->get_items() as $line_item_id => $line_item ) {
|
||||
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ function wcs_get_users_resubscribe_link_for_product( $product_id ) {
|
|||
* Checks the cart to see if it contains a subscription product renewal.
|
||||
*
|
||||
* @param bool|array $cart The cart item containing the renewal, else false.
|
||||
* @return string
|
||||
* @return array|false
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0
|
||||
*/
|
||||
function wcs_cart_contains_resubscribe( $cart = null ) {
|
||||
|
|
@ -184,6 +184,10 @@ function wcs_can_user_resubscribe_to( $subscription, $user_id = 0 ) {
|
|||
|
||||
$can_user_resubscribe = false;
|
||||
|
||||
} elseif ( $subscription->contains_unavailable_product() ) {
|
||||
|
||||
$can_user_resubscribe = false;
|
||||
|
||||
} else {
|
||||
|
||||
$resubscribe_order_ids = $subscription->get_related_orders( 'ids', 'resubscribe' );
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
/**
|
||||
* Downloads Admin Announcement Handler Class
|
||||
*
|
||||
* @package WooCommerce Subscriptions
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class WC_Subscription_Downloads_Admin_Welcome_Announcement {
|
||||
|
||||
/**
|
||||
* Initialize the tour handler
|
||||
*/
|
||||
public static function init() {
|
||||
// Register scripts and styles
|
||||
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) );
|
||||
|
||||
// Add the tour HTML to the admin footer
|
||||
add_action( 'admin_footer', array( __CLASS__, 'output_tour' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue required scripts and styles
|
||||
*/
|
||||
public static function enqueue_scripts() {
|
||||
if ( ! self::is_woocommerce_admin_or_subscriptions_listing() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$screen = get_current_screen();
|
||||
|
||||
wp_localize_script(
|
||||
'wcs-admin',
|
||||
'wcsDownloadsSettings',
|
||||
array(
|
||||
'imagesPath' => 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 '<div id="wcs-downloads-welcome-announcement-root" class="woocommerce-tour-kit"></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* WooCommerce Subscription Downloads Ajax.
|
||||
*
|
||||
* @package WC_Subscription_Downloads_Ajax
|
||||
* @category Ajax
|
||||
* @author WooThemes
|
||||
*/
|
||||
class WC_Subscription_Downloads_Ajax {
|
||||
|
||||
/**
|
||||
* Ajax actions.
|
||||
*/
|
||||
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' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Search subscription products.
|
||||
*/
|
||||
public function search_subscriptions() {
|
||||
ob_start();
|
||||
|
||||
global $wpdb;
|
||||
|
||||
check_ajax_referer( 'search-products', 'security' );
|
||||
|
||||
$term = wc_clean( stripslashes( $_GET['term'] ) );
|
||||
|
||||
if ( empty( $term ) ) {
|
||||
die();
|
||||
}
|
||||
|
||||
$found_subscriptions = array();
|
||||
|
||||
$term = apply_filters( 'woocommerce_subscription_downloads_json_search_order_number', $term );
|
||||
|
||||
// Find subscription products by title.
|
||||
$query_subscriptions = $wpdb->get_results( $wpdb->prepare( "
|
||||
SELECT ID
|
||||
FROM $wpdb->posts AS posts
|
||||
LEFT JOIN $wpdb->term_relationships AS t_relationships ON(posts.ID = t_relationships.object_id)
|
||||
LEFT JOIN $wpdb->term_taxonomy AS t_taxonomy ON(t_relationships.term_taxonomy_id = t_taxonomy.term_taxonomy_id)
|
||||
LEFT JOIN $wpdb->terms AS terms ON(t_taxonomy.term_id = terms.term_id)
|
||||
WHERE posts.post_type = 'product'
|
||||
AND posts.post_status = 'publish'
|
||||
AND posts.post_title LIKE %s
|
||||
AND t_taxonomy.taxonomy = 'product_type'
|
||||
AND (terms.slug = 'subscription' OR terms.slug = 'variable-subscription')
|
||||
ORDER BY posts.post_date DESC
|
||||
", '%' . $term . '%' ) );
|
||||
|
||||
if ( $query_subscriptions ) {
|
||||
foreach ( $query_subscriptions as $item ) {
|
||||
$_product = wc_get_product( $item->ID );
|
||||
$found_subscriptions[ $item->ID ] = sanitize_text_field( $_product->get_formatted_name() );
|
||||
|
||||
if ( 'variable-subscription' == $_product->get_type() ) {
|
||||
$chindren = get_children( array( 'post_parent' => $_product->get_id(), 'post_type' => 'product_variation' ) );
|
||||
|
||||
foreach ( $chindren as $child ) {
|
||||
$_child_product = wc_get_product( $child );
|
||||
$found_subscriptions[ $child->ID ] = sanitize_text_field( $_child_product->get_formatted_name() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* WooCommerce Subscription Downloads Install.
|
||||
*
|
||||
* @package WC_Subscription_Downloads_Install
|
||||
* @category Install
|
||||
* @author WooThemes
|
||||
*/
|
||||
class WC_Subscription_Downloads_Install {
|
||||
|
||||
/**
|
||||
* Run the install.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->create_table();
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the plugin table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function create_table() {
|
||||
global $wpdb;
|
||||
|
||||
$version = get_option( 'woocommerce_subscription_downloads_version' );
|
||||
|
||||
if ( ! $version ) {
|
||||
add_option( 'woocommerce_subscription_downloads_version', WC_Subscriptions::$version );
|
||||
|
||||
$collate = '';
|
||||
|
||||
if ( $wpdb->has_cap( 'collation' ) ) {
|
||||
if ( ! empty( $wpdb->charset ) ) {
|
||||
$collate .= "DEFAULT CHARACTER SET $wpdb->charset";
|
||||
}
|
||||
if ( ! empty( $wpdb->collate ) ) {
|
||||
$collate .= " COLLATE $wpdb->collate";
|
||||
}
|
||||
}
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
|
||||
$create = "
|
||||
CREATE TABLE {$wpdb->prefix}woocommerce_subscription_downloads (
|
||||
id bigint(20) NOT NULL auto_increment,
|
||||
product_id bigint(20) NOT NULL,
|
||||
subscription_id bigint(20) NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) $collate;
|
||||
";
|
||||
|
||||
dbDelta( $create );
|
||||
} else {
|
||||
update_option( 'woocommerce_subscription_downloads_version', WC_Subscriptions::$version );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* WooCommerce Subscription Downloads Order.
|
||||
*
|
||||
* @package WC_Subscription_Downloads_Order
|
||||
* @category Order
|
||||
* @author WooThemes
|
||||
*/
|
||||
class WC_Subscription_Downloads_Order {
|
||||
|
||||
/**
|
||||
* Order actions.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'woocommerce_subscription_status_changed', array( $this, 'download_permissions' ), 10, 4 );
|
||||
add_action( 'woocommerce_email_after_order_table', array( $this, 'email_list_downloads' ), 10, 3 );
|
||||
add_action( 'woocommerce_subscriptions_switched_item', array( $this, 'handle_download_switch' ), 10, 3 );
|
||||
add_filter( 'woocommerce_order_get_downloadable_items', array( $this, 'remove_subscription_download_duplicates' ), 1, 2 );
|
||||
add_filter( 'woocommerce_customer_available_downloads', array( $this, 'remove_customer_download_duplicates' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the download permissions in the subscription depending on the status.
|
||||
*
|
||||
* @param int $subscription_id Subscription ID.
|
||||
* @param string $old_status Old status.
|
||||
* @param string $new_status New status.
|
||||
* @param WC_Subscription $subscription Subscription object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function download_permissions( $subscription_id, $old_status, $new_status, $subscription ) {
|
||||
if ( ! in_array( $new_status, array( 'active', 'expired', 'cancelled' ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$product_item_ids = array_map( function( $item ) {
|
||||
return $item['product_id'];
|
||||
}, $subscription->get_items() );
|
||||
|
||||
foreach ( $subscription->get_items() as $item ) {
|
||||
|
||||
// Gets the downloadable products.
|
||||
$downloadable_products = WC_Subscription_Downloads::get_downloadable_products( $item['product_id'], $item['variation_id'] );
|
||||
|
||||
if ( $downloadable_products ) {
|
||||
|
||||
foreach ( $downloadable_products as $product_id ) {
|
||||
$_product = wc_get_product( $product_id );
|
||||
|
||||
if ( ! $_product ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// @phpstan-ignore property.notFound
|
||||
$product_status = version_compare( WC_VERSION, '3.0', '<' ) ? $_product->post->post_status : $_product->get_status();
|
||||
|
||||
if ( 'expired' === $new_status || 'cancelled' === $new_status ) {
|
||||
WCS_Download_Handler::revoke_downloadable_file_permission( $product_id, $subscription_id, $subscription->get_user_id() );
|
||||
}
|
||||
// Adds the downloadable files to the subscription.
|
||||
else if ( $_product && $_product->exists() && $_product->is_downloadable() && 'publish' === $product_status ) {
|
||||
WCS_Download_Handler::revoke_downloadable_file_permission( $product_id, $subscription_id, $subscription->get_user_id() );
|
||||
$downloads = version_compare( WC_VERSION, '3.0', '<' ) ? $_product->get_files() : $_product->get_downloads();
|
||||
|
||||
foreach ( array_keys( $downloads ) as $download_id ) {
|
||||
wc_downloadable_file_permission( $download_id, $product_id, $subscription );
|
||||
}
|
||||
|
||||
if ( ! in_array( $_product->get_id(), $product_item_ids ) ) {
|
||||
// Skip wrong recalculation of totals by adding a 0 amount Subscriptions.
|
||||
$totals = array(
|
||||
'subtotal' => wc_format_decimal( 0 ),
|
||||
'total' => wc_format_decimal( 0 ),
|
||||
'subtotal_tax' => wc_format_decimal( 0 ),
|
||||
'tax' => wc_format_decimal( 0 ),
|
||||
);
|
||||
|
||||
$subscription->add_product( $_product, 1, array( 'totals' => $totals ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove downloads duplicates on subscriptions.
|
||||
*
|
||||
* @since 1.1.29
|
||||
*
|
||||
* @param array $downloads List of downloads.
|
||||
* @param WC_Order $order The order.
|
||||
*
|
||||
* @return array Array of downloads.
|
||||
*/
|
||||
public static function remove_subscription_download_duplicates( $downloads, $order ) {
|
||||
if ( is_a( $order, 'WC_Subscription' ) ) {
|
||||
$downloads = array_unique( $downloads, SORT_REGULAR );
|
||||
}
|
||||
return $downloads;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove customer download duplicates that were added to the same order.
|
||||
*
|
||||
* @since 1.1.30
|
||||
*
|
||||
* @param array $downloads List of downloads.
|
||||
* @param int $customer_id The customer id.
|
||||
*
|
||||
* @return array Array of downloads.
|
||||
*/
|
||||
public static function remove_customer_download_duplicates( $downloads, $customer_id ) {
|
||||
// As downloads have an order_id, the following only removes download duplicates from the same order.
|
||||
$downloads = array_unique( $downloads, SORT_REGULAR );
|
||||
|
||||
return $downloads;
|
||||
}
|
||||
|
||||
/**
|
||||
* List the downloads in order emails.
|
||||
*
|
||||
* @param WC_Order $order Order data
|
||||
* @param bool $sent_to_admin Sent or not to admin.
|
||||
* @param bool $plain_text Plain or HTML email.
|
||||
*/
|
||||
public function email_list_downloads( $order, $sent_to_admin = false, $plain_text = false ) {
|
||||
// @phpstan-ignore property.notFound
|
||||
$order_status = version_compare( WC_VERSION, '3.0', '<' ) ? $order->status : $order->get_status();
|
||||
|
||||
if ( $sent_to_admin && ! in_array( $order_status, array( 'processing', 'completed' ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$downloads = WC_Subscription_Downloads::get_order_downloads( $order );
|
||||
|
||||
if ( $downloads && $plain_text ) {
|
||||
$html = apply_filters( 'woocommerce_subscription_downloads_my_downloads_title', __( 'Available downloads', 'woocommerce-subscriptions' ) );
|
||||
$html .= PHP_EOL . PHP_EOL;
|
||||
|
||||
foreach ( $downloads as $download ) {
|
||||
$html .= $download['name'] . ': ' . $download['download_url'] . PHP_EOL;
|
||||
}
|
||||
|
||||
$html .= PHP_EOL;
|
||||
$html .= '****************************************************';
|
||||
$html .= PHP_EOL . PHP_EOL;
|
||||
|
||||
echo esc_html( wp_strip_all_tags( $html ) );
|
||||
|
||||
} elseif ( $downloads && ! $plain_text ) {
|
||||
$html = '<h2>' . esc_html( apply_filters( 'woocommerce_subscription_downloads_my_downloads_title', __( 'Available downloads', 'woocommerce-subscriptions' ) ) ) . '</h2>';
|
||||
|
||||
$html .= '<table cellspacing="0" cellpadding="0" style="width: 100%; vertical-align: top;" border="0">';
|
||||
$html .= '<tr>';
|
||||
$html .= '<td valign="top">';
|
||||
$html .= '<ul class="digital-downloads">';
|
||||
foreach ( $downloads as $download ) {
|
||||
$html .= sprintf( '<li><a href="%1$s" title="%2$s" target="_blank">%2$s</a></li>', esc_url( $download['download_url'] ), esc_html( $download['name'] ) );
|
||||
}
|
||||
$html .= '</ul>';
|
||||
$html .= '</td>';
|
||||
$html .= '</tr>';
|
||||
$html .= '</table>';
|
||||
|
||||
/**
|
||||
* The following HTML output consists of both static content and variable elements.
|
||||
* The variable elements, such as user-generated content or database values, are properly escaped to prevent security vulnerabilities.
|
||||
*
|
||||
* If this HTML is ever exposed externally via a filter or if more variable elements are added, additional security measures should be taken into account.
|
||||
* Consider using the necessary escaping functions/methods to ensure the continued safety of the output.
|
||||
*
|
||||
* Note: The warning 'WordPress.Security.EscapeOutput.OutputNotEscaped' has been suppressed intentionally, but ensure that the code adheres to the required security standards.
|
||||
*/
|
||||
echo $html; // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke download permissions granted on the old switch item.
|
||||
*
|
||||
* @param WC_Subscription $subscription
|
||||
* @param array $new_item
|
||||
* @param array $old_item
|
||||
*/
|
||||
public function handle_download_switch( $subscription, $new_item, $old_item ) {
|
||||
if ( ! is_object( $subscription ) ) {
|
||||
$subscription = wcs_get_subscription( $subscription );
|
||||
}
|
||||
|
||||
$subscription_id = $subscription->get_id();
|
||||
$downloadable_products = WC_Subscription_Downloads::get_downloadable_products( $old_item['product_id'], $old_item['variation_id'] );
|
||||
$subscription_items = $subscription->get_items();
|
||||
|
||||
// Remove old item attached to the subscription.
|
||||
foreach ( $subscription_items as $item ) {
|
||||
if ( in_array( $item['product_id'], $downloadable_products ) || in_array( $item['variation_id'], $downloadable_products ) ) {
|
||||
$item = $subscription->get_item( $item );
|
||||
if ( $item ) {
|
||||
$item->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Further, remove all attached downloadable products to the subscription.
|
||||
foreach ( $downloadable_products as $product_id ) {
|
||||
WCS_Download_Handler::revoke_downloadable_file_permission( $product_id, $subscription_id, $subscription->get_user_id() );
|
||||
}
|
||||
|
||||
// Re-trigger download permissions. It will automatically add permissions to the new items.
|
||||
$this->download_permissions( $subscription_id, '', 'active', $subscription );
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,815 @@
|
|||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* WooCommerce Subscription Downloads Products.
|
||||
*
|
||||
* @package WC_Subscription_Downloads_Products
|
||||
*/
|
||||
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' ) );
|
||||
add_action( 'woocommerce_variation_options_download', array( $this, 'variable_write_panel_options' ), 10, 3 );
|
||||
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() {
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle product variation save - generic handler for all variation updates.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_product_variation_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;
|
||||
}
|
||||
|
||||
$variation = wc_get_product( $post_id );
|
||||
if ( ! $variation || ! $variation->is_type( 'variation' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple product write panel options.
|
||||
*/
|
||||
public function simple_write_panel_options() {
|
||||
global $post;
|
||||
?>
|
||||
<p class="form-field _subscription_downloads_field hide_if_subscription">
|
||||
<label for="subscription-downloads-ids"><?php esc_html_e( 'Linked subscription products', 'woocommerce-subscriptions' ); ?></label>
|
||||
|
||||
<select id="subscription-downloads-ids" multiple="multiple" data-action="wc_subscription_downloads_search" data-placeholder="<?php esc_attr_e( 'Select subscriptions', 'woocommerce-subscriptions' ); ?>" class="subscription-downloads-ids wc-product-search" name="_subscription_downloads_ids[]" style="width: 50%;">
|
||||
<?php
|
||||
$subscriptions_ids = WC_Subscription_Downloads::get_subscriptions( $post->ID );
|
||||
|
||||
if ( $subscriptions_ids ) {
|
||||
foreach ( $subscriptions_ids as $subscription_id ) {
|
||||
$subscription = wc_get_product( $subscription_id );
|
||||
|
||||
if ( $subscription ) {
|
||||
echo '<option value="' . esc_attr( $subscription_id ) . '" selected="selected">' . esc_html( wp_strip_all_tags( $subscription->get_formatted_name() ) ) . '</option>';
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
|
||||
<span class="description"><?php esc_html_e( 'Select subscription products that will include this downloadable product.', 'woocommerce-subscriptions' ); ?></span>
|
||||
<?php wp_nonce_field( self::EDITOR_UPDATE, self::RELATIONSHIP_DOWNLOAD_TO_SUB, false ); ?>
|
||||
</p>
|
||||
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Variable product write panel options.
|
||||
*/
|
||||
public function variable_write_panel_options( $loop, $variation_data, $variation ) {
|
||||
?>
|
||||
<tr class="show_if_variation_downloadable">
|
||||
<td colspan="2">
|
||||
<p class="form-field _subscription_downloads_field form-row form-row-full hide_if_variable-subscription">
|
||||
<label><?php esc_html_e( 'Linked subscription products', 'woocommerce-subscriptions' ); ?>:</label>
|
||||
<?php echo wc_help_tip( wc_sanitize_tooltip( __( 'Select subscription products that will include this downloadable product.', 'woocommerce-subscriptions' ) ) ); ?>
|
||||
|
||||
<select multiple="multiple" data-placeholder="<?php esc_html_e( 'Select subscriptions', 'woocommerce-subscriptions' ); ?>" class="subscription-downloads-ids wc-product-search" name="_variable_subscription_downloads_ids[<?php echo esc_attr( $variation->ID ); ?>][]" style="width: 100%">
|
||||
<?php
|
||||
$subscriptions_ids = WC_Subscription_Downloads::get_subscriptions( $variation->ID );
|
||||
if ( $subscriptions_ids ) {
|
||||
foreach ( $subscriptions_ids as $subscription_id ) {
|
||||
$subscription = wc_get_product( $subscription_id );
|
||||
|
||||
if ( $subscription ) {
|
||||
echo '<option value="' . esc_attr( $subscription_id ) . '" selected="selected">' . esc_html( wp_strip_all_tags( $subscription->get_formatted_name() ) ) . '</option>';
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<?php wp_nonce_field( self::EDITOR_UPDATE, self::RELATIONSHIP_VAR_DOWNLOAD_TO_SUB . $variation->ID, false ); ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field with which to link the subscription product (the product being edited) with zero-or-many
|
||||
* downloadable products.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function subscription_product_editor_ui(): void {
|
||||
global $post;
|
||||
|
||||
if ( ! $post instanceof WP_Post ) {
|
||||
wc_get_logger()->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 .= "<option value='$product_id' selected='selected'>$product_name</option>";
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- variables are escaped above.
|
||||
echo "
|
||||
<div class='options_group subscription_linked_downloadable_products_section'>
|
||||
<p class='form-field subscription_linked_downloadable_products'>
|
||||
<label for='subscription-linked-downloadable-products'>$label</label>
|
||||
<select
|
||||
class='wc-product-search subscription-downloads-ids'
|
||||
data-action='wc_subscription_linked_downloadable_products_search'
|
||||
data-placeholder='$placeholder'
|
||||
id='subscription-linked-downloadable-products'
|
||||
multiple='multiple'
|
||||
name='_subscription_linked_downloadable_products[]'
|
||||
style='width: 50%;'
|
||||
>
|
||||
$linked_products
|
||||
</select>
|
||||
<span class='description'>$description</span>
|
||||
$nonce_field
|
||||
</p>
|
||||
</div>
|
||||
";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $loop
|
||||
* @param array $variation_data
|
||||
* @param WP_Post $variation
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function variable_subscription_product_editor_ui( $loop, $variation_data, $variation ): void {
|
||||
if ( ! $variation instanceof WP_Post ) {
|
||||
wc_get_logger()->warning(
|
||||
'Unable to add the downloadable products selector to the variation section of the product editor (we do not have a valid post object).',
|
||||
array( 'backtrace' => true )
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$variation_id = (int) $variation->ID;
|
||||
$label = esc_html__( 'Linked downloadable products', 'woocommerce-subscriptions' );
|
||||
$linked_products = '';
|
||||
$nonce_field = wp_nonce_field( self::EDITOR_UPDATE, self::RELATIONSHIP_VAR_SUB_TO_DOWNLOAD . $variation_id, false, false );
|
||||
$placeholder = esc_attr__( 'Select products', 'woocommerce-subscriptions' );
|
||||
$tooltip = wc_help_tip( wc_sanitize_tooltip( __( 'Select simple and variable downloadable products that will be included with this subscription variation.', 'woocommerce-subscriptions' ) ) );
|
||||
|
||||
foreach ( WC_Subscription_Downloads::get_downloadable_products( $variation->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 .= "<option value='$product_id' selected='selected'>$product_name</option>";
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- variables are escaped above.
|
||||
echo "
|
||||
<div class='variable_subscription_linked_downloadable_products show_if_variable-subscription' style='display: none'>
|
||||
<p class='form-row form-field subscription_linked_downloadable_products'>
|
||||
<label for='subscription-linked-downloadable-products'>$label</label>
|
||||
$tooltip
|
||||
<select
|
||||
class='wc-product-search subscription-downloads-ids'
|
||||
data-action='wc_subscription_linked_downloadable_products_search'
|
||||
data-placeholder='$placeholder'
|
||||
id='subscription-linked-downloadable-products'
|
||||
multiple='multiple'
|
||||
name='_subscription_linked_downloadable_products_{$variation_id}[]'
|
||||
style='width: 100%;'
|
||||
>
|
||||
$linked_products
|
||||
</select>
|
||||
$nonce_field
|
||||
</p>
|
||||
</div>
|
||||
";
|
||||
}
|
||||
|
||||
/**
|
||||
* Search orders from subscription product ID.
|
||||
*
|
||||
* @param int $subscription_product_id
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_orders( $subscription_product_id ) {
|
||||
global $wpdb;
|
||||
|
||||
$orders = array();
|
||||
$meta_key = '_product_id';
|
||||
|
||||
// Check if subscription product has parent (i.e. is a variable subscription product).
|
||||
$parent_id = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT post_parent AS parent_id
|
||||
FROM {$wpdb->prefix}posts
|
||||
WHERE ID = %d;
|
||||
",
|
||||
$subscription_product_id
|
||||
)
|
||||
);
|
||||
|
||||
// If the subscription product is a variation, use variation meta key to find related orders.
|
||||
if ( ! empty( $parent_id ) ) {
|
||||
$meta_key = '_variation_id';
|
||||
}
|
||||
|
||||
$results = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT order_items.order_id AS id
|
||||
FROM {$wpdb->prefix}woocommerce_order_items as order_items
|
||||
LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS itemmeta ON order_items.order_item_id = itemmeta.order_item_id
|
||||
WHERE itemmeta.meta_key = %s
|
||||
AND itemmeta.meta_value = %d;
|
||||
",
|
||||
$meta_key,
|
||||
$subscription_product_id
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $results as $order ) {
|
||||
$orders[] = $order->id;
|
||||
}
|
||||
|
||||
return apply_filters( 'woocommerce_subscription_downloads_get_orders', $orders, $subscription_product_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke access to download.
|
||||
*
|
||||
* @param bool $download_id
|
||||
* @param bool $product_id
|
||||
* @param bool $order_id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function revoke_access_to_download( $download_id, $product_id, $order_id ) {
|
||||
global $wpdb;
|
||||
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"
|
||||
DELETE FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions
|
||||
WHERE order_id = %d AND product_id = %d AND download_id = %s;
|
||||
",
|
||||
$order_id,
|
||||
$product_id,
|
||||
$download_id
|
||||
)
|
||||
);
|
||||
|
||||
do_action( 'woocommerce_ajax_revoke_access_to_product_download', $download_id, $product_id, $order_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update subscription downloads table and orders according in respect to the described relationship between a
|
||||
* regular product and zero-to-many regular subscription products.
|
||||
*
|
||||
* @param int $product_id The downloadable product ID.
|
||||
* @param array $subscriptions Subscription product IDs.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function update_subscription_downloads( $product_id, $subscriptions ) {
|
||||
$current = array_map( 'intval', WC_Subscription_Downloads::get_subscriptions( $product_id ) );
|
||||
$subscriptions = array_map( 'intval', (array) $subscriptions );
|
||||
|
||||
sort( $current );
|
||||
sort( $subscriptions );
|
||||
|
||||
$to_delete = array_diff( $current, $subscriptions );
|
||||
$to_create = array_diff( $subscriptions, $current );
|
||||
|
||||
$this->delete_relationships( $to_delete, array( $product_id ) );
|
||||
$this->create_relationships( $to_create, array( $product_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update subscription downloads table and orders according in respect to the described relationship between a
|
||||
* subscription product and zero-to-many regular products.
|
||||
*
|
||||
* @param int $subscription_product_id Subscription product ID.
|
||||
* @param int[] $new_ids IDs for downloadable products that should be associated with the subscription product.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function update_subscription_products( int $subscription_product_id, array $new_ids ): void {
|
||||
$existing_ids = array_map( 'intval', WC_Subscription_Downloads::get_downloadable_products( $subscription_product_id ) );
|
||||
$new_ids = array_map( 'intval', $new_ids );
|
||||
|
||||
sort( $existing_ids );
|
||||
sort( $new_ids );
|
||||
|
||||
$to_delete = array_diff( $existing_ids, $new_ids );
|
||||
$to_create = array_diff( $new_ids, $existing_ids );
|
||||
|
||||
$this->delete_relationships( array( $subscription_product_id ), $to_delete );
|
||||
$this->create_relationships( array( $subscription_product_id ), $to_create );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes relationships that exist between any of the supplied subscription IDs and any of the supplied product
|
||||
* IDs.
|
||||
*
|
||||
* The most common use case will be to supply a single subscription ID and one-or-more product IDs, or else the
|
||||
* inverse.
|
||||
*
|
||||
* @param int[] $subscription_ids
|
||||
* @param int[] $product_ids
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function delete_relationships( array $subscription_ids, array $product_ids ): void {
|
||||
global $wpdb;
|
||||
|
||||
foreach ( $product_ids as $product_id ) {
|
||||
$product_id = (int) $product_id;
|
||||
|
||||
foreach ( $subscription_ids as $subscription_id ) {
|
||||
$subscription_id = (int) $subscription_id;
|
||||
|
||||
$wpdb->delete(
|
||||
$wpdb->prefix . 'woocommerce_subscription_downloads',
|
||||
array(
|
||||
'product_id' => $product_id,
|
||||
'subscription_id' => $subscription_id,
|
||||
),
|
||||
array(
|
||||
'%d',
|
||||
'%d',
|
||||
)
|
||||
);
|
||||
|
||||
$orders = $this->get_orders( $subscription_id );
|
||||
foreach ( $orders as $order_id ) {
|
||||
$product = wc_get_product( $product_id );
|
||||
$downloads = $product->get_downloads();
|
||||
|
||||
// Adds the downloadable files to the order/subscription.
|
||||
foreach ( array_keys( $downloads ) as $download_id ) {
|
||||
$this->revoke_access_to_download( $download_id, $product_id, $order_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke download permissions for a product across all related subscriptions.
|
||||
*
|
||||
* @param int $product_id Product ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function revoke_permissions_for_product( $product_id ) {
|
||||
global $wpdb;
|
||||
|
||||
$subscription_ids = WC_Subscription_Downloads::get_subscriptions( $product_id );
|
||||
|
||||
if ( empty( $subscription_ids ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $subscription_ids as $subscription_id ) {
|
||||
$orders = $this->get_orders( $subscription_id );
|
||||
|
||||
foreach ( $orders as $order_id ) {
|
||||
// Delete ALL permissions for this product+order combination.
|
||||
// This ensures that when files change, old permissions with different download_ids are removed.
|
||||
$wpdb->delete(
|
||||
$wpdb->prefix . 'woocommerce_downloadable_product_permissions',
|
||||
array(
|
||||
'order_id' => $order_id,
|
||||
'product_id' => $product_id,
|
||||
),
|
||||
array(
|
||||
'%d',
|
||||
'%d',
|
||||
)
|
||||
);
|
||||
|
||||
do_action( 'woocommerce_revoke_access_to_product_download', $product_id, $order_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Grant download permissions for a product across all related subscriptions.
|
||||
*
|
||||
* @param int $product_id Product ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function grant_permissions_for_product( $product_id ) {
|
||||
$subscription_product_ids = WC_Subscription_Downloads::get_subscriptions( $product_id );
|
||||
$product = wc_get_product( $product_id );
|
||||
|
||||
if ( empty( $subscription_product_ids ) || ! $product ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$downloads = $product->get_downloads();
|
||||
|
||||
foreach ( $subscription_product_ids as $subscription_id ) {
|
||||
$orders = $this->get_orders( $subscription_id );
|
||||
|
||||
foreach ( $orders as $order_id ) {
|
||||
$order = wc_get_order( $order_id );
|
||||
|
||||
if ( ! is_a( $order, 'WC_Subscription' ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( array_keys( $downloads ) as $download_id ) {
|
||||
wc_downloadable_file_permission( $download_id, $product_id, $order );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds relationships between the specified subscription and product IDs.
|
||||
*
|
||||
* The most common use case will be to supply a single subscription ID and one-or-more product IDs, or else the
|
||||
* inverse.
|
||||
*
|
||||
* @param int[] $subscription_ids
|
||||
* @param int[] $product_ids
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function create_relationships( array $subscription_ids, array $product_ids ): void {
|
||||
global $wpdb;
|
||||
|
||||
foreach ( $product_ids as $product_id ) {
|
||||
$product_id = (int) $product_id;
|
||||
$product = wc_get_product( $product_id );
|
||||
|
||||
// Check if product has public status.
|
||||
$has_public_status = false;
|
||||
if ( $product ) {
|
||||
$status_object = get_post_status_object( $product->get_status() );
|
||||
$has_public_status = $status_object && $status_object->public;
|
||||
}
|
||||
|
||||
foreach ( $subscription_ids as $subscription_id ) {
|
||||
$subscription_id = (int) $subscription_id;
|
||||
|
||||
$wpdb->insert(
|
||||
$wpdb->prefix . 'woocommerce_subscription_downloads',
|
||||
array(
|
||||
'product_id' => $product_id,
|
||||
'subscription_id' => $subscription_id,
|
||||
),
|
||||
array(
|
||||
'%d',
|
||||
'%d',
|
||||
)
|
||||
);
|
||||
|
||||
// Only grant download permissions if product has public status.
|
||||
if ( $has_public_status ) {
|
||||
$orders = $this->get_orders( $subscription_id );
|
||||
foreach ( $orders as $order_id ) {
|
||||
$order = wc_get_order( $order_id );
|
||||
|
||||
if ( ! is_a( $order, 'WC_Subscription' ) ) {
|
||||
// avoid adding permissions to orders and it's
|
||||
// subscription for the same user, causing duplicates
|
||||
// to show up
|
||||
continue;
|
||||
}
|
||||
|
||||
$product = wc_get_product( $product_id );
|
||||
$downloads = $product->get_downloads();
|
||||
|
||||
// Adds the downloadable files to the order/subscription.
|
||||
foreach ( array_keys( $downloads ) as $download_id ) {
|
||||
wc_downloadable_file_permission( $download_id, $product_id, $order );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save simple product data.
|
||||
*
|
||||
* @param int $product_id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save_simple_product_data( $product_id ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$subscription_downloads_ids = ! empty( $_POST['_subscription_downloads_ids'] ) ? wc_clean( wp_unslash( $_POST['_subscription_downloads_ids'] ) ) : '';
|
||||
|
||||
if ( empty( $subscription_downloads_ids ) ) {
|
||||
$subscription_downloads_ids = array();
|
||||
}
|
||||
|
||||
$this->update_subscription_downloads( $product_id, $subscription_downloads_ids );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save subscriptions information when duplicating a product.
|
||||
*
|
||||
* @param int|WC_Product $id_or_product Duplicated product ID
|
||||
* @param WP_Post|WC_Product $post Product being duplicated
|
||||
*/
|
||||
public function save_subscriptions_when_duplicating_product( $id_or_product, $post ) {
|
||||
$post_id = is_a( $post, 'WC_Product' ) ? $post->get_parent_id() : $post->ID;
|
||||
$new_id = is_a( $id_or_product, 'WC_Product' ) ? $id_or_product->get_id() : $id_or_product;
|
||||
|
||||
$subscriptions = WC_Subscription_Downloads::get_subscriptions( $post_id );
|
||||
if ( ! empty( $subscriptions ) ) {
|
||||
$this->update_subscription_downloads( $new_id, $subscriptions );
|
||||
}
|
||||
|
||||
$children_products = get_children( 'post_parent=' . $post_id . '&post_type=product_variation' );
|
||||
if ( empty( $children_products ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create assoc array where keys are flatten variation attributes and values
|
||||
// are original product variations.
|
||||
$children_ids_by_variation_attributes = array();
|
||||
foreach ( $children_products as $child ) {
|
||||
$str_attributes = $this->get_str_variation_attributes( $child );
|
||||
if ( ! empty( $str_attributes ) ) {
|
||||
$children_ids_by_variation_attributes[ $str_attributes ] = $child;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy variations' subscriptions.
|
||||
$exclude = apply_filters( 'woocommerce_duplicate_product_exclude_children', false );
|
||||
$new_children_products = get_children( 'post_parent=' . $new_id . '&post_type=product_variation' );
|
||||
if ( ! $exclude && ! empty( $new_children_products ) ) {
|
||||
foreach ( $new_children_products as $child ) {
|
||||
$str_attributes = $this->get_str_variation_attributes( $child );
|
||||
if ( ! empty( $children_ids_by_variation_attributes[ $str_attributes ] ) ) {
|
||||
$this->save_subscriptions_when_duplicating_product(
|
||||
$child->ID,
|
||||
$children_ids_by_variation_attributes[ $str_attributes ]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string representation of variation attributes from a given product variation.
|
||||
*
|
||||
* @param mixed $product_variation Product variation
|
||||
*
|
||||
* @return string Variation attributes
|
||||
*/
|
||||
protected function get_str_variation_attributes( $product_variation ) {
|
||||
$product_variation = wc_get_product( $product_variation );
|
||||
if ( ! is_callable( array( $product_variation, 'get_formatted_variation_attributes' ) ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (string) wc_get_formatted_variation( $product_variation, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated, do not use. Previously took care of saving product data for variations.
|
||||
*
|
||||
* @deprecated 8.3.0
|
||||
*
|
||||
* @param int $variation_id
|
||||
* @param int $index
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
// phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
public function save_variation_product_data( $variation_id, $index ) {
|
||||
wc_deprecated_function( __METHOD__, '8.3.0', __CLASS__ . '::handle_product_variation_save' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated, do not use. Previously took care of saving product data.
|
||||
*
|
||||
* @deprecated 8.3.0
|
||||
*
|
||||
* @param int $subscription_product_id
|
||||
* @param int|null $index
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
// phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
public function save_subscription_product_data( int $subscription_product_id, ?int $index = null ) {
|
||||
wc_deprecated_function( __METHOD__, '8.3.0', __CLASS__ . '::handle_product_save' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated, do not use. Previously set up assets for the Subscription Downloads extension.
|
||||
*
|
||||
* @deprecated 8.3.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function scripts() {
|
||||
wc_deprecated_function( __METHOD__, '8.3.0' );
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
|
||||
/**
|
||||
* Registers and manages settings related to linked downloadable files functionality.
|
||||
*
|
||||
* @internal This class is used internally by WooCommerce Subscriptions. It is not intended for third party use, and may change at any time.
|
||||
*/
|
||||
class WC_Subscription_Downloads_Settings {
|
||||
public function __construct() {
|
||||
add_filter( 'woocommerce_subscription_settings', array( $this, 'add_settings' ) );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if WooCommerce Subscription Downloads plugin is enabled and add a warning about the bundled feature if it is.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
public static function add_notice_about_bundled_feature() {
|
||||
$screen = get_current_screen();
|
||||
if ( ! $screen ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$is_subscriptions_settings_page = 'woocommerce_page_wc-settings' === $screen->id && isset( $_GET['tab'] ) && 'subscriptions' === sanitize_text_field( wp_unslash( $_GET['tab'] ) );
|
||||
|
||||
// Only show notice on plugins page or subscriptions settings page.
|
||||
if ( 'plugins' !== $screen->id && ! $is_subscriptions_settings_page ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Constants::get_constant( 'WC_SUBSCRIPTION_DOWNLOADS_VERSION' ) ) {
|
||||
$message = __( 'WooCommerce Subscription Downloads is now part of WooCommerce Subscriptions — no extra plugin needed. You can deactivate and uninstall WooCommerce Subscription Downloads via the plugin admin screen.', 'woocommerce-subscriptions' );
|
||||
|
||||
wp_admin_notice(
|
||||
$message,
|
||||
array(
|
||||
'type' => 'warning',
|
||||
'dismissible' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds our settings to the main subscription settings page.
|
||||
*
|
||||
* @param array $settings The full subscription settings array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_settings( array $settings ): array {
|
||||
$download_settings = array(
|
||||
array(
|
||||
'name' => __( 'Downloads', 'woocommerce-subscriptions' ),
|
||||
'type' => 'title',
|
||||
'id' => WC_Subscriptions_Admin::$option_prefix . '_downloads_settings',
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Enable downloadable file sharing', 'woocommerce-subscriptions' ),
|
||||
'desc' => __( 'Allow downloadable files from simple and variable products to be shared with subscription products so they are available to active subscribers.', 'woocommerce-subscriptions' ),
|
||||
'id' => WC_Subscriptions_Admin::$option_prefix . '_enable_downloadable_file_linking',
|
||||
'default' => 'no',
|
||||
'type' => 'checkbox',
|
||||
'row_class' => 'enable-downloadable-file-linking',
|
||||
),
|
||||
array(
|
||||
'type' => 'sectionend',
|
||||
'id' => WC_Subscriptions_Admin::$option_prefix . '_downloads_settings',
|
||||
),
|
||||
);
|
||||
|
||||
// Insert the switch settings in after the synchronisation section otherwise add them to the end.
|
||||
if ( ! WC_Subscriptions_Admin::insert_setting_after( $settings, WC_Subscriptions_Admin::$option_prefix . '_gifting', $download_settings, 'multiple-settings', 'sectionend' ) ) {
|
||||
$settings = array_merge( $settings, $download_settings );
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Subscriptions Downloads is enabled.
|
||||
* @since 8.1.0
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_enabled() {
|
||||
return get_option( WC_Subscriptions_Admin::$option_prefix . '_enable_downloadable_file_linking', 'no' ) === 'yes';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Point of entry for our 'linked downloadable files' functionality.
|
||||
*
|
||||
* Along with other classes in this directory, this used to exist as a standalone plugin (important to note because, for
|
||||
* backwards compatibility reasons, that may place limits on future refactoring).
|
||||
*
|
||||
* The overall goal is to let merchants associate individual downloadable products with subscription products.
|
||||
* Customers who purchase the subscription product then automatically are granted acccess to the relevant downloadable
|
||||
* files.
|
||||
*
|
||||
* This class sets up the functionality, and also provides a high-level interface through methods such as
|
||||
* get_order_downloads( $order ) and get_subscriptions( $product_id ).
|
||||
*
|
||||
* @since 8.1.0
|
||||
*/
|
||||
class WC_Subscription_Downloads {
|
||||
/**
|
||||
* Initialize the various subsystems that drive 'linked downloadable files' functionality.
|
||||
*/
|
||||
public static function setup(): void {
|
||||
|
||||
if ( WC_Subscription_Downloads_Settings::is_enabled() ) {
|
||||
new WC_Subscription_Downloads_Order();
|
||||
|
||||
if ( is_admin() ) {
|
||||
new WC_Subscription_Downloads_Products();
|
||||
new WC_Subscription_Downloads_Ajax();
|
||||
}
|
||||
}
|
||||
|
||||
// Needs to load on admin even when downloads is disabled.
|
||||
// To ensure the settings are added to the subscription settings page.
|
||||
if ( is_admin() ) {
|
||||
new WC_Subscription_Downloads_Settings();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the plugin.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function install() {
|
||||
new WC_Subscription_Downloads_Install();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the ID of a downloadable product, returns an array of linked subscription product IDs.
|
||||
*
|
||||
* @param int $product_id
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_subscriptions( $product_id ) {
|
||||
global $wpdb;
|
||||
|
||||
$query = $wpdb->get_results( $wpdb->prepare( "SELECT subscription_id FROM {$wpdb->prefix}woocommerce_subscription_downloads WHERE product_id = %d", $product_id ), ARRAY_A );
|
||||
|
||||
$subscriptions = array();
|
||||
foreach ( $query as $item ) {
|
||||
$subscriptions[] = $item['subscription_id'];
|
||||
}
|
||||
|
||||
return $subscriptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get downloadable products from a subscription.
|
||||
*
|
||||
* @param int $subscription_id
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_downloadable_products( $subscription_id, $subscription_variable_id = '' ) {
|
||||
global $wpdb;
|
||||
|
||||
$query = $wpdb->get_results( $wpdb->prepare( "SELECT product_id FROM {$wpdb->prefix}woocommerce_subscription_downloads WHERE subscription_id = %d OR subscription_id = %d", $subscription_id, $subscription_variable_id ), ARRAY_A );
|
||||
|
||||
$products = array();
|
||||
foreach ( $query as $item ) {
|
||||
$products[] = $item['product_id'];
|
||||
}
|
||||
|
||||
return $products;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get order download files.
|
||||
*
|
||||
* @param WC_Order $order Order data.
|
||||
*
|
||||
* @return array Download data (name, file and download_url).
|
||||
*/
|
||||
public static function get_order_downloads( $order ) {
|
||||
$downloads = array();
|
||||
|
||||
if ( class_exists( 'WC_Subscriptions_Core_Plugin' ) || version_compare( WC_Subscriptions::$version, '2.0.0', '>=' ) ) {
|
||||
$contains_subscription = wcs_order_contains_subscription( $order );
|
||||
} else {
|
||||
$contains_subscription = WC_Subscriptions_Order::order_contains_subscription( $order );
|
||||
}
|
||||
|
||||
if ( 0 < count( $order->get_items() ) && $contains_subscription && $order->is_download_permitted() ) {
|
||||
foreach ( $order->get_items() as $item ) {
|
||||
|
||||
// Gets the downloadable products.
|
||||
$downloadable_products = self::get_downloadable_products( $item['product_id'], $item['variation_id'] );
|
||||
|
||||
if ( $downloadable_products ) {
|
||||
foreach ( $downloadable_products as $product_id ) {
|
||||
$_item = array(
|
||||
'product_id' => $product_id,
|
||||
'variation_id' => '',
|
||||
);
|
||||
|
||||
// Get the download data.
|
||||
if ( version_compare( WC_VERSION, '3.0', '<' ) ) {
|
||||
$_downloads = $order->get_item_downloads( $_item );
|
||||
} else {
|
||||
$order_item = new WC_Order_Item_Product();
|
||||
$product = wc_get_product( $product_id );
|
||||
if ( empty( $product ) ) {
|
||||
continue;
|
||||
}
|
||||
$order_item->set_product( $product );
|
||||
$order_item->set_order_id( $order->get_id() );
|
||||
$_downloads = $order_item->get_item_downloads();
|
||||
}
|
||||
|
||||
if ( empty( $_downloads ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $_downloads as $download ) {
|
||||
$downloads[] = $download;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $downloads;
|
||||
}
|
||||
}
|
||||
|
|
@ -69,10 +69,20 @@ class WCS_Cart_Early_Renewal extends WCS_Cart_Renewal {
|
|||
|
||||
if ( wcs_can_user_renew_early( $subscription ) && $subscription->payment_method_supports( 'subscription_date_changes' ) && $subscription->has_status( 'active' ) ) {
|
||||
|
||||
$actions['subscription_renewal_early'] = array(
|
||||
$action = array(
|
||||
'url' => wcs_get_early_renewal_url( $subscription ),
|
||||
'name' => __( 'Renew now', 'woocommerce-subscriptions' ),
|
||||
'role' => 'link',
|
||||
);
|
||||
|
||||
// Set role to 'button' if renewal via modal is enabled (it opens a modal).
|
||||
// Modal ID is set to a predictable value containing the subscription ID for aria-controls.
|
||||
if ( WCS_Early_Renewal_Manager::is_early_renewal_via_modal_enabled() ) {
|
||||
$action['role'] = 'button';
|
||||
$action['modal_id'] = 'wcs-early-renewal-modal-' . $subscription->get_id();
|
||||
}
|
||||
|
||||
$actions['subscription_renewal_early'] = $action;
|
||||
}
|
||||
|
||||
return $actions;
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ class WCS_Early_Renewal_Modal_Handler {
|
|||
|
||||
if ( wc_wp_theme_get_element_class_name( 'button' ) ) {
|
||||
$place_order_action['attributes']['class'] .= ' ' . wc_wp_theme_get_element_class_name( 'button' );
|
||||
$place_order_action['attributes']['role'] = 'button';
|
||||
}
|
||||
|
||||
$callback_args = array(
|
||||
|
|
@ -59,6 +60,8 @@ class WCS_Early_Renewal_Modal_Handler {
|
|||
);
|
||||
|
||||
$modal = new WCS_Modal( $callback_args, '.subscription_renewal_early', 'callback', __( 'Renew early', 'woocommerce-subscriptions' ) );
|
||||
// Set the modal ID to match the predictable value used for aria-controls in subscription-details.php
|
||||
$modal->set_id( 'wcs-early-renewal-modal-' . $subscription->get_id() );
|
||||
$modal->add_action( $place_order_action );
|
||||
$modal->print_html();
|
||||
}
|
||||
|
|
@ -178,6 +181,10 @@ class WCS_Early_Renewal_Modal_Handler {
|
|||
$user_id = ! empty( $user_id ) ? absint( $user_id ) : get_current_user_id();
|
||||
$subscription = wcs_get_subscription( $subscription );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! WCS_Early_Renewal_Manager::is_early_renewal_via_modal_enabled() || ! wcs_can_user_renew_early( $subscription, $user_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -468,7 +468,7 @@ class WCS_Gifting {
|
|||
<?php
|
||||
if ( 'WooCommerce Memberships' === $plugin_name ) {
|
||||
// translators: 1$-2$: opening and closing <strong> tags, 3$ plugin name, 4$ required plugin version, 5$-6$: opening and closing link tags, leads to plugins.php in admin, 7$: line break, 8$-9$ Opening and closing small tags.
|
||||
printf( esc_html__( '%1$sWooCommerce Subscriptions Gifting Membership integration is inactive.%2$s In order to integrate with WooCommerce Memberships, WooCommerce Subscriptions Gifting requires %3$s %4$s or newer. %5$sPlease update »%6$s %7$s%8$sNote: All other WooCommerce Subscriptions Gifting features will remain available, however purchasing membership plans for recipients will fail to grant the membership to the gift recipient.%9$s', 'woocommerce-subscriptions' ), '<strong>', '</strong>', esc_html( $plugin_name ), esc_html( $required_version ), '<a href="' . esc_url( admin_url( 'plugins.php' ) ) . '">', '</a>', '</br>', '<small>', '</small>' );
|
||||
printf( esc_html__( '%1$sWooCommerce Subscriptions Gifting Membership integration is inactive.%2$s In order to integrate with WooCommerce Memberships, WooCommerce Subscriptions Gifting requires %3$s %4$s or newer. %5$sPlease update »%6$s %7$s%8$sNote: All other WooCommerce Subscriptions Gifting features will remain available, however purchasing membership plans for recipients will fail to grant the membership to the gift recipient.%9$s', 'woocommerce-subscriptions' ), '<strong>', '</strong>', esc_html( $plugin_name ), esc_html( $required_version ), '<a href="' . esc_url( admin_url( 'plugins.php' ) ) . '">', '</a>', '<br>', '<small>', '</small>' );
|
||||
} else {
|
||||
// translators: 1$-2$: opening and closing <strong> tags, 3$ plugin name, 4$ required plugin version, 5$-6$: opening and closing link tags, leads to plugins.php in admin.
|
||||
printf( esc_html__( '%1$sWooCommerce Subscriptions Gifting is inactive.%2$s This version of WooCommerce Subscriptions Gifting requires %3$s %4$s or newer. %5$sPlease update »%6$s', 'woocommerce-subscriptions' ), '<strong>', '</strong>', esc_html( $plugin_name ), esc_html( $required_version ), '<a href="' . esc_url( admin_url( 'plugins.php' ) ) . '">', '</a>' );
|
||||
|
|
@ -517,7 +517,7 @@ class WCS_Gifting {
|
|||
public static function is_gifted_subscription( $subscription ) {
|
||||
$is_gifted_subscription = false;
|
||||
|
||||
if ( ! $subscription instanceof WC_Subscription ) {
|
||||
if ( is_int( $subscription ) ) {
|
||||
$subscription = wcs_get_subscription( $subscription );
|
||||
}
|
||||
|
||||
|
|
@ -654,20 +654,17 @@ class WCS_Gifting {
|
|||
* Retrieve the recipient user ID from a subscription.
|
||||
*
|
||||
* @param WC_Subscription $subscription Subscription object.
|
||||
*
|
||||
* @return string The recipient's user ID. Returns an empty string if there is no recipient set.
|
||||
*/
|
||||
public static function get_recipient_user( $subscription ) {
|
||||
$recipient_user_id = '';
|
||||
|
||||
if ( method_exists( $subscription, 'get_meta' ) ) {
|
||||
if ( $subscription->meta_exists( '_recipient_user' ) ) {
|
||||
$recipient_user_id = $subscription->get_meta( '_recipient_user' );
|
||||
}
|
||||
} else { // WC < 3.0.
|
||||
$recipient_user_id = $subscription->recipient_user;
|
||||
// There may be cases, especially when emails are being previewed in the customizer, where we receive something
|
||||
// other than a WC_Subscription object.
|
||||
if ( $subscription instanceof WC_Subscription && $subscription->meta_exists( '_recipient_user' ) ) {
|
||||
return $subscription->get_meta( '_recipient_user' );
|
||||
}
|
||||
|
||||
return $recipient_user_id;
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -677,8 +674,9 @@ class WCS_Gifting {
|
|||
* @param int $user_id The user ID of the user to set as the recipient on the subscription.
|
||||
* @param string $save Whether to save the data or not, 'save' to save the data, otherwise it won't be saved.
|
||||
* @param int $meta_id The meta ID of existing meta data if you wish to overwrite an existing recipient meta value.
|
||||
* @param WC_Order $order Order object.
|
||||
*/
|
||||
public static function set_recipient_user( &$subscription, $user_id, $save = 'save', $meta_id = 0 ) {
|
||||
public static function set_recipient_user( &$subscription, $user_id, $save = 'save', $meta_id = 0, ?WC_Order $order = null ) {
|
||||
$current_user_id = absint( self::get_recipient_user( $subscription ) );
|
||||
$subscription->recipient_user = $user_id;
|
||||
|
||||
|
|
@ -690,7 +688,9 @@ class WCS_Gifting {
|
|||
$gifting_subcription_item = reset( $gifting_subscription_items );
|
||||
|
||||
if ( ! empty( $gifting_subcription_item ) ) {
|
||||
$order = wc_get_order( $subscription->get_parent_id() );
|
||||
if ( ! $order ) {
|
||||
$order = wc_get_order( $subscription->get_parent_id() );
|
||||
}
|
||||
foreach ( $order->get_items() as $order_item ) {
|
||||
if ( $order_item->get_meta( '_wcsg_cart_key' ) === $gifting_subcription_item->get_meta( '_wcsg_cart_key' ) ) {
|
||||
$order_item->add_meta_data( 'wcsg_recipient', 'wcsg_recipient_id_' . $user_id, true );
|
||||
|
|
|
|||
|
|
@ -33,32 +33,8 @@ class WCSG_Admin_Welcome_Announcement {
|
|||
|
||||
$screen = get_current_screen();
|
||||
|
||||
$script_asset_path = \WC_Subscriptions_Plugin::instance()->get_plugin_directory( 'build/gifting-welcome-announcement.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-gifting-welcome-announcement',
|
||||
plugins_url( '/build/gifting-welcome-announcement.js', WC_Subscriptions::$plugin_file ),
|
||||
$script_asset['dependencies'],
|
||||
$script_asset['version'],
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'wcs-gifting-welcome-announcement',
|
||||
'wcs-admin',
|
||||
'wcsGiftingSettings',
|
||||
array(
|
||||
'imagesPath' => plugins_url( '/assets/images', WC_Subscriptions::$plugin_file ),
|
||||
|
|
@ -68,19 +44,6 @@ class WCSG_Admin_Welcome_Announcement {
|
|||
'isSubscriptionsListing' => 'woocommerce_page_wc-orders--shop_subscription' === $screen->id,
|
||||
)
|
||||
);
|
||||
|
||||
wp_enqueue_style(
|
||||
'wcs-gifting-welcome-announcement',
|
||||
plugins_url( '/build/style-gifting-welcome-announcement.css', WC_Subscriptions::$plugin_file ),
|
||||
array( 'wp-components' ),
|
||||
$script_asset['version']
|
||||
);
|
||||
|
||||
wp_set_script_translations(
|
||||
'wcs-gifting-welcome-announcement',
|
||||
'woocommerce-subscriptions',
|
||||
plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'languages'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class WCSG_Admin {
|
|||
|
||||
add_filter( 'woocommerce_order_items_meta_get_formatted', __CLASS__ . '::remove_recipient_order_item_meta', 1, 1 );
|
||||
|
||||
add_filter( 'woocommerce_subscription_settings', __CLASS__ . '::add_settings', 10, 1 );
|
||||
add_filter( 'woocommerce_subscription_settings', __CLASS__ . '::add_settings' );
|
||||
|
||||
if ( wcsg_is_wc_subscriptions_pre( '2.3.5' ) ) {
|
||||
add_filter( 'request', __CLASS__ . '::request_query', 11, 1 );
|
||||
|
|
@ -112,7 +112,12 @@ class WCSG_Admin {
|
|||
|
||||
if ( 'order_title' === $column && WCS_Gifting::is_gifted_subscription( $subscription ) ) {
|
||||
|
||||
$recipient_id = WCS_Gifting::get_recipient_user( $subscription );
|
||||
$recipient_id = WCS_Gifting::get_recipient_user( $subscription );
|
||||
|
||||
if ( ! $recipient_id ) {
|
||||
return $column_content;
|
||||
}
|
||||
|
||||
$recipient_user = get_userdata( $recipient_id );
|
||||
$recipient_name = '<a href="' . esc_url( get_edit_user_link( $recipient_id ) ) . '">';
|
||||
|
||||
|
|
@ -172,50 +177,52 @@ class WCSG_Admin {
|
|||
* @return array $settings New set of settings.
|
||||
*/
|
||||
public static function add_settings( $settings ) {
|
||||
|
||||
return array_merge(
|
||||
$settings,
|
||||
$gifting_settings = array(
|
||||
array(
|
||||
array(
|
||||
'name' => __( 'Gifting', 'woocommerce-subscriptions' ),
|
||||
'type' => 'title',
|
||||
'id' => self::$option_prefix,
|
||||
'name' => __( 'Gifting', 'woocommerce-subscriptions' ),
|
||||
'type' => 'title',
|
||||
'id' => self::$option_prefix,
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Enable gifting', 'woocommerce-subscriptions' ),
|
||||
'desc' => __( 'Allow shoppers to gift a subscription', 'woocommerce-subscriptions' ),
|
||||
'id' => self::$option_prefix . '_enable_gifting',
|
||||
'default' => 'no',
|
||||
'type' => 'checkbox',
|
||||
'row_class' => 'enable-gifting',
|
||||
),
|
||||
array(
|
||||
'name' => '',
|
||||
'desc' => __( 'You can override this global setting on each product.', 'woocommerce-subscriptions' ),
|
||||
'id' => self::$option_prefix . '_default_option',
|
||||
'default' => 'disabled',
|
||||
'type' => 'radio',
|
||||
'desc_at_end' => true,
|
||||
'row_class' => 'gifting-radios',
|
||||
'options' => array(
|
||||
'enabled' => __( 'Enabled for all products', 'woocommerce-subscriptions' ),
|
||||
'disabled' => __( 'Disabled for all products', 'woocommerce-subscriptions' ),
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Enable gifting', 'woocommerce-subscriptions' ),
|
||||
'desc' => __( 'Allow shoppers to gift a subscription', 'woocommerce-subscriptions' ),
|
||||
'id' => self::$option_prefix . '_enable_gifting',
|
||||
'default' => 'no',
|
||||
'type' => 'checkbox',
|
||||
'row_class' => 'enable-gifting',
|
||||
),
|
||||
array(
|
||||
'name' => '',
|
||||
'desc' => __( 'You can override this global setting on each product.', 'woocommerce-subscriptions' ),
|
||||
'id' => self::$option_prefix . '_default_option',
|
||||
'default' => 'disabled',
|
||||
'type' => 'radio',
|
||||
'desc_at_end' => true,
|
||||
'row_class' => 'gifting-radios',
|
||||
'options' => array(
|
||||
'enabled' => __( 'Enabled for all products', 'woocommerce-subscriptions' ),
|
||||
'disabled' => __( 'Disabled for all products', 'woocommerce-subscriptions' ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Gifting Checkbox Text', 'woocommerce-subscriptions' ),
|
||||
'desc' => __( 'This is what shoppers will see in the product page and cart.', 'woocommerce-subscriptions' ),
|
||||
'id' => self::$option_prefix . '_gifting_checkbox_text',
|
||||
'default' => __( 'This is a gift', 'woocommerce-subscriptions' ),
|
||||
'type' => 'text',
|
||||
'row_class' => 'gifting-checkbox-text',
|
||||
),
|
||||
array(
|
||||
'type' => 'sectionend',
|
||||
'id' => self::$option_prefix,
|
||||
),
|
||||
)
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Gifting Checkbox Text', 'woocommerce-subscriptions' ),
|
||||
'desc' => __( 'This is what shoppers will see in the product page and cart.', 'woocommerce-subscriptions' ),
|
||||
'id' => self::$option_prefix . '_gifting_checkbox_text',
|
||||
'default' => __( 'This is a gift', 'woocommerce-subscriptions' ),
|
||||
'type' => 'text',
|
||||
'row_class' => 'gifting-checkbox-text',
|
||||
),
|
||||
array(
|
||||
'type' => 'sectionend',
|
||||
'id' => self::$option_prefix,
|
||||
),
|
||||
);
|
||||
|
||||
if ( ! WC_Subscriptions_Admin::insert_setting_after( $settings, WC_Subscriptions_Admin::$option_prefix . '_renewal_options', $gifting_settings, 'multiple_settings', 'sectionend' ) ) {
|
||||
return array_merge( $settings, $gifting_settings );
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -277,9 +284,12 @@ class WCSG_Admin {
|
|||
$user_string = '';
|
||||
$user_id = '';
|
||||
if ( WCS_Gifting::is_gifted_subscription( $subscription ) ) {
|
||||
$user_id = WCS_Gifting::get_recipient_user( $subscription );
|
||||
$user = get_user_by( 'id', $user_id );
|
||||
$user_string = esc_html( $user->display_name ) . ' (#' . absint( $user->ID ) . ' – ' . esc_html( $user->user_email );
|
||||
$user_id = WCS_Gifting::get_recipient_user( $subscription );
|
||||
|
||||
if ( $user_id ) {
|
||||
$user = get_user_by( 'id', $user_id );
|
||||
$user_string = esc_html( $user->display_name ) . ' (#' . absint( $user->ID ) . ' – ' . esc_html( $user->user_email );
|
||||
}
|
||||
}
|
||||
|
||||
if ( is_callable( array( 'WCS_Select2', 'render' ) ) ) {
|
||||
|
|
|
|||
|
|
@ -79,11 +79,6 @@ class WCSG_Email {
|
|||
* Hooks up all of WCS Gifting emails after the WooCommerce object is constructed.
|
||||
*/
|
||||
public static function hook_email() {
|
||||
|
||||
add_action( 'woocommerce_created_customer', __CLASS__ . '::maybe_remove_wc_new_customer_email', 9, 2 );
|
||||
add_action( 'woocommerce_created_customer', __CLASS__ . '::send_new_recipient_user_email', 10, 3 );
|
||||
add_action( 'woocommerce_created_customer', __CLASS__ . '::maybe_reattach_wc_new_customer_email', 11, 2 );
|
||||
|
||||
add_action( 'subscriptions_activated_for_order', __CLASS__ . '::maybe_send_recipient_order_emails', 11, 1 );
|
||||
|
||||
$renewal_notification_actions = array(
|
||||
|
|
@ -154,50 +149,6 @@ class WCSG_Email {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a cart item contains recipient data matching the new customer, dont send the core WooCommerce new customer email.
|
||||
*
|
||||
* @param int $customer_id The ID of the new customer being created.
|
||||
* @param array $new_customer_data Array of data associated to the new customer.
|
||||
*/
|
||||
public static function maybe_remove_wc_new_customer_email( $customer_id, $new_customer_data ) {
|
||||
|
||||
if ( ! WC()->cart || ! WC()->cart->cart_contents ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( WC()->cart->cart_contents as $key => $item ) {
|
||||
if ( ! empty( $item['wcsg_gift_recipients_email'] ) ) {
|
||||
if ( $item['wcsg_gift_recipients_email'] == $new_customer_data['user_email'] ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
|
||||
remove_action( current_filter(), array( 'WC_Emails', 'send_transactional_email' ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a cart item contains recipient data matching the new customer, reattach the core WooCommerce new customer email.
|
||||
*
|
||||
* @param int $customer_id The ID of the new customer being created.
|
||||
* @param array $new_customer_data Array of data associated to the new customer.
|
||||
*/
|
||||
public static function maybe_reattach_wc_new_customer_email( $customer_id, $new_customer_data ) {
|
||||
|
||||
if ( ! WC()->cart || ! WC()->cart->cart_contents ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( WC()->cart->cart_contents as $key => $item ) {
|
||||
if ( ! empty( $item['wcsg_gift_recipients_email'] ) ) {
|
||||
if ( $item['wcsg_gift_recipients_email'] == $new_customer_data['user_email'] ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
|
||||
add_action( current_filter(), array( 'WC_Emails', 'send_transactional_email' ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates purchaser new recipient user email.
|
||||
*
|
||||
|
|
@ -217,28 +168,6 @@ class WCSG_Email {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a cart item contains recipient data matching the new customer, init the mailer and call the notification for new recipient customers.
|
||||
*
|
||||
* @param int $customer_id The ID of the new customer being created.
|
||||
* @param array $new_customer_data Recipient's user data.
|
||||
* @param bool $password_generated Whether the password has been generated for the customer.
|
||||
*/
|
||||
public static function send_new_recipient_user_email( $customer_id, $new_customer_data, $password_generated ) {
|
||||
if ( ! WC()->cart || ! WC()->cart->cart_contents ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( WC()->cart->cart_contents as $key => $item ) {
|
||||
if ( isset( $item['wcsg_gift_recipients_email'] ) ) {
|
||||
if ( $item['wcsg_gift_recipients_email'] === $new_customer_data['user_email'] ) {
|
||||
self::generate_new_recipient_user_email( get_current_user_id(), $customer_id );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will get the necessary data to resend the new recipient new email.
|
||||
*
|
||||
|
|
@ -457,15 +386,34 @@ class WCSG_Email {
|
|||
}
|
||||
|
||||
/**
|
||||
* If a cart item contains recipient data matching the new customer, init the mailer and call the notification for new recipient customers.
|
||||
*
|
||||
* @param int $customer_id The ID of the new customer being created.
|
||||
* @param array $new_customer_data Customer data.
|
||||
* @param bool $password_generated Whether the password has been generated for the customer.
|
||||
* @deprecated 2.0
|
||||
* Hooks into the WooCommerce created customer action to prevent sending the core WooCommerce new customer email and send the Gifting new recipient user email instead.
|
||||
*/
|
||||
public static function send_new_recient_user_email( $customer_id, $new_customer_data, $password_generated ) {
|
||||
_deprecated_function( __METHOD__, '2.0.0', __CLASS__ . '::send_new_recipient_user_email()' );
|
||||
self::send_new_recipient_user_email( $customer_id, $new_customer_data, $password_generated );
|
||||
public static function use_gifting_new_account_email() {
|
||||
add_action( 'woocommerce_created_customer', __CLASS__ . '::remove_wc_new_customer_email', 9, 0 );
|
||||
add_action( 'woocommerce_created_customer', __CLASS__ . '::send_new_recipient_user_email', 10, 1 );
|
||||
add_action( 'woocommerce_created_customer', __CLASS__ . '::reattach_wc_new_customer_email', 11, 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent sending the core WooCommerce new customer email.
|
||||
*/
|
||||
public static function remove_wc_new_customer_email() {
|
||||
remove_action( current_filter(), array( 'WC_Emails', 'send_transactional_email' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the Gifting new recipient user email. Overriding the core WooCommerce new customer email.
|
||||
*
|
||||
* @param int $customer_id The ID of the new customer being created.
|
||||
*/
|
||||
public static function send_new_recipient_user_email( $customer_id ) {
|
||||
self::generate_new_recipient_user_email( get_current_user_id(), $customer_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reattaches the core WooCommerce new customer email after sending the gifting new account email.
|
||||
*/
|
||||
public static function reattach_wc_new_customer_email() {
|
||||
add_action( current_filter(), array( 'WC_Emails', 'send_transactional_email' ) );
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ class WCSG_Recipient_Management {
|
|||
add_action( 'woocommerce_subscription_status_updated', __CLASS__ . '::maybe_update_recipient_role', 10, 2 );
|
||||
|
||||
// Hooked onto priority 8 for compatibility with WooCommerce Memberships.
|
||||
add_action( 'woocommerce_order_status_completed', __CLASS__ . '::maybe_create_recipient', 8 );
|
||||
add_action( 'woocommerce_order_status_processing', __CLASS__ . '::maybe_create_recipient', 8 );
|
||||
add_action( 'woocommerce_order_status_completed', __CLASS__ . '::maybe_create_recipient', 8, 2 );
|
||||
add_action( 'woocommerce_order_status_processing', __CLASS__ . '::maybe_create_recipient', 8, 2 );
|
||||
|
||||
if ( wcsg_is_woocommerce_pre( '3.0' ) ) {
|
||||
add_action( 'woocommerce_add_order_item_meta', __CLASS__ . '::maybe_add_recipient_order_item_meta', 10, 2 );
|
||||
|
|
@ -619,12 +619,13 @@ class WCSG_Recipient_Management {
|
|||
* When orders are processed/completed, create new recipients and attach shipping information to gifted subscriptions.
|
||||
*
|
||||
* @param int $order_id Order ID.
|
||||
* @param WC_Order $order Order object.
|
||||
*/
|
||||
public static function maybe_create_recipient( $order_id ) {
|
||||
public static function maybe_create_recipient( $order_id, ?WC_Order $order = null ) {
|
||||
$subscriptions = wcs_get_subscriptions_for_order( $order_id );
|
||||
|
||||
foreach( $subscriptions as $subscription ) {
|
||||
self::maybe_create_recipient_and_attach_shipping_information( $subscription );
|
||||
self::maybe_create_recipient_and_attach_shipping_information( $subscription, $order );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -632,8 +633,9 @@ class WCSG_Recipient_Management {
|
|||
* Maybe create a recipient user and attach shipping information to a subscription.
|
||||
*
|
||||
* @param WC_Subscription $subscription The subscription object.
|
||||
* @param WC_Order $order Order object.
|
||||
*/
|
||||
public static function maybe_create_recipient_and_attach_shipping_information( $subscription ) {
|
||||
public static function maybe_create_recipient_and_attach_shipping_information( $subscription, ?WC_Order $order = null ) {
|
||||
$recipient_user_email_address = $subscription->get_meta( '_recipient_user_email_address' );
|
||||
|
||||
if ( ! $recipient_user_email_address ) {
|
||||
|
|
@ -644,6 +646,7 @@ class WCSG_Recipient_Management {
|
|||
|
||||
// Create a new user if the recipient's email doesn't already exist.
|
||||
if ( ! $recipient_user_id ) {
|
||||
WCSG_Email::use_gifting_new_account_email();
|
||||
$recipient_user_id = WCS_Gifting::create_recipient_user( $recipient_user_email_address );
|
||||
}
|
||||
|
||||
|
|
@ -651,7 +654,7 @@ class WCSG_Recipient_Management {
|
|||
return;
|
||||
}
|
||||
|
||||
WCS_Gifting::set_recipient_user( $subscription, $recipient_user_id );
|
||||
WCS_Gifting::set_recipient_user( $subscription, $recipient_user_id, 'save', 0, $order );
|
||||
|
||||
$subscription->set_shipping_first_name( get_user_meta( $recipient_user_id, 'shipping_first_name', true ) );
|
||||
$subscription->set_shipping_last_name( get_user_meta( $recipient_user_id, 'shipping_last_name', true ) );
|
||||
|
|
@ -665,14 +668,4 @@ class WCSG_Recipient_Management {
|
|||
|
||||
$subscription->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach WooCommerce version dependent hooks
|
||||
*
|
||||
* @since 7.8.0 - Originally implemented in WooCommerce Subscriptions Gifting 1.0.1.
|
||||
* @deprecated 2.0.0
|
||||
*/
|
||||
public static function attach_dependant_hooks() {
|
||||
_deprecated_function( __METHOD__, '2.0.0', __CLASS__ . '::init()' );
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@ class WCSG_Email_Completed_Renewal_Order extends WCS_Email_Completed_Renewal_Ord
|
|||
$this->title = __( 'Completed Renewal Order - Recipient', 'woocommerce-subscriptions' );
|
||||
$this->description = __( 'Renewal order complete emails are sent to the recipient when a subscription renewal order is marked complete and usually indicates that the item for that renewal period has been shipped.', 'woocommerce-subscriptions' );
|
||||
$this->customer_email = true;
|
||||
$this->heading = __( 'Your renewal order is complete', 'woocommerce-subscriptions' );
|
||||
$this->subject = __( 'Your {blogname} renewal order from {order_date} is complete', 'woocommerce-subscriptions' );
|
||||
|
||||
$this->template_html = 'emails/recipient-completed-renewal-order.php';
|
||||
$this->template_plain = 'emails/plain/recipient-completed-renewal-order.php';
|
||||
|
|
@ -42,6 +40,28 @@ class WCSG_Email_Completed_Renewal_Order extends WCS_Email_Completed_Renewal_Ord
|
|||
WC_Email::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default e-mail subject.
|
||||
*
|
||||
* @param bool $paid Whether the order has been paid or not.
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.5.3
|
||||
* @return string
|
||||
*/
|
||||
public function get_default_subject( $paid = false ) {
|
||||
return __( 'Your {blogname} renewal order from {order_date} is complete', 'woocommerce-subscriptions' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default e-mail heading.
|
||||
*
|
||||
* @param bool $paid Whether the order has been paid or not.
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.5.3
|
||||
* @return string
|
||||
*/
|
||||
public function get_default_heading( $paid = false ) {
|
||||
return __( 'Your renewal order is complete', 'woocommerce-subscriptions' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger function.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -59,8 +59,7 @@ class WCSG_Email_Customer_New_Account extends WC_Email {
|
|||
$this->title = __( 'New Recipient Account', 'woocommerce-subscriptions' );
|
||||
$this->description = __( 'New account notification emails are sent to the subscription recipient when an account is created for them.', 'woocommerce-subscriptions' );
|
||||
$this->customer_email = true;
|
||||
$this->subject = __( 'Your account on {site_title}', 'woocommerce-subscriptions' );
|
||||
$this->heading = __( 'Welcome to {site_title}', 'woocommerce-subscriptions' );
|
||||
|
||||
$this->template_html = 'emails/new-recipient-customer.php';
|
||||
$this->template_plain = 'emails/plain/new-recipient-customer.php';
|
||||
$this->template_base = plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'templates/gifting/';
|
||||
|
|
@ -71,6 +70,29 @@ class WCSG_Email_Customer_New_Account extends WC_Email {
|
|||
WC_Email::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default e-mail subject.
|
||||
*
|
||||
* @param bool $paid Whether the order has been paid or not.
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.5.3
|
||||
* @return string
|
||||
*/
|
||||
public function get_default_subject( $paid = false ) {
|
||||
return __( 'Your account on {site_title}', 'woocommerce-subscriptions' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default e-mail heading.
|
||||
*
|
||||
* @param bool $paid Whether the order has been paid or not.
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.5.3
|
||||
* @return string
|
||||
*/
|
||||
public function get_default_heading( $paid = false ) {
|
||||
return __( 'Welcome to {site_title}', 'woocommerce-subscriptions' );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Trigger function.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -30,8 +30,7 @@ class WCSG_Email_Processing_Renewal_Order extends WCS_Email_Processing_Renewal_O
|
|||
$this->title = __( 'Processing Renewal Order - Recipient', 'woocommerce-subscriptions' );
|
||||
$this->description = __( 'This is an order notification sent to the recipient 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 {blogname} renewal order receipt from {order_date}', 'woocommerce-subscriptions' );
|
||||
|
||||
$this->template_html = 'emails/recipient-processing-renewal-order.php';
|
||||
$this->template_plain = 'emails/plain/recipient-processing-renewal-order.php';
|
||||
$this->template_base = plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'templates/gifting/';
|
||||
|
|
@ -42,6 +41,29 @@ class WCSG_Email_Processing_Renewal_Order extends WCS_Email_Processing_Renewal_O
|
|||
WC_Email::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default e-mail subject.
|
||||
*
|
||||
* @param bool $paid Whether the order has been paid or not.
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.5.3
|
||||
* @return string
|
||||
*/
|
||||
public function get_default_subject( $paid = false ) {
|
||||
return __( 'Your {blogname} renewal order receipt from {order_date}', 'woocommerce-subscriptions' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default e-mail heading.
|
||||
*
|
||||
* @param bool $paid Whether the order has been paid or not.
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.5.3
|
||||
* @return string
|
||||
*/
|
||||
public function get_default_heading( $paid = false ) {
|
||||
return __( 'Thank you for your order', 'woocommerce-subscriptions' );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Trigger function.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -51,8 +51,7 @@ class WCSG_Email_Recipient_New_Initial_Order extends WC_Email {
|
|||
$this->title = __( 'New Initial Order - Recipient', 'woocommerce-subscriptions' );
|
||||
$this->description = __( 'This email is sent to recipients notifying them of subscriptions purchased for them.', 'woocommerce-subscriptions' );
|
||||
$this->customer_email = true;
|
||||
$this->heading = __( 'New Order', 'woocommerce-subscriptions' );
|
||||
$this->subject = __( 'Your new subscriptions at {site_title}', 'woocommerce-subscriptions' );
|
||||
|
||||
$this->template_html = 'emails/recipient-new-initial-order.php';
|
||||
$this->template_plain = 'emails/plain/recipient-new-initial-order.php';
|
||||
$this->template_base = plugin_dir_path( WC_Subscriptions::$plugin_file ) . 'templates/gifting/';
|
||||
|
|
@ -63,6 +62,28 @@ class WCSG_Email_Recipient_New_Initial_Order extends WC_Email {
|
|||
WC_Email::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default e-mail subject.
|
||||
*
|
||||
* @param bool $paid Whether the order has been paid or not.
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.5.3
|
||||
* @return string
|
||||
*/
|
||||
public function get_default_subject( $paid = false ) {
|
||||
return __( 'Your new subscriptions at {site_title}', 'woocommerce-subscriptions' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default e-mail heading.
|
||||
*
|
||||
* @param bool $paid Whether the order has been paid or not.
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.5.3
|
||||
* @return string
|
||||
*/
|
||||
public function get_default_heading( $paid = false ) {
|
||||
return __( 'New Order', 'woocommerce-subscriptions' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger function.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -47,6 +47,12 @@ class WCS_Retry_Manager {
|
|||
self::$setting_id = WC_Subscriptions_Admin::$option_prefix . '_enable_retry';
|
||||
self::$admin = new WCS_Retry_Admin( self::$setting_id );
|
||||
|
||||
// Show admin notice if the retry table is missing.
|
||||
add_action( 'admin_notices', array( __CLASS__, 'maybe_show_missing_table_notice' ) );
|
||||
|
||||
// Add debug tools to the WooCommerce > Status > Tools administration screen.
|
||||
add_filter( 'woocommerce_debug_tools', array( __CLASS__, 'add_retry_manager_debug_tools' ) );
|
||||
|
||||
if ( self::is_retry_enabled() ) {
|
||||
WCS_Retry_Email::init();
|
||||
|
||||
|
|
@ -525,4 +531,88 @@ class WCS_Retry_Manager {
|
|||
public static function init_store() {
|
||||
self::store()->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the payment retry table exists.
|
||||
*
|
||||
* @return bool True if the table exists, false otherwise.
|
||||
*/
|
||||
public static function retry_table_exists(): bool {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . 'wcs_payment_retries';
|
||||
|
||||
return $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an admin notice if the retry table is missing.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function maybe_show_missing_table_notice() {
|
||||
if (
|
||||
! current_user_can( 'manage_woocommerce' ) ||
|
||||
! self::is_retry_enabled() ||
|
||||
self::retry_table_exists()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate the URL to the debug tool.
|
||||
$recreate_url = wp_nonce_url(
|
||||
admin_url( 'admin.php?page=wc-status&tab=tools&action=retry_manager' ),
|
||||
'debug_action'
|
||||
);
|
||||
|
||||
$message = sprintf(
|
||||
/* translators: %s: URL to recreate the table */
|
||||
__( 'The WooCommerce Subscriptions payment retry table is missing. This may affect the automatic payment retry functionality. <a href="%s">Click here to recreate the table</a>.', 'woocommerce-subscriptions' ),
|
||||
esc_url( $recreate_url )
|
||||
);
|
||||
|
||||
wcs_add_admin_notice( $message, 'error' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add retry manager debug tools to the WooCommerce > Status > Tools administration screen.
|
||||
*
|
||||
* @param array $tools The array of tools.
|
||||
* @return array The array of tools.
|
||||
*/
|
||||
public static function add_retry_manager_debug_tools( $tools ) {
|
||||
if ( ! self::is_retry_enabled() ) {
|
||||
return $tools;
|
||||
}
|
||||
|
||||
$tools['retry_manager'] = [
|
||||
'name' => __( 'Recreate subscriptions payment retry table', 'woocommerce-subscriptions' ),
|
||||
'button' => __( 'Recreate', 'woocommerce-subscriptions' ),
|
||||
'desc' => __( 'This tool will recreate the subscription payment retry table if it is missing.', 'woocommerce-subscriptions' ),
|
||||
'callback' => array( __CLASS__, 'recreate_payment_retry_table' ),
|
||||
];
|
||||
|
||||
return $tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recreate the payment retry table.
|
||||
*
|
||||
* @return string Informative string to show after the tool is triggered in UI.
|
||||
*/
|
||||
public static function recreate_payment_retry_table(): string {
|
||||
// Check if the table already exists.
|
||||
if ( self::retry_table_exists() ) {
|
||||
return __( 'The payment retry table already exists.', 'woocommerce-subscriptions' );
|
||||
}
|
||||
|
||||
// Create the table.
|
||||
if ( ! self::$table_maker ) {
|
||||
self::$table_maker = new WCS_Retry_Table_Maker();
|
||||
}
|
||||
|
||||
self::$table_maker->recreate_tables();
|
||||
|
||||
return __( 'Payment retry table has been successfully created.', 'woocommerce-subscriptions' );
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,9 +35,6 @@ class WCS_Email_Customer_Payment_Retry extends WCS_Email_Customer_Renewal_Invoic
|
|||
$this->template_plain = 'emails/plain/customer-payment-retry.php';
|
||||
$this->template_base = WC_Subscriptions_Plugin::instance()->get_plugin_directory( 'templates/' );
|
||||
|
||||
$this->subject = __( 'Automatic payment failed for {order_number}, we will retry {retry_time}', 'woocommerce-subscriptions' );
|
||||
$this->heading = __( 'Automatic payment failed for order {order_number}', 'woocommerce-subscriptions' );
|
||||
|
||||
// We want all the parent's methods, with none of its properties, so call its parent's constructor, rather than my parent constructor
|
||||
WC_Email::__construct();
|
||||
}
|
||||
|
|
@ -50,7 +47,7 @@ class WCS_Email_Customer_Payment_Retry extends WCS_Email_Customer_Renewal_Invoic
|
|||
* @return string
|
||||
*/
|
||||
public function get_default_subject( $paid = false ) {
|
||||
return $this->subject;
|
||||
return __( 'Automatic payment failed for {order_number}, we will retry {retry_time}', 'woocommerce-subscriptions' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -61,7 +58,7 @@ class WCS_Email_Customer_Payment_Retry extends WCS_Email_Customer_Renewal_Invoic
|
|||
* @return string
|
||||
*/
|
||||
public function get_default_heading( $paid = false ) {
|
||||
return $this->heading;
|
||||
return __( 'Automatic payment failed for order {order_number}', 'woocommerce-subscriptions' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -32,9 +32,6 @@ class WCS_Email_Payment_Retry extends WC_Email_Failed_Order {
|
|||
$this->title = __( 'Payment Retry', 'woocommerce-subscriptions' );
|
||||
$this->description = __( 'Payment retry emails are sent to chosen recipient(s) when an attempt to automatically process a subscription renewal payment has failed and a retry rule has been applied to retry the payment in the future.', 'woocommerce-subscriptions' );
|
||||
|
||||
$this->heading = __( 'Automatic renewal payment failed', 'woocommerce-subscriptions' );
|
||||
$this->subject = __( '[{site_title}] Automatic payment failed for {order_number}, retry scheduled to run {retry_time}', 'woocommerce-subscriptions' );
|
||||
|
||||
$this->template_html = 'emails/admin-payment-retry.php';
|
||||
$this->template_plain = 'emails/plain/admin-payment-retry.php';
|
||||
$this->template_base = WC_Subscriptions_Plugin::instance()->get_plugin_directory( 'templates/' );
|
||||
|
|
@ -53,7 +50,7 @@ class WCS_Email_Payment_Retry extends WC_Email_Failed_Order {
|
|||
* @return string
|
||||
*/
|
||||
public function get_default_subject() {
|
||||
return $this->subject;
|
||||
return __( '[{site_title}] Automatic payment failed for {order_number}, retry scheduled to run {retry_time}', 'woocommerce-subscriptions' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -63,7 +60,7 @@ class WCS_Email_Payment_Retry extends WC_Email_Failed_Order {
|
|||
* @return string
|
||||
*/
|
||||
public function get_default_heading() {
|
||||
return $this->heading;
|
||||
return __( 'Automatic renewal payment failed', 'woocommerce-subscriptions' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Automattic\WooCommerce_Subscriptions\Internal\Utilities\Request;
|
||||
|
||||
/**
|
||||
* A class to make it possible to switch between different subscriptions (i.e. upgrade/downgrade a subscription)
|
||||
*
|
||||
|
|
@ -44,6 +47,9 @@ class WC_Subscriptions_Switcher {
|
|||
// Add the "Switch" button to the View Subscription table
|
||||
add_action( 'woocommerce_order_item_meta_end', array( __CLASS__, 'print_switch_link' ), 10, 3 );
|
||||
|
||||
// Add hidden form inputs for AJAX add-to-cart compatibility during switches
|
||||
add_action( 'woocommerce_before_add_to_cart_button', array( __CLASS__, 'add_switch_hidden_inputs' ) );
|
||||
|
||||
// We need to create subscriptions on checkout and want to do it after almost all other extensions have added their products/items/fees
|
||||
add_action( 'woocommerce_checkout_order_processed', array( __CLASS__, 'process_checkout' ), 50, 2 );
|
||||
|
||||
|
|
@ -179,22 +185,26 @@ class WC_Subscriptions_Switcher {
|
|||
public static function subscription_switch_handler() {
|
||||
global $post;
|
||||
|
||||
// If the current user doesn't own the subscription, remove the query arg from the URL
|
||||
if ( isset( $_GET['switch-subscription'] ) && isset( $_GET['item'] ) ) {
|
||||
$switch_subscription_id = Request::get_var( 'switch-subscription' );
|
||||
$item_id = Request::get_var( 'item' );
|
||||
|
||||
$subscription = wcs_get_subscription( absint( $_GET['switch-subscription'] ) );
|
||||
$line_item = $subscription ? wcs_get_order_item( absint( $_GET['item'] ), $subscription ) : false;
|
||||
$nonce = ! empty( $_GET['_wcsnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wcsnonce'] ) ) : false;
|
||||
// If the current user doesn't own the subscription, remove the query arg from the URL
|
||||
if ( $switch_subscription_id && $item_id ) {
|
||||
|
||||
$subscription = wcs_get_subscription( absint( $switch_subscription_id ) );
|
||||
$line_item = $subscription ? wcs_get_order_item( absint( $item_id ), $subscription ) : false;
|
||||
$nonce = Request::get_var( '_wcsnonce' );
|
||||
$nonce = $nonce ? sanitize_text_field( wp_unslash( $nonce ) ) : false;
|
||||
|
||||
// Visiting a switch link for someone elses subscription or if the switch link doesn't contain a valid nonce
|
||||
if ( ! is_object( $subscription ) || empty( $nonce ) || ! wp_verify_nonce( $nonce, 'wcs_switch_request' ) || empty( $line_item ) || ! self::can_item_be_switched_by_user( $line_item, $subscription ) ) {
|
||||
|
||||
wp_safe_redirect( remove_query_arg( array( 'switch-subscription', 'auto-switch', 'item', '_wcsnonce' ) ) );
|
||||
exit();
|
||||
Request::redirect( remove_query_arg( array( 'switch-subscription', 'auto-switch', 'item', '_wcsnonce' ) ) );
|
||||
return;
|
||||
|
||||
} else {
|
||||
|
||||
if ( isset( $_GET['auto-switch'] ) ) {
|
||||
if ( Request::get_var( 'auto-switch' ) ) {
|
||||
$switch_message = __( 'You have a subscription to this product. Choosing a new subscription will replace your existing subscription.', 'woocommerce-subscriptions' );
|
||||
} else {
|
||||
$switch_message = __( 'Choose a new subscription.', 'woocommerce-subscriptions' );
|
||||
|
|
@ -239,8 +249,8 @@ class WC_Subscriptions_Switcher {
|
|||
if ( $removed_item_count > 0 ) {
|
||||
wc_add_notice( _n( 'Your cart contained an invalid subscription switch request. It has been removed.', 'Your cart contained invalid subscription switch requests. They have been removed.', $removed_item_count, 'woocommerce-subscriptions' ), 'error' );
|
||||
|
||||
wp_safe_redirect( wc_get_cart_url() );
|
||||
exit();
|
||||
Request::redirect( wc_get_cart_url() );
|
||||
return;
|
||||
}
|
||||
} elseif ( is_product() && $product = wc_get_product( $post ) ) { // Automatically initiate the switch process for limited variable subscriptions
|
||||
|
||||
|
|
@ -308,8 +318,8 @@ class WC_Subscriptions_Switcher {
|
|||
}
|
||||
|
||||
if ( apply_filters( 'wcs_initiate_auto_switch', self::can_item_be_switched_by_user( $item, $subscription ), $item, $subscription ) ) {
|
||||
wp_safe_redirect( add_query_arg( 'auto-switch', 'true', self::get_switch_url( $item_id, $item, $subscription ) ) );
|
||||
exit;
|
||||
Request::redirect( add_query_arg( 'auto-switch', 'true', self::get_switch_url( $item_id, $item, $subscription ) ) );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -560,6 +570,30 @@ class WC_Subscriptions_Switcher {
|
|||
echo wp_kses( apply_filters( 'woocommerce_subscriptions_switch_link', $switch_link, $item_id, $item, $subscription ), array( 'a' => array( 'href' => array(), 'title' => array(), 'class' => array() ) ) ); // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound
|
||||
}
|
||||
|
||||
/**
|
||||
* Add hidden form inputs for subscription switch parameters.
|
||||
*
|
||||
* When a customer is switching subscriptions, the switch parameters are passed via URL query arguments.
|
||||
* This method outputs them as hidden form inputs so they're included when AJAX add-to-cart plugins
|
||||
* serialize and submit the form via POST.
|
||||
*
|
||||
* @since 8.3.0
|
||||
*/
|
||||
public static function add_switch_hidden_inputs() {
|
||||
$switch_subscription_id = Request::get_var( 'switch-subscription' );
|
||||
$item_id = Request::get_var( 'item' );
|
||||
$nonce = Request::get_var( '_wcsnonce' );
|
||||
|
||||
// Only output if we're in a switch context with a valid nonce
|
||||
if ( ! $switch_subscription_id || ! $item_id || ! $nonce ) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<input type="hidden" name="switch-subscription" value="' . esc_attr( $switch_subscription_id ) . '" />';
|
||||
echo '<input type="hidden" name="item" value="' . esc_attr( $item_id ) . '" />';
|
||||
echo '<input type="hidden" name="_wcsnonce" value="' . esc_attr( $nonce ) . '" />';
|
||||
}
|
||||
|
||||
/**
|
||||
* The link for switching a subscription - the product page for variable subscriptions, or grouped product page for grouped subscriptions.
|
||||
*
|
||||
|
|
@ -1211,7 +1245,7 @@ class WC_Subscriptions_Switcher {
|
|||
public static function cart_contains_switches( $item_action = 'switch' ) {
|
||||
$subscription_switches = [];
|
||||
|
||||
if ( is_admin() && ( ! defined( 'DOING_AJAX' ) || false == DOING_AJAX ) ) {
|
||||
if ( is_admin() && ! wp_doing_ajax() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1282,7 +1316,33 @@ class WC_Subscriptions_Switcher {
|
|||
// If the switch is for a grouped product, we need to check the other products grouped with this one
|
||||
if ( $parent_products ) {
|
||||
foreach ( $parent_products as $parent_id ) {
|
||||
$switch_product_ids = array_unique( array_merge( $switch_product_ids, wc_get_product( $parent_id )->get_children() ) );
|
||||
$parent_product = wc_get_product( $parent_id );
|
||||
|
||||
if ( ! $parent_product ) {
|
||||
wc_get_logger()->error(
|
||||
'Parent product {parent_id} for switch product {product_id} not found',
|
||||
array(
|
||||
'parent_id' => $parent_id,
|
||||
'product_id' => $product_id,
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$parent_product_children = $parent_product->get_children();
|
||||
|
||||
if ( ! is_array( $parent_product_children ) ) {
|
||||
wc_get_logger()->error(
|
||||
'Children of parent product {parent_id} for switch product {product_id} is not an array',
|
||||
array(
|
||||
'parent_id' => $parent_id,
|
||||
'product_id' => $product_id,
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$switch_product_ids = array_unique( array_merge( $switch_product_ids, $parent_product_children ) );
|
||||
}
|
||||
} elseif ( $switch_product->is_type( 'subscription_variation' ) ) {
|
||||
$switch_product_ids[] = $switch_product->get_parent_id();
|
||||
|
|
@ -1312,7 +1372,12 @@ class WC_Subscriptions_Switcher {
|
|||
return;
|
||||
}
|
||||
|
||||
$subscription = wcs_get_subscription( $cart_item_data['subscription_switch']['subscription_id'] );
|
||||
$subscription = wcs_get_subscription( $cart_item_data['subscription_switch']['subscription_id'] );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$existing_item = wcs_get_order_item( $cart_item_data['subscription_switch']['item_id'], $subscription );
|
||||
$cart_item = WC()->cart->get_cart_item( $cart_item_key );
|
||||
|
||||
|
|
@ -1351,17 +1416,26 @@ class WC_Subscriptions_Switcher {
|
|||
|
||||
try {
|
||||
|
||||
if ( ! isset( $_GET['switch-subscription'] ) ) {
|
||||
$switch_subscription_id = Request::get_var( 'switch-subscription' );
|
||||
|
||||
if ( ! $switch_subscription_id ) {
|
||||
return $is_valid;
|
||||
}
|
||||
|
||||
if ( empty( $_GET['_wcsnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wcsnonce'] ) ), 'wcs_switch_request' ) ) {
|
||||
$nonce = Request::get_var( '_wcsnonce' );
|
||||
|
||||
if ( empty( $nonce ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $nonce ) ), 'wcs_switch_request' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$subscription = wcs_get_subscription( absint( $_GET['switch-subscription'] ) );
|
||||
$item_id = absint( $_GET['item'] );
|
||||
$item = wcs_get_order_item( $item_id, $subscription );
|
||||
$subscription = wcs_get_subscription( absint( $switch_subscription_id ) );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
throw new Exception( __( 'The subscription may have been deleted.', 'woocommerce-subscriptions' ) );
|
||||
}
|
||||
|
||||
$item_id = absint( Request::get_var( 'item' ) );
|
||||
$item = wcs_get_order_item( $item_id, $subscription );
|
||||
|
||||
// Prevent switching to non-subscription product
|
||||
if ( ! WC_Subscriptions_Product::is_subscription( $product_id ) ) {
|
||||
|
|
@ -1432,21 +1506,27 @@ class WC_Subscriptions_Switcher {
|
|||
public static function set_switch_details_in_cart( $cart_item_data, $product_id, $variation_id ) {
|
||||
|
||||
try {
|
||||
if ( ! isset( $_GET['switch-subscription'] ) ) {
|
||||
$switch_subscription_id = Request::get_var( 'switch-subscription' );
|
||||
|
||||
if ( ! $switch_subscription_id ) {
|
||||
return $cart_item_data;
|
||||
}
|
||||
|
||||
$subscription = wcs_get_subscription( absint( $_GET['switch-subscription'] ) );
|
||||
$subscription = wcs_get_subscription( absint( $switch_subscription_id ) );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
throw new Exception( __( 'The subscription may have been deleted.', 'woocommerce-subscriptions' ) );
|
||||
}
|
||||
|
||||
// Requesting a switch for someone elses subscription
|
||||
if ( ! current_user_can( 'switch_shop_subscription', $subscription->get_id() ) ) {
|
||||
wc_add_notice( __( 'You can not switch this subscription. It appears you do not own the subscription.', 'woocommerce-subscriptions' ), 'error' );
|
||||
WC()->cart->empty_cart( true );
|
||||
wp_safe_redirect( get_permalink( $product_id ) );
|
||||
exit();
|
||||
Request::redirect( get_permalink( $product_id ) );
|
||||
return;
|
||||
}
|
||||
|
||||
$item = wcs_get_order_item( absint( $_GET['item'] ), $subscription );
|
||||
$item = wcs_get_order_item( absint( Request::get_var( 'item' ) ), $subscription );
|
||||
|
||||
// Else it's a valid switch
|
||||
$product = wc_get_product( $item['product_id'] );
|
||||
|
|
@ -1455,7 +1535,33 @@ class WC_Subscriptions_Switcher {
|
|||
|
||||
if ( ! empty( $parent_products ) ) {
|
||||
foreach ( $parent_products as $parent_id ) {
|
||||
$child_products = array_unique( array_merge( $child_products, wc_get_product( $parent_id )->get_children() ) );
|
||||
$parent_product = wc_get_product( $parent_id );
|
||||
|
||||
if ( ! $parent_product ) {
|
||||
wc_get_logger()->error(
|
||||
'Parent product {parent_id} for switch product {product_id} not found',
|
||||
array(
|
||||
'parent_id' => $parent_id,
|
||||
'product_id' => $item['product_id'],
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$parent_product_children = $parent_product->get_children();
|
||||
|
||||
if ( ! is_array( $parent_product_children ) ) {
|
||||
wc_get_logger()->error(
|
||||
'Children of parent product {parent_id} for switch product {product_id} is not an array',
|
||||
array(
|
||||
'parent_id' => $parent_id,
|
||||
'product_id' => $item['product_id'],
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$child_products = array_unique( array_merge( $child_products, $parent_product_children ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1472,7 +1578,7 @@ class WC_Subscriptions_Switcher {
|
|||
|
||||
$cart_item_data['subscription_switch'] = array(
|
||||
'subscription_id' => $subscription->get_id(),
|
||||
'item_id' => absint( $_GET['item'] ),
|
||||
'item_id' => absint( Request::get_var( 'item' ) ),
|
||||
'next_payment_timestamp' => $next_payment_timestamp,
|
||||
'upgraded_or_downgraded' => '',
|
||||
);
|
||||
|
|
@ -1483,8 +1589,8 @@ class WC_Subscriptions_Switcher {
|
|||
|
||||
wc_add_notice( __( 'There was an error locating the switch details.', 'woocommerce-subscriptions' ), 'error' );
|
||||
WC()->cart->empty_cart( true );
|
||||
wp_safe_redirect( get_permalink( wc_get_page_id( 'cart' ) ) );
|
||||
exit();
|
||||
Request::redirect( get_permalink( wc_get_page_id( 'cart' ) ) );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1983,6 +2089,10 @@ class WC_Subscriptions_Switcher {
|
|||
// Subscription objects hold an internal cache of line items so we need to get an updated subscription object after changing the line item types directly in the database.
|
||||
$subscription = wcs_get_subscription( $subscription_id );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! empty( $add_note ) ) {
|
||||
$subscription->add_order_note( $add_note );
|
||||
}
|
||||
|
|
@ -2056,7 +2166,11 @@ class WC_Subscriptions_Switcher {
|
|||
$subscription->save();
|
||||
|
||||
// We just changed above the type of some items related to this subscription, so we need to reload it to get the newest items
|
||||
wcs_get_subscription( $subscription->get_id() )->calculate_totals();
|
||||
$refreshed_subscription = wcs_get_subscription( $subscription->get_id() );
|
||||
|
||||
if ( $refreshed_subscription ) {
|
||||
$refreshed_subscription->calculate_totals();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2102,6 +2216,10 @@ class WC_Subscriptions_Switcher {
|
|||
|
||||
$subscription = wcs_get_subscription( $cart_item['subscription_switch']['subscription_id'] );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_manual_subscription = $subscription->is_manual();
|
||||
|
||||
// Check for $0 / period to a non-zero $ / period and manual subscription
|
||||
|
|
@ -2546,6 +2664,10 @@ class WC_Subscriptions_Switcher {
|
|||
|
||||
$subscription = wcs_get_subscription( $cart_item['subscription_switch']['subscription_id'] );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
! self::is_single_item_subscription( $subscription ) && (
|
||||
self::has_different_length( $recurring_cart, $subscription ) ||
|
||||
|
|
@ -2607,6 +2729,11 @@ class WC_Subscriptions_Switcher {
|
|||
}
|
||||
|
||||
$subscription = wcs_get_subscription( $cart_item['subscription_switch']['subscription_id'] );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$switch_order = $subscription->get_last_order( 'all', 'switch' );
|
||||
|
||||
if ( empty( $switch_order ) ) {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
575
phpstan.neon
575
phpstan.neon
|
|
@ -66,236 +66,6 @@ parameters:
|
|||
count: 3
|
||||
path: includes/api/class-wc-rest-subscriptions-controller.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_API_Subscriptions_Customers\\:\\:\\$base\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions-customers.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_API_Subscriptions_Customers\\:\\:\\$server\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions-customers.php
|
||||
|
||||
-
|
||||
message: "#^Access to constant READABLE on an unknown class WC_API_SERVER\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions-customers.php
|
||||
|
||||
-
|
||||
message: "#^Access to private property WooCommerce\\:\\:\\$api\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions-customers.php
|
||||
|
||||
-
|
||||
message: "#^Access to property \\$WC_API_Subscriptions on an unknown class WC_API\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions-customers.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method WC_API_Subscriptions_Customers\\:\\:validate_request\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions-customers.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\$server of method WC_API_Subscriptions_Customers\\:\\:__construct\\(\\) has invalid type WC_API_Server\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions-customers.php
|
||||
|
||||
-
|
||||
message: "#^WC_API_Subscriptions_Customers\\:\\:__construct\\(\\) calls parent\\:\\:__construct\\(\\) but WC_API_Subscriptions_Customers does not extend any class\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions-customers.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_API_Subscriptions\\:\\:\\$post_type\\.$#"
|
||||
count: 7
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_API_Subscriptions\\:\\:\\$server\\.$#"
|
||||
count: 5
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WooCommerce\\:\\:\\$payment_gateways\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Access to constant ACCEPT_DATA on an unknown class WC_API_Server\\.$#"
|
||||
count: 4
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Access to constant CREATABLE on an unknown class WC_API_Server\\.$#"
|
||||
count: 2
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Access to constant DELETABLE on an unknown class WC_API_SERVER\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Access to constant DELETABLE on an unknown class WC_API_Server\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Access to constant EDITABLE on an unknown class WC_API_SERVER\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Access to constant EDITABLE on an unknown class WC_API_Server\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Access to constant READABLE on an unknown class WC_API_Server\\.$#"
|
||||
count: 7
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Access to private property WooCommerce\\:\\:\\$api\\.$#"
|
||||
count: 2
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Access to property \\$WC_API_Customers on an unknown class WC_API\\.$#"
|
||||
count: 2
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method WC_API_Subscriptions\\:\\:create_order\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method WC_API_Subscriptions\\:\\:create_order_note\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method WC_API_Subscriptions\\:\\:delete\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method WC_API_Subscriptions\\:\\:delete_order_note\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method WC_API_Subscriptions\\:\\:edit_order\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method WC_API_Subscriptions\\:\\:edit_order_note\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method WC_API_Subscriptions\\:\\:get_order\\(\\)\\.$#"
|
||||
count: 2
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method WC_API_Subscriptions\\:\\:get_order_note\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method WC_API_Subscriptions\\:\\:get_order_notes\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method WC_API_Subscriptions\\:\\:get_orders_count\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method WC_API_Subscriptions\\:\\:is_readable\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method WC_API_Subscriptions\\:\\:merge_query_args\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method WC_API_Subscriptions\\:\\:validate_request\\(\\)\\.$#"
|
||||
count: 4
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Call to method getCode\\(\\) on an unknown class WC_API_Exception\\.$#"
|
||||
count: 2
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Call to method getErrorCode\\(\\) on an unknown class WC_API_Exception\\.$#"
|
||||
count: 2
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Call to method getMessage\\(\\) on an unknown class WC_API_Exception\\.$#"
|
||||
count: 2
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Caught class WC_API_Exception not found\\.$#"
|
||||
count: 2
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Default value of the parameter \\#4 \\$page \\(int\\) of method WC_API_Subscriptions\\:\\:get_subscriptions\\(\\) is incompatible with type null\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Instantiated class WC_API_Exception not found\\.$#"
|
||||
count: 6
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^PHPDoc tag @param has invalid value \\(array data raw order data\\)\\: Unexpected token \"data\", expected variable at offset 67$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^PHPDoc tag @param references unknown parameter\\: \\$fields$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^PHPDoc tag @param references unknown parameter\\: \\$id$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^is_wp_error\\(WC_Subscription\\) will always evaluate to false\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-api-subscriptions.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WooCommerce\\:\\:\\$payment_gateways\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-rest-subscriptions-controller.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined static method WC_REST_Orders_Controller\\:\\:create_order\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-rest-subscriptions-controller.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined static method WC_REST_Orders_Controller\\:\\:update_order\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: includes/api/legacy/class-wc-rest-subscriptions-controller.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method WC_Order_Item\\:\\:get_product\\(\\)\\.$#"
|
||||
count: 1
|
||||
|
|
@ -1416,351 +1186,6 @@ parameters:
|
|||
count: 1
|
||||
path: includes/core/gateways/paypal/includes/templates/html-ipn-failure-notice.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Subscription_Legacy\\:\\:\\$product_custom_fields\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Subscription_Legacy\\:\\:\\$product_type\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Subscription_Legacy\\:\\:\\$subscription_limit\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Subscription_Legacy\\:\\:\\$subscription_one_time_shipping\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Subscription_Legacy\\:\\:\\$subscription_payment_sync_date\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Subscription_Variation_Legacy\\:\\:\\$parent_product_type\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-subscription-variation-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Subscription_Variation_Legacy\\:\\:\\$product_type\\.$#"
|
||||
count: 2
|
||||
path: includes/core/legacy/class-wc-product-subscription-variation-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Subscription_Variation_Legacy\\:\\:\\$variation_id\\.$#"
|
||||
count: 2
|
||||
path: includes/core/legacy/class-wc-product-subscription-variation-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Subscription_Variation_Legacy\\:\\:\\$variation_inherited_meta_data\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-subscription-variation-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Subscription_Variation_Legacy\\:\\:\\$variation_level_meta_data\\.$#"
|
||||
count: 3
|
||||
path: includes/core/legacy/class-wc-product-subscription-variation-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Method WC_Product_Subscription_Variation\\:\\:__construct\\(\\) invoked with 2 parameters, 0\\-1 required\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-subscription-variation-legacy.php
|
||||
|
||||
-
|
||||
message: "#^PHPDoc tag @param references unknown parameter\\: \\$key$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-subscription-variation-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property \\$this\\(WC_Product_Variable_Subscription_Legacy\\)\\:\\:\\$max_variation_price\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property \\$this\\(WC_Product_Variable_Subscription_Legacy\\)\\:\\:\\$min_variation_regular_price\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property \\$this\\(WC_Product_Variable_Subscription_Legacy\\)\\:\\:\\$subscription_length\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property \\$this\\(WC_Product_Variable_Subscription_Legacy\\)\\:\\:\\$subscription_sign_up_fee\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property \\$this\\(WC_Product_Variable_Subscription_Legacy\\)\\:\\:\\$subscription_trial_length\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property \\$this\\(WC_Product_Variable_Subscription_Legacy\\)\\:\\:\\$subscription_trial_period\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Variable_Subscription_Legacy\\:\\:\\$max_variation_price\\.$#"
|
||||
count: 2
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Variable_Subscription_Legacy\\:\\:\\$min_variation_price\\.$#"
|
||||
count: 2
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Variable_Subscription_Legacy\\:\\:\\$min_variation_regular_price\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Variable_Subscription_Legacy\\:\\:\\$parent_product_type\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Variable_Subscription_Legacy\\:\\:\\$product_custom_fields\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Variable_Subscription_Legacy\\:\\:\\$subscription_length\\.$#"
|
||||
count: 2
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Variable_Subscription_Legacy\\:\\:\\$subscription_limit\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Variable_Subscription_Legacy\\:\\:\\$subscription_one_time_shipping\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Variable_Subscription_Legacy\\:\\:\\$subscription_payment_sync_date\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Variable_Subscription_Legacy\\:\\:\\$subscription_sign_up_fee\\.$#"
|
||||
count: 2
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Variable_Subscription_Legacy\\:\\:\\$subscription_trial_length\\.$#"
|
||||
count: 2
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Product_Variable_Subscription_Legacy\\:\\:\\$subscription_trial_period\\.$#"
|
||||
count: 2
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method WooCommerce\\:\\:clear_product_transients\\(\\)\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Method WC_Product_Variable_Subscription_Legacy\\:\\:get_price_hash\\(\\) invoked with 2 parameters, 0\\-1 required\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^PHPDoc tag @param has invalid value \\(WC_Product\\)\\: Unexpected token \"\\\\n\\\\t \\* \", expected variable at offset 318$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^PHPDoc tag @param references unknown parameter\\: \\$include_taxes$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-product-variable-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$billing_address_1\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$billing_address_2\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$billing_city\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$billing_company\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$billing_country\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$billing_email\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$billing_first_name\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$billing_last_name\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$billing_phone\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$billing_postcode\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$billing_state\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$customer_note\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$order_key\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$payment_method\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$payment_method_title\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$post\\.$#"
|
||||
count: 13
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$post_status\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$prices_include_tax\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$shipping_address_1\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$shipping_address_2\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$shipping_city\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$shipping_company\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$shipping_country\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$shipping_first_name\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$shipping_last_name\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$shipping_postcode\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property WC_Subscription_Legacy\\:\\:\\$shipping_state\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Default value of the parameter \\#3 \\$meta_id \\(string\\) of method WC_Subscription_Legacy\\:\\:update_meta_data\\(\\) is incompatible with type int\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^Method WC_Subscription_Legacy\\:\\:set_last_order_date\\(\\) should return WC_DateTime\\|null but return statement is missing\\.$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^PHPDoc tag @param has invalid value \\(array\\|int Either an order item \\(in the array format returned by self\\:\\:get_items\\(\\)\\) or the ID of an order item\\.\\)\\: Unexpected token \"Either\", expected variable at offset 399$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^PHPDoc tag @param has invalid value \\(int\\)\\: Unexpected token \"\\\\n\\\\t \", expected variable at offset 61$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^PHPDoc tag @param has invalid value \\(string\\)\\: Unexpected token \"\\\\n\\\\t \\* \", expected variable at offset 555$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^PHPDoc tag @param has invalid value \\(string\\)\\: Unexpected token \"\\\\n\\\\t \\* \", expected variable at offset 573$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^PHPDoc tag @param has invalid value \\(string\\|integer\\|object\\)\\: Unexpected token \"\\\\n\\\\t \\* \", expected variable at offset 203$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wc-subscription-legacy.php
|
||||
|
||||
-
|
||||
message: "#^PHPDoc tag @param references unknown parameter\\: \\$product$#"
|
||||
count: 1
|
||||
path: includes/core/legacy/class-wcs-array-property-post-meta-black-magic.php
|
||||
|
||||
-
|
||||
message: "#^Function as_next_scheduled_action not found\\.$#"
|
||||
count: 3
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@ class Events {
|
|||
*/
|
||||
public function __construct( ?callable $event_recorder = null ) {
|
||||
$this->event_recorder = $event_recorder ?? function ( string $event_name, array $event_properties = array() ) {
|
||||
// The WC_Site_Tracking class is not always available, depending on the WC version and request type (e.g. AJAX).
|
||||
// This check prevents a fatal error if the class is not loaded.
|
||||
if ( ! class_exists( 'WC_Site_Tracking' ) ) {
|
||||
return;
|
||||
}
|
||||
// Note that the following method ensures nothing is sent home unless tracking is enabled.
|
||||
WC_Tracks::record_event( $event_name, $event_properties );
|
||||
};
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue