Compare commits
3 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
56e03c0544 | |
|
|
bfe7fb609d | |
|
|
c7e30d354e |
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
/* global wc_subscription_downloads_product */
|
||||
jQuery( document ).ready( function ( $ ) {
|
||||
function subscriptionDownloadsChosen() {
|
||||
$( 'select.subscription-downloads-ids' ).ajaxChosen({
|
||||
method: 'GET',
|
||||
url: wc_subscription_downloads_product.ajax_url,
|
||||
dataType: 'json',
|
||||
afterTypeDelay: 100,
|
||||
minTermLength: 1,
|
||||
data: {
|
||||
action: 'wc_subscription_downloads_search',
|
||||
security: wc_subscription_downloads_product.security
|
||||
}
|
||||
}, function ( data ) {
|
||||
|
||||
var orders = {};
|
||||
|
||||
$.each( data, function ( i, val ) {
|
||||
orders[i] = val;
|
||||
});
|
||||
|
||||
return orders;
|
||||
});
|
||||
}
|
||||
|
||||
subscriptionDownloadsChosen();
|
||||
|
||||
$( 'body' ).on( 'woocommerce_variations_added', function () {
|
||||
subscriptionDownloadsChosen();
|
||||
});
|
||||
});
|
||||
|
|
@ -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 +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' => 'ac38e67791839651a41a');
|
||||
<?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');
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
<?php return array('dependencies' => array('react', 'react-dom', 'wc-blocks-checkout', 'wp-components', 'wp-data', 'wp-i18n', 'wp-plugins', 'wp-url'), 'version' => 'e7869a0a766c0f65ff8a');
|
||||
<?php return array('dependencies' => array('react', 'react-dom', 'wc-blocks-checkout', 'wp-components', 'wp-data', 'wp-i18n', 'wp-plugins', 'wp-url'), 'version' => 'cf4dd71c0c7418b591fd');
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,5 +1,43 @@
|
|||
*** 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.
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
@ -45,6 +46,7 @@ class WC_Subscriptions_Plugin extends WC_Subscriptions_Core_Plugin {
|
|||
add_action( 'admin_enqueue_scripts', array( $this, 'maybe_show_welcome_message' ) );
|
||||
add_action( 'plugins_loaded', array( $this, 'init_gifting' ) );
|
||||
add_action( 'plugins_loaded', array( $this, 'init_downloads' ) );
|
||||
add_action( 'admin_notices', array( WC_Subscription_Downloads_Settings::class, 'add_notice_about_bundled_feature' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -299,10 +301,16 @@ class WC_Subscriptions_Plugin extends WC_Subscriptions_Core_Plugin {
|
|||
*/
|
||||
public function init_downloads() {
|
||||
if (
|
||||
! defined( 'WCS_ALLOW_SUBSCRIPTION_DOWNLOADS' )
|
||||
|| $this->is_plugin_being_activated( 'woocommerce-subscription-downloads' )
|
||||
$this->is_plugin_being_activated( 'woocommerce-subscription-downloads' )
|
||||
|| class_exists( WC_Subscription_Downloads::class, false )
|
||||
) {
|
||||
if ( class_exists( WC_Subscription_Downloads::class, false ) ) {
|
||||
// Will show the welcome announcement if the standalone plugin is active and the welcome announcement has not been dismissed.
|
||||
if ( ! WC_Subscription_Downloads_Admin_Welcome_Announcement::is_welcome_announcement_dismissed() ) {
|
||||
WC_Subscription_Downloads_Admin_Welcome_Announcement::init();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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>';
|
||||
?>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -428,13 +428,38 @@ class WC_Subscription extends WC_Order {
|
|||
/**
|
||||
* Checks if the subscription contains an unavailable product.
|
||||
*
|
||||
* A product is considered unavailable if it is:
|
||||
* - Deleted (not found)
|
||||
* - Not published (draft, trash, private, etc.)
|
||||
*
|
||||
* Note: This method intentionally does NOT use is_purchasable() to avoid incorrectly
|
||||
* flagging limited products as unavailable. Limited products return is_purchasable() = false
|
||||
* for users with existing subscriptions, but they should still be available for resubscribe.
|
||||
* Functions like wcs_can_user_resubscribe_to() have specific logic to handle limited products
|
||||
* by checking if the user has an active subscription.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function contains_unavailable_product() {
|
||||
/** @var WC_Order_Item_Product $line_item */
|
||||
foreach ( $this->get_items() as $line_item ) {
|
||||
$product = $line_item->get_product();
|
||||
if ( ! $product instanceof WC_Product || ! $product->is_purchasable() ) {
|
||||
|
||||
// Product doesn't exist (deleted).
|
||||
if ( ! $product instanceof WC_Product ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the product is a subscription variation, use the parent product.
|
||||
if ( $product->is_type( 'subscription_variation' ) ) {
|
||||
$parent_product_id = $product->get_parent_id();
|
||||
$product = wc_get_product( $parent_product_id );
|
||||
}
|
||||
|
||||
// Check if product is published. Products with other statuses (draft, trash, private)
|
||||
// are not available for purchase or resubscribe.
|
||||
$product_status = $product->get_status();
|
||||
if ( 'publish' !== $product_status ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,43 @@ class WCS_Limiter {
|
|||
do_action( 'woocommerce_subscriptions_product_options_advanced' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the session contains a renewal for a given product.
|
||||
* Used for the pay for order flow.
|
||||
*
|
||||
* @param WC_Product $product The product to check.
|
||||
* @return bool
|
||||
*/
|
||||
private static function session_contains_renewal( $product ) {
|
||||
if ( ! empty( WC()->session->cart ) ) {
|
||||
foreach ( WC()->session->cart as $cart_item_key => $cart_item ) {
|
||||
if ( (int) $product->get_id() === (int) $cart_item['product_id'] && isset( $cart_item['subscription_renewal'] ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the session contains a resubscribe for a given product.
|
||||
* Used for the pay for order flow with limited subscriptions products.
|
||||
*
|
||||
* @param WC_Product $product The product to check.
|
||||
* @return bool
|
||||
*/
|
||||
private static function session_contains_resubscribe( $product ) {
|
||||
if ( ! empty( WC()->session->cart ) ) {
|
||||
foreach ( WC()->session->cart as $cart_item_key => $cart_item ) {
|
||||
if ( (int) $product->get_id() === (int) $cart_item['product_id'] && isset( $cart_item['subscription_resubscribe'] ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Canonical is_purchasable method to be called by product classes.
|
||||
*
|
||||
|
|
@ -68,73 +105,62 @@ class WCS_Limiter {
|
|||
*/
|
||||
public static function is_purchasable( $purchasable, $product ) {
|
||||
|
||||
// Prevents making a non purchasable product purchasable again.
|
||||
// This can happen if the product is disabled and limited and the customer is trying to renew the subscription for example.
|
||||
if ( ! $purchasable ) {
|
||||
return $purchasable;
|
||||
// Check if product is private (for variations, also check parent product)
|
||||
$is_private_product = 'private' === $product->get_status();
|
||||
if ( $product->get_parent_id() > 0 ) {
|
||||
$parent_product = wc_get_product( $product->get_parent_id() );
|
||||
if ( $parent_product ) {
|
||||
$is_private_product = 'private' === $parent_product->get_status();
|
||||
}
|
||||
}
|
||||
|
||||
switch ( $product->get_type() ) {
|
||||
case 'subscription':
|
||||
case 'variable-subscription':
|
||||
// Checks if the product is limited.
|
||||
if ( false === self::is_product_limited( $purchasable, $product ) ) {
|
||||
// Product is limited, so it is not purchasable.
|
||||
// Checks limits for variable subscription products.
|
||||
if ( $product->get_type() === 'subscription_variation' && isset( $parent_product ) ) {
|
||||
|
||||
if ( $is_private_product ) {
|
||||
$purchasable = false;
|
||||
|
||||
// Forces product to be available when processing a renewal order. Allowing people to renew private products.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce validation is not required for this context.
|
||||
if ( self::is_paying_for_failed_renewal_order( $parent_product ) || isset( $_GET['subscription_renewal'] ) || wcs_cart_contains_renewal() || self::session_contains_renewal( $parent_product ) ) {
|
||||
$purchasable = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'no' !== wcs_get_product_limitation( $parent_product ) && ( ! empty( WC()->cart->cart_contents ) || self::session_contains_resubscribe( $parent_product ) ) && ! wcs_is_order_received_page() && ! wcs_is_paypal_api_page() ) {
|
||||
// When mixed checkout is disabled, the variation is replaceable.
|
||||
if ( 'yes' === get_option( WC_Subscriptions_Admin::$option_prefix . '_multiple_purchase', 'no' ) ) {
|
||||
foreach ( WC()->cart->cart_contents as $cart_item ) {
|
||||
// If the variable product is limited, it can't be purchased if it is the same variation
|
||||
if ( $product->get_parent_id() === $cart_item['data']->get_parent_id() && $product->get_id() !== $cart_item['data']->get_id() ) {
|
||||
$purchasable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // Checks limits for simple subscription products.
|
||||
if ( $is_private_product ) {
|
||||
$purchasable = false;
|
||||
|
||||
// Forces product to be available when processing a renewal order. Allowing people to renew private products.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce validation is not required for this context.
|
||||
if ( self::is_paying_for_failed_renewal_order( $product ) || isset( $_GET['subscription_renewal'] ) || wcs_cart_contains_renewal() || self::session_contains_renewal( $product ) ) {
|
||||
$purchasable = true;
|
||||
}
|
||||
}
|
||||
|
||||
// This actually means the product is limited when returning false.
|
||||
if ( false === self::is_product_limited( $purchasable, $product ) ) {
|
||||
$resubscribe_cart_item = wcs_cart_contains_resubscribe();
|
||||
// Allows the product to be resubscribed but not purchased again.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce validation is not required for this context.
|
||||
if ( empty( $_GET['resubscribe'] ) && false === $resubscribe_cart_item && false === self::session_contains_resubscribe( $product ) ) {
|
||||
$purchasable = false;
|
||||
|
||||
// Unless it's resubscribing, renewing or restoring cart from session.
|
||||
$resubscribe_cart_item = wcs_cart_contains_resubscribe();
|
||||
|
||||
// Resubscribe logic
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! empty( $_GET['resubscribe'] ) || false !== $resubscribe_cart_item ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$subscription_id = ( isset( $_GET['resubscribe'] ) ) ? absint( $_GET['resubscribe'] ) : $resubscribe_cart_item['subscription_resubscribe']['subscription_id'];
|
||||
$subscription = wcs_get_subscription( $subscription_id );
|
||||
|
||||
if ( $subscription && $subscription->has_product( $product->get_id() ) && wcs_can_user_resubscribe_to( $subscription ) ) {
|
||||
$purchasable = true;
|
||||
}
|
||||
|
||||
// Renewal logic
|
||||
} elseif (
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
isset( $_GET['subscription_renewal'] ) ||
|
||||
wcs_cart_contains_renewal()
|
||||
) {
|
||||
$purchasable = true;
|
||||
|
||||
// Restoring cart from session, so need to check the cart in the session (wcs_cart_contains_renewal() only checks the cart).
|
||||
} elseif ( ! empty( WC()->session->cart ) ) {
|
||||
foreach ( WC()->session->cart as $cart_item_key => $cart_item ) {
|
||||
if ( (int) $product->get_id() === (int) $cart_item['product_id'] && ( isset( $cart_item['subscription_renewal'] ) || isset( $cart_item['subscription_resubscribe'] ) ) ) {
|
||||
$purchasable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'subscription_variation':
|
||||
$variable_product = wc_get_product( $product->get_parent_id() );
|
||||
|
||||
if ( 'no' != wcs_get_product_limitation( $variable_product ) && ! empty( WC()->cart->cart_contents ) && ! wcs_is_order_received_page() && ! wcs_is_paypal_api_page() ) {
|
||||
|
||||
// When mixed checkout is disabled, the variation is replaceable
|
||||
if ( 'no' === get_option( WC_Subscriptions_Admin::$option_prefix . '_multiple_purchase', 'no' ) ) {
|
||||
$purchasable = true;
|
||||
} else { // When mixed checkout is enabled
|
||||
foreach ( WC()->cart->cart_contents as $cart_item ) {
|
||||
// If the variable product is limited, it can't be purchased if it is the same variation
|
||||
if ( $product->get_parent_id() === $cart_item['data']->get_parent_id() && $product->get_id() !== $cart_item['data']->get_id() ) {
|
||||
$purchasable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $purchasable;
|
||||
}
|
||||
|
||||
|
|
@ -302,12 +328,73 @@ class WCS_Limiter {
|
|||
return self::$order_awaiting_payment_for_product[ $product_id ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we're currently paying for a failed renewal order containing the product.
|
||||
*
|
||||
* @since 8.3.0 - Migrated from WooCommerce Subscriptions v2.1.0
|
||||
* @param WC_Product $product The product to check.
|
||||
* @return bool
|
||||
*/
|
||||
protected static function is_paying_for_failed_renewal_order( $product ) {
|
||||
global $wp;
|
||||
|
||||
// Check if we're on the pay for order page
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! isset( $_GET['pay_for_order'] ) || ! isset( $_GET['key'] ) || ! isset( $wp->query_vars['order-pay'] ) ) {
|
||||
// Also check if cart contains a failed renewal order payment
|
||||
$failed_renewal_cart_item = wcs_cart_contains_failed_renewal_order_payment();
|
||||
if ( false !== $failed_renewal_cart_item ) {
|
||||
$cart_item_product_id = isset( $failed_renewal_cart_item['variation_id'] ) && $failed_renewal_cart_item['variation_id'] > 0
|
||||
? $failed_renewal_cart_item['variation_id']
|
||||
: $failed_renewal_cart_item['product_id'];
|
||||
// Check both the product ID and parent product ID (for variations)
|
||||
if ( (int) $product->get_id() === (int) $cart_item_product_id || (int) $product->get_id() === (int) $failed_renewal_cart_item['product_id'] ) {
|
||||
return true;
|
||||
}
|
||||
// Also check if product is a variation and matches the parent
|
||||
if ( $product->get_parent_id() > 0 && (int) $product->get_parent_id() === (int) $failed_renewal_cart_item['product_id'] ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$order_key = isset( $_GET['key'] ) ? wc_clean( wp_unslash( $_GET['key'] ) ) : '';
|
||||
$order_id = isset( $wp->query_vars['order-pay'] ) ? $wp->query_vars['order-pay'] : 0;
|
||||
$order = wc_get_order( absint( $order_id ) );
|
||||
|
||||
if ( ! $order || $order->get_order_key() !== $order_key ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if order is a failed renewal order
|
||||
if ( ! $order->has_status( 'failed' ) && ! wcs_order_contains_renewal( $order ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the order contains the product
|
||||
foreach ( $order->get_items() as $item ) {
|
||||
$item_product_id = isset( $item['variation_id'] ) && $item['variation_id'] > 0 ? $item['variation_id'] : $item['product_id'];
|
||||
// Check both the product ID and parent product ID (for variations)
|
||||
if ( (int) $product->get_id() === (int) $item_product_id || (int) $product->get_id() === (int) $item['product_id'] ) {
|
||||
return true;
|
||||
}
|
||||
// Also check if product is a variation and matches the parent
|
||||
if ( $product->get_parent_id() > 0 && (int) $product->get_parent_id() === (int) $item['product_id'] ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the order statuses that enable the order again button and functionality.
|
||||
*
|
||||
* This function will return no statuses if the order contains non purchasable or limited products.
|
||||
*
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v3.0.2
|
||||
* @since 8.3.0 - Migrated from WooCommerce Subscriptions v3.0.2
|
||||
*
|
||||
* @param array $statuses The order statuses that enable the order again button.
|
||||
* @return array $statuses An empty array if the order contains limited products, otherwise the default statuses are returned.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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' ) );
|
||||
|
|
|
|||
|
|
@ -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' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -227,9 +227,8 @@ class WC_Subscriptions_Upgrader {
|
|||
WCS_Plugin_Upgrade_7_8_0::check_gifting_plugin_is_enabled();
|
||||
}
|
||||
|
||||
if ( false && version_compare( self::$stored_plugin_version, '8.2.0', '<' ) ) {
|
||||
// TODO: remove false from the above conditional, once we are ready to make subscription downloads functionality available.
|
||||
WCS_Plugin_Upgrade_8_1_0::check_downloads_plugin_is_enabled();
|
||||
if ( version_compare( self::$stored_plugin_version, '8.3.0', '<' ) ) {
|
||||
WCS_Plugin_Upgrade_8_3_0::check_downloads_plugin_is_enabled();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class WCS_Plugin_Upgrade_8_1_0 {
|
||||
class WCS_Plugin_Upgrade_8_3_0 {
|
||||
|
||||
/**
|
||||
* Check if the Gifting plugin is enabled and update the settings.
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ class WC_Subscription_Downloads_Ajax {
|
|||
*/
|
||||
public function __construct() {
|
||||
add_action( 'wp_ajax_wc_subscription_downloads_search', array( $this, 'search_subscriptions' ) );
|
||||
add_action( 'wp_ajax_wc_subscription_linked_downloadable_products_search', array( $this, 'search_downloadable_products' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -72,4 +73,39 @@ class WC_Subscription_Downloads_Ajax {
|
|||
|
||||
wp_send_json( $found_subscriptions );
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for downloadable products that are simple or variants.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function search_downloadable_products(): void {
|
||||
$results = array();
|
||||
|
||||
// Prevent error noise from leaking.
|
||||
ob_start();
|
||||
|
||||
if ( isset( $_GET['term'] ) && check_ajax_referer( 'search-products', 'security' ) ) {
|
||||
$term = wc_clean( wp_unslash( $_GET['term'] ) );
|
||||
}
|
||||
|
||||
if ( ! empty( $term ) ) {
|
||||
$products = wc_get_products(
|
||||
array(
|
||||
'downloadable' => true,
|
||||
'limit' => 100,
|
||||
's' => $term,
|
||||
'type' => array( 'simple', 'variation' ),
|
||||
'status' => 'any',
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $products as $product ) {
|
||||
$results[ $product->get_id() ] = sanitize_text_field( $product->get_formatted_name() );
|
||||
}
|
||||
}
|
||||
|
||||
ob_clean();
|
||||
wp_send_json( $results );
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,52 +7,202 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
* WooCommerce Subscription Downloads Products.
|
||||
*
|
||||
* @package WC_Subscription_Downloads_Products
|
||||
* @category Products
|
||||
* @author WooThemes
|
||||
*/
|
||||
class WC_Subscription_Downloads_Products {
|
||||
public const EDITOR_UPDATE = 'wcsubs_subscription_download_relationships';
|
||||
public const RELATIONSHIP_DOWNLOAD_TO_SUB = 'download-to-sub';
|
||||
public const RELATIONSHIP_VAR_DOWNLOAD_TO_SUB = 'var-download-to-sub';
|
||||
public const RELATIONSHIP_SUB_TO_DOWNLOAD = 'sub-to-download';
|
||||
public const RELATIONSHIP_VAR_SUB_TO_DOWNLOAD = 'var-sub-to-download';
|
||||
|
||||
/**
|
||||
* Products actions.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'woocommerce_product_options_downloads', array( $this, 'simple_write_panel_options' ), 10 );
|
||||
add_action( 'woocommerce_product_options_downloads', array( $this, 'simple_write_panel_options' ) );
|
||||
add_action( 'woocommerce_variation_options_download', array( $this, 'variable_write_panel_options' ), 10, 3 );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'scripts' ) );
|
||||
add_action( 'woocommerce_process_product_meta_simple', array( $this, 'save_simple_product_data' ), 10 );
|
||||
add_action( 'woocommerce_save_product_variation', array( $this, 'save_variation_product_data' ), 10, 2 );
|
||||
add_action( 'woocommerce_product_options_pricing', array( $this, 'subscription_product_editor_ui' ) );
|
||||
add_action( 'woocommerce_variable_subscription_pricing', array( $this, 'variable_subscription_product_editor_ui' ), 10, 3 );
|
||||
|
||||
add_action( 'save_post_product', array( $this, 'handle_product_save' ) );
|
||||
add_action( 'save_post_product_variation', array( $this, 'handle_product_variation_save' ) );
|
||||
add_action( 'woocommerce_save_product_variation', array( $this, 'handle_product_variation_save' ) );
|
||||
add_action( 'woocommerce_update_product', array( $this, 'handle_product_save' ) );
|
||||
add_action( 'woocommerce_update_product_variation', array( $this, 'handle_product_variation_save' ) );
|
||||
|
||||
add_action( 'init', array( $this, 'init' ) );
|
||||
}
|
||||
|
||||
public function init() {
|
||||
if ( version_compare( WC_VERSION, '3.0', '<' ) ) {
|
||||
add_action( 'woocommerce_duplicate_product', array( $this, 'save_subscriptions_when_duplicating_product' ), 10, 2 );
|
||||
} else {
|
||||
add_action( 'woocommerce_product_duplicate', array( $this, 'save_subscriptions_when_duplicating_product' ), 10, 2 );
|
||||
add_action( 'woocommerce_product_duplicate', array( $this, 'save_subscriptions_when_duplicating_product' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle product save - generic handler for all product updates.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_product_save( $post_id ) {
|
||||
// Bail if this is an autosave or revision.
|
||||
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( wp_is_post_revision( $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$product = wc_get_product( $post_id );
|
||||
|
||||
if ( ! $product ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$product_is_downloadable = $product->is_downloadable();
|
||||
$product_is_subscription = $product->is_type( array( 'subscription', 'variable-subscription' ) );
|
||||
|
||||
// We do not allow downloadable subscription products to be linked with other subscription products; this is
|
||||
// principally to avoid confusion (though it would be technically feasible).
|
||||
if ( $product_is_subscription && ! $product_is_downloadable ) {
|
||||
$this->handle_subscription_product_save( $post_id );
|
||||
} elseif ( ! $product_is_subscription && $product_is_downloadable ) {
|
||||
$this->handle_downloadable_product_save( $post_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Product screen scripts.
|
||||
* Handle product variation save - generic handler for all variation updates.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function scripts() {
|
||||
$screen = get_current_screen();
|
||||
public function handle_product_variation_save( $post_id ) {
|
||||
// Bail if this is an autosave or revision.
|
||||
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'product' == $screen->id && version_compare( WC_VERSION, '2.3.0', '<' ) ) {
|
||||
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
|
||||
if ( wp_is_post_revision( $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_script( 'wc_subscription_downloads_writepanel', plugins_url( 'assets/js/admin/writepanel' . $suffix . '.js', plugin_dir_path( __FILE__ ) ), array( 'ajax-chosen', 'chosen' ), WC_Subscriptions::$version, true );
|
||||
$variation = wc_get_product( $post_id );
|
||||
if ( ! $variation || ! $variation->is_type( 'variation' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_localize_script(
|
||||
'wc_subscription_downloads_writepanel',
|
||||
'wc_subscription_downloads_product',
|
||||
array(
|
||||
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
||||
'security' => wp_create_nonce( 'search-products' ),
|
||||
)
|
||||
);
|
||||
// Handle downloadable variations (they link TO subscriptions).
|
||||
if ( $variation->is_downloadable() ) {
|
||||
$this->handle_downloadable_product_save( $post_id );
|
||||
}
|
||||
|
||||
// Handle subscription variations (they link TO downloadable products).
|
||||
$parent = wc_get_product( $variation->get_parent_id() );
|
||||
|
||||
if ( $parent && $parent->is_type( 'variable-subscription' ) ) {
|
||||
$this->handle_subscription_product_save( $post_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle save for downloadable products (simple or variation).
|
||||
* These products link TO subscription products.
|
||||
*
|
||||
* @param int $product_id Product or variation ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function handle_downloadable_product_save( $product_id ) {
|
||||
$product = wc_get_product( $product_id );
|
||||
|
||||
if ( ! $product ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
if (
|
||||
isset( $_POST[ self::RELATIONSHIP_VAR_DOWNLOAD_TO_SUB . $product_id ] )
|
||||
&& wp_verify_nonce( $_POST[ self::RELATIONSHIP_VAR_DOWNLOAD_TO_SUB . $product_id ], self::EDITOR_UPDATE )
|
||||
) {
|
||||
$subscription_ids = wc_clean( wp_unslash( $_POST['_variable_subscription_downloads_ids'][ $product_id ] ?? array() ) );
|
||||
$subscription_ids = array_filter( (array) $subscription_ids );
|
||||
$this->update_subscription_downloads( $product_id, $subscription_ids );
|
||||
}
|
||||
|
||||
if (
|
||||
isset( $_POST[ self::RELATIONSHIP_DOWNLOAD_TO_SUB ] )
|
||||
&& wp_verify_nonce( $_POST[ self::RELATIONSHIP_DOWNLOAD_TO_SUB ], self::EDITOR_UPDATE )
|
||||
) {
|
||||
$subscription_ids = wc_clean( wp_unslash( $_POST['_subscription_downloads_ids'] ?? array() ) );
|
||||
$subscription_ids = array_filter( (array) $subscription_ids );
|
||||
$this->update_subscription_downloads( $product_id, $subscription_ids );
|
||||
}
|
||||
|
||||
// Observe and act on product status changes (regardless of whether they were made from within the product
|
||||
// editor, therefore we don't care about nonce checks here).
|
||||
$this->assess_downloadable_product_status( $product_id );
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle save for subscription products (simple subscription or variation).
|
||||
* These products link TO downloadable products.
|
||||
*
|
||||
* @param int $product_id Subscription product or variation ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function handle_subscription_product_save( $product_id ) {
|
||||
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
if (
|
||||
isset( $_POST[ self::RELATIONSHIP_VAR_SUB_TO_DOWNLOAD . $product_id ] )
|
||||
&& wp_verify_nonce( $_POST[ self::RELATIONSHIP_VAR_SUB_TO_DOWNLOAD . $product_id ], self::EDITOR_UPDATE )
|
||||
) {
|
||||
$product_ids = wc_clean( wp_unslash( $_POST[ '_subscription_linked_downloadable_products_' . $product_id ] ?? array() ) );
|
||||
$product_ids = array_filter( (array) $product_ids );
|
||||
$this->update_subscription_products( $product_id, $product_ids );
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
isset( $_POST[ self::RELATIONSHIP_SUB_TO_DOWNLOAD ] )
|
||||
&& wp_verify_nonce( $_POST[ self::RELATIONSHIP_SUB_TO_DOWNLOAD ], self::EDITOR_UPDATE )
|
||||
) {
|
||||
$product_ids = wc_clean( wp_unslash( (array) $_POST['_subscription_linked_downloadable_products'] ?? array() ) );
|
||||
$product_ids = array_filter( (array) $product_ids );
|
||||
$this->update_subscription_products( $product_id, $product_ids );
|
||||
}
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* Assess downloadable product status and adjust permissions accordingly.
|
||||
* Called when no form data is available (e.g., status change, REST API update, file changes).
|
||||
*
|
||||
* @param int $product_id Product ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function assess_downloadable_product_status( $product_id ) {
|
||||
$product = wc_get_product( $product_id );
|
||||
|
||||
if ( ! $product || ! $product->is_downloadable() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$status_object = get_post_status_object( $product->get_status() );
|
||||
$is_public = $status_object && $status_object->public;
|
||||
|
||||
// Always revoke existing permissions first to ensure clean state.
|
||||
// This handles file changes and status transitions.
|
||||
$this->revoke_permissions_for_product( $product_id );
|
||||
|
||||
// Grant fresh permissions only if product is public.
|
||||
if ( $is_public ) {
|
||||
$this->grant_permissions_for_product( $product_id );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -62,28 +212,27 @@ class WC_Subscription_Downloads_Products {
|
|||
public function simple_write_panel_options() {
|
||||
global $post;
|
||||
?>
|
||||
<p class="form-field _subscription_downloads_field">
|
||||
<label for="subscription-downloads-ids"><?php esc_html_e( 'Included subscription products', 'woocommerce-subscriptions' ); ?></label>
|
||||
<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 ( empty( $subscriptions_ids ) ) {
|
||||
$subscriptions_ids = get_post_meta( $post->ID, '_subscription_downloads_ids', true );
|
||||
}
|
||||
|
||||
if ( $subscriptions_ids ) {
|
||||
foreach ( $subscriptions_ids as $subscription_id ) {
|
||||
$_subscription = wc_get_product( $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>';
|
||||
if ( $subscription ) {
|
||||
echo '<option value="' . esc_attr( $subscription_id ) . '" selected="selected">' . esc_html( wp_strip_all_tags( $subscription->get_formatted_name() ) ) . '</option>';
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
|
||||
<?php echo wc_help_tip( wc_sanitize_tooltip( __( 'Select subscription products that will include this downloadable product.', 'woocommerce-subscriptions' ) ) ); ?>
|
||||
<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
|
||||
|
|
@ -96,30 +245,145 @@ class WC_Subscription_Downloads_Products {
|
|||
?>
|
||||
<tr class="show_if_variation_downloadable">
|
||||
<td colspan="2">
|
||||
<p class="form-field form-row form-row-full">
|
||||
<label><?php esc_html_e( 'Included subscription products', 'woocommerce-subscriptions' ); ?>:</label>
|
||||
<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( $loop ); ?>][]" style="width: 100%">
|
||||
<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 );
|
||||
$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>';
|
||||
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.
|
||||
*
|
||||
|
|
@ -166,9 +430,7 @@ class WC_Subscription_Downloads_Products {
|
|||
$orders[] = $order->id;
|
||||
}
|
||||
|
||||
$orders = apply_filters( 'woocommerce_subscription_downloads_get_orders', $orders, $subscription_product_id );
|
||||
|
||||
return $orders;
|
||||
return apply_filters( 'woocommerce_subscription_downloads_get_orders', $orders, $subscription_product_id );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -183,40 +445,93 @@ class WC_Subscription_Downloads_Products {
|
|||
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 ) );
|
||||
$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.
|
||||
* 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
|
||||
* @param array $subscriptions
|
||||
* @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;
|
||||
|
||||
if ( version_compare( WC_VERSION, '3.0', '<' ) && ! empty( $subscriptions ) ) {
|
||||
$subscriptions = explode( ',', $subscriptions );
|
||||
}
|
||||
foreach ( $product_ids as $product_id ) {
|
||||
$product_id = (int) $product_id;
|
||||
|
||||
$current = WC_Subscription_Downloads::get_subscriptions( $product_id );
|
||||
foreach ( $subscription_ids as $subscription_id ) {
|
||||
$subscription_id = (int) $subscription_id;
|
||||
|
||||
// Delete items.
|
||||
$delete_ids = array_diff( $current, $subscriptions );
|
||||
if ( $delete_ids ) {
|
||||
foreach ( $delete_ids as $delete ) {
|
||||
$wpdb->delete(
|
||||
$wpdb->prefix . 'woocommerce_subscription_downloads',
|
||||
array(
|
||||
'product_id' => $product_id,
|
||||
'subscription_id' => $delete,
|
||||
'subscription_id' => $subscription_id,
|
||||
),
|
||||
array(
|
||||
'%d',
|
||||
|
|
@ -224,10 +539,10 @@ class WC_Subscription_Downloads_Products {
|
|||
)
|
||||
);
|
||||
|
||||
$_orders = $this->get_orders( $delete );
|
||||
foreach ( $_orders as $order_id ) {
|
||||
$_product = wc_get_product( $product_id );
|
||||
$downloads = version_compare( WC_VERSION, '3.0', '<' ) ? $_product->get_files() : $_product->get_downloads();
|
||||
$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 ) {
|
||||
|
|
@ -236,16 +551,35 @@ class WC_Subscription_Downloads_Products {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add items.
|
||||
$add_ids = array_diff( $subscriptions, $current );
|
||||
if ( $add_ids ) {
|
||||
foreach ( $add_ids as $add ) {
|
||||
$wpdb->insert(
|
||||
$wpdb->prefix . 'woocommerce_subscription_downloads',
|
||||
/**
|
||||
* 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(
|
||||
'product_id' => $product_id,
|
||||
'subscription_id' => $add,
|
||||
'order_id' => $order_id,
|
||||
'product_id' => $product_id,
|
||||
),
|
||||
array(
|
||||
'%d',
|
||||
|
|
@ -253,23 +587,105 @@ class WC_Subscription_Downloads_Products {
|
|||
)
|
||||
);
|
||||
|
||||
$_orders = $this->get_orders( $add );
|
||||
foreach ( $_orders as $order_id ) {
|
||||
$order = wc_get_order( $order_id );
|
||||
do_action( 'woocommerce_revoke_access_to_product_download', $product_id, $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;
|
||||
}
|
||||
/**
|
||||
* 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 );
|
||||
|
||||
$_product = wc_get_product( $product_id );
|
||||
$downloads = version_compare( WC_VERSION, '3.0', '<' ) ? $_product->get_files() : $_product->get_downloads();
|
||||
if ( empty( $subscription_product_ids ) || ! $product ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Adds the downloadable files to the order/subscription.
|
||||
foreach ( array_keys( $downloads ) as $download_id ) {
|
||||
wc_downloadable_file_permission( $download_id, $product_id, $order );
|
||||
$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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -284,41 +700,14 @@ class WC_Subscription_Downloads_Products {
|
|||
* @return void
|
||||
*/
|
||||
public function save_simple_product_data( $product_id ) {
|
||||
$subscription_ids = ! empty( $_POST['_subscription_downloads_ids'] ) ? wc_clean( wp_unslash( $_POST['_subscription_downloads_ids'] ) ) : '';
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$subscription_downloads_ids = ! empty( $_POST['_subscription_downloads_ids'] ) ? wc_clean( wp_unslash( $_POST['_subscription_downloads_ids'] ) ) : '';
|
||||
|
||||
if ( ! isset( $_POST['_downloadable'] ) || 'publish' !== get_post_status( $product_id ) ) {
|
||||
update_post_meta( $product_id, '_subscription_downloads_ids', $subscription_ids );
|
||||
return;
|
||||
if ( empty( $subscription_downloads_ids ) ) {
|
||||
$subscription_downloads_ids = array();
|
||||
}
|
||||
|
||||
delete_post_meta( $product_id, '_subscription_downloads_ids', $subscription_ids );
|
||||
$subscriptions = $subscription_ids ?: array();
|
||||
|
||||
$this->update_subscription_downloads( $product_id, $subscriptions );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save variable product data.
|
||||
*
|
||||
* @param int $variation_id
|
||||
* @param int $index
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save_variation_product_data( $variation_id, $index ) {
|
||||
if ( ! isset( $_POST['variable_is_downloadable'][ $index ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subscriptions = isset( $_POST['_variable_subscription_downloads_ids'][ $index ] ) ? wc_clean( wp_unslash( $_POST['_variable_subscription_downloads_ids'][ $index ] ) ) : array();
|
||||
|
||||
if ( version_compare( WC_VERSION, '3.0.0', '<' ) ) {
|
||||
$subscriptions = explode( ',', $subscriptions );
|
||||
}
|
||||
|
||||
$subscriptions = array_filter( $subscriptions ); // nosemgrep: audit.php.lang.misc.array-filter-no-callback -- $subscriptions are already passed through wc_clean() and wp_unslash().
|
||||
|
||||
$this->update_subscription_downloads( $variation_id, $subscriptions );
|
||||
$this->update_subscription_downloads( $product_id, $subscription_downloads_ids );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -382,4 +771,45 @@ class WC_Subscription_Downloads_Products {
|
|||
|
||||
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' );
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
|
||||
/**
|
||||
* Registers and manages settings related to linked downloadable files functionality.
|
||||
*
|
||||
|
|
@ -10,6 +12,39 @@ class WC_Subscription_Downloads_Settings {
|
|||
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.
|
||||
*
|
||||
|
|
@ -25,8 +60,8 @@ class WC_Subscription_Downloads_Settings {
|
|||
'id' => WC_Subscriptions_Admin::$option_prefix . '_downloads_settings',
|
||||
),
|
||||
array(
|
||||
'name' => __( 'Enable product linking to subscriptions', 'woocommerce-subscriptions' ),
|
||||
'desc' => __( 'Allow simple and variable downloadable products to be included with subscription products.', 'woocommerce-subscriptions' ),
|
||||
'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',
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class WC_Subscription_Downloads {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get subscriptions from a downloadable product.
|
||||
* Given the ID of a downloadable product, returns an array of linked subscription product IDs.
|
||||
*
|
||||
* @param int $product_id
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
@ -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();
|
||||
|
|
@ -1356,21 +1416,25 @@ 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'] ) );
|
||||
$subscription = wcs_get_subscription( absint( $switch_subscription_id ) );
|
||||
|
||||
if ( ! $subscription ) {
|
||||
throw new Exception( __( 'The subscription may have been deleted.', 'woocommerce-subscriptions' ) );
|
||||
}
|
||||
|
||||
$item_id = absint( $_GET['item'] );
|
||||
$item_id = absint( Request::get_var( 'item' ) );
|
||||
$item = wcs_get_order_item( $item_id, $subscription );
|
||||
|
||||
// Prevent switching to non-subscription product
|
||||
|
|
@ -1442,11 +1506,13 @@ 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' ) );
|
||||
|
|
@ -1456,11 +1522,11 @@ class WC_Subscriptions_Switcher {
|
|||
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'] );
|
||||
|
|
@ -1469,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 ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1486,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' => '',
|
||||
);
|
||||
|
|
@ -1497,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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 );
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce_Subscriptions\Internal\Utilities;
|
||||
|
||||
/**
|
||||
* Utilities for handling request-related operations, including test-safe redirects and exits.
|
||||
*
|
||||
* @internal This class may be modified, moved or removed in future releases.
|
||||
*/
|
||||
class Request {
|
||||
/**
|
||||
* Cached POST input variables.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private static ?array $post_vars = null;
|
||||
|
||||
/**
|
||||
* Cached GET input variables.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
private static ?array $url_vars = null;
|
||||
|
||||
/**
|
||||
* Exit unless running in test environment.
|
||||
*
|
||||
* This method allows tests to run without terminating the PHP process,
|
||||
* enabling proper code coverage collection and test assertions.
|
||||
* In production, this behaves exactly like exit().
|
||||
*
|
||||
* @since 8.3.0
|
||||
* @return void
|
||||
*/
|
||||
public static function exit() {
|
||||
if ( defined( 'WCS_ENVIRONMENT_TYPE' ) && 'tests' === WCS_ENVIRONMENT_TYPE ) {
|
||||
return;
|
||||
}
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe redirect that works in test environment.
|
||||
*
|
||||
* In production, this performs a normal wp_safe_redirect() and exits by default.
|
||||
* In tests, it stores the redirect information in a global variable for assertions
|
||||
* instead of attempting to send headers (which would fail).
|
||||
*
|
||||
* @since 8.3.0
|
||||
* @param string $location The URL to redirect to.
|
||||
* @param int $status HTTP status code (default 302).
|
||||
* @param bool $should_exit Whether to exit after setting redirect headers (default true).
|
||||
* @return void
|
||||
*/
|
||||
public static function redirect( $location, $status = 302, $should_exit = true ) {
|
||||
if ( defined( 'WCS_ENVIRONMENT_TYPE' ) && 'tests' === WCS_ENVIRONMENT_TYPE ) {
|
||||
// Store redirect info for test assertions but don't actually redirect
|
||||
$GLOBALS['wcs_test_redirect'] = array(
|
||||
'location' => $location,
|
||||
'status' => $status,
|
||||
);
|
||||
return;
|
||||
}
|
||||
wp_safe_redirect( $location, $status );
|
||||
|
||||
if ( $should_exit ) {
|
||||
self::exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Supplies the value of the POST or URL query parameter matching $key, or else returns $default.
|
||||
*
|
||||
* Essentially, this is an alternative to inspecting the $_REQUEST super-global and is intended for cases where we
|
||||
* are interested in a key:value pair, regardless of whether it was sent as a post var or URL query var.
|
||||
*
|
||||
* Its advantages are that it only ever examines the POST and GET inputs (POST taking priority, if both contain the
|
||||
* same key): cookies are always ignored. It also looks directly at the inputs, instead of using the $_POST or $_GET
|
||||
* superglobals (which can be manipulated).
|
||||
*
|
||||
* @since 8.3.0
|
||||
*
|
||||
* @param string $key The key to look up.
|
||||
* @param mixed $default_value The value to return if the key is not found. Defaults to null.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get_var( string $key, $default_value = null ) {
|
||||
if ( null === self::$post_vars ) {
|
||||
self::$post_vars = filter_input_array( INPUT_POST ) ?? array();
|
||||
self::$url_vars = filter_input_array( INPUT_GET ) ?? array();
|
||||
}
|
||||
|
||||
if ( isset( self::$post_vars[ $key ] ) ) {
|
||||
return self::$post_vars[ $key ];
|
||||
} elseif ( isset( self::$url_vars[ $key ] ) ) {
|
||||
return self::$url_vars[ $key ];
|
||||
}
|
||||
|
||||
return $default_value;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
/**
|
||||
* Outputs a subscription variation's pricing fields for WooCommerce 2.3+
|
||||
*
|
||||
* You may also be interested in assets/js/admin/admin.js, which dynamically relocates some of these fields.
|
||||
*
|
||||
* @version 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.12
|
||||
*
|
||||
* @var int $loop
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@
|
|||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
$display_heading = true;
|
||||
|
||||
foreach ( WC()->cart->get_coupons() as $code => $coupon ) {
|
||||
$display_heading = true;
|
||||
foreach ( $recurring_carts as $recurring_cart_key => $recurring_cart ) {
|
||||
foreach ( $recurring_cart->get_coupons() as $recurring_code => $recurring_coupon ) {
|
||||
if ( $recurring_code !== $code ) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
* Shows the info of a particular subscription without pricing for the recipient on the account page
|
||||
*
|
||||
* @package WooCommerce Subscriptions Gifting/Templates
|
||||
* @version 2.0.0
|
||||
* @version 8.2.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
|
|
@ -32,13 +32,24 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
foreach ( $subscription_items as $item_id => $item ) {
|
||||
$_product = apply_filters( 'woocommerce_subscriptions_order_item_product', $item->get_product(), $item );
|
||||
if ( apply_filters( 'woocommerce_order_item_visible', true, $item ) ) {
|
||||
// Translators: %s: product name.
|
||||
$aria_label = sprintf( __( 'Remove %s', 'woocommerce-subscriptions' ), esc_html( $_product->get_name() ) );
|
||||
?>
|
||||
<tr class="<?php echo esc_attr( apply_filters( 'woocommerce_order_item_class', 'order_item', $item, $subscription ) ); ?>">
|
||||
<?php if ( $allow_remove_items ) : ?>
|
||||
<td class="remove_item">
|
||||
<?php if ( wcs_can_item_be_removed( $item, $subscription ) ) : ?>
|
||||
<?php $confirm_notice = apply_filters( 'woocommerce_subscriptions_order_item_remove_confirmation_text', __( 'Are you sure you want remove this item from your subscription?', 'woocommerce-subscriptions' ), $item, $_product, $subscription ); ?>
|
||||
<a href="<?php echo esc_url( WCS_Remove_Item::get_remove_url( $subscription->get_id(), $item_id ) ); ?>" class="remove" onclick="return confirm('<?php printf( esc_html( $confirm_notice ) ); ?>');">×</a>
|
||||
<?php $confirm_notice = apply_filters( 'woocommerce_subscriptions_order_item_remove_confirmation_text', __( 'Are you sure you want to remove this item from your subscription?', 'woocommerce-subscriptions' ), $item, $_product, $subscription ); ?>
|
||||
<a
|
||||
href="<?php echo esc_url( WCS_Remove_Item::get_remove_url( $subscription->get_id(), $item_id ) ); ?>"
|
||||
class="remove"
|
||||
role="button"
|
||||
onclick="return confirm('<?php printf( esc_html( $confirm_notice ) ); ?>');"
|
||||
aria-haspopup="dialog"
|
||||
aria-label="<?php echo esc_attr( $aria_label ); ?>"
|
||||
>
|
||||
×
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<?php endif; ?>
|
||||
|
|
|
|||
|
|
@ -18,21 +18,34 @@ $include_item_removal_links = $include_switch_links = false;
|
|||
<?php do_action( 'woocommerce_subscription_totals', $subscription, $include_item_removal_links, $totals, $include_switch_links ); ?>
|
||||
</div>
|
||||
<p class="wcs_early_renew_modal_note">
|
||||
<?php if ( ! empty( $new_next_payment_date ) ) {
|
||||
echo wp_kses_post( sprintf(
|
||||
__( 'By renewing your subscription early your next payment will be %s.', 'woocommerce-subscriptions' ),
|
||||
'<strong>' . esc_html( date_i18n( wc_date_format(), $new_next_payment_date->getOffsetTimestamp() ) ) . '</strong>'
|
||||
) );
|
||||
<?php
|
||||
if ( ! empty( $new_next_payment_date ) ) {
|
||||
echo wp_kses_post(
|
||||
sprintf(
|
||||
// Translators: 1: new next payment date.
|
||||
__( 'By renewing your subscription early your next payment will be %s.', 'woocommerce-subscriptions' ),
|
||||
'<strong>' . esc_html( date_i18n( wc_date_format(), $new_next_payment_date->getOffsetTimestamp() ) ) . '</strong>'
|
||||
)
|
||||
);
|
||||
} else {
|
||||
echo wp_kses_post( sprintf(
|
||||
__( 'By renewing your subscription early, your scheduled next payment on %s will be cancelled.', 'woocommerce-subscriptions' ),
|
||||
'<strong>' . esc_html( date_i18n( wc_date_format(), $subscription->get_time( 'next_payment', 'site' ) ) ) . '</strong>'
|
||||
) );
|
||||
}?>
|
||||
echo wp_kses_post(
|
||||
sprintf(
|
||||
// Translators: 1: currently schedulednext payment date.
|
||||
__( 'By renewing your subscription early, your scheduled next payment on %1$s will be cancelled.', 'woocommerce-subscriptions' ),
|
||||
'<strong>' . esc_html( date_i18n( wc_date_format(), $subscription->get_time( 'next_payment', 'site' ) ) ) . '</strong>'
|
||||
)
|
||||
);
|
||||
}
|
||||
?>
|
||||
<br>
|
||||
<?php echo wp_kses_post( sprintf(
|
||||
__( 'Want to renew early via the checkout? Click %shere.%s', 'woocommerce-subscriptions' ),
|
||||
'<a href="' . esc_url( wcs_get_early_renewal_url( $subscription ) ) . '">',
|
||||
'</a>'
|
||||
) ) ?>
|
||||
<?php
|
||||
echo wp_kses_post(
|
||||
sprintf(
|
||||
// Translators: 1: opening link tag 2: closing link tag.
|
||||
__( '%1$sClick here to renew early via the checkout.%2$s', 'woocommerce-subscriptions' ),
|
||||
'<a href="' . esc_url( wcs_get_early_renewal_url( $subscription ) ) . '">',
|
||||
'</a>'
|
||||
)
|
||||
)
|
||||
?>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -9,13 +9,19 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
exit;
|
||||
}
|
||||
?>
|
||||
<div data-modal-trigger="<?php echo esc_attr( $modal->get_trigger() );?>" class="wcs-modal" tabindex="0">
|
||||
<article class="content-wrapper">
|
||||
<div data-modal-trigger="<?php echo esc_attr( $modal->get_trigger() );?>" class="wcs-modal" id="<?php echo esc_attr( $modal->get_id() ); ?>" tabindex="0">
|
||||
<?php
|
||||
$article_attributes = 'class="content-wrapper" role="dialog" aria-modal="true"';
|
||||
if ( $modal->has_heading() ) {
|
||||
$article_attributes .= ' aria-labelledby="' . esc_attr( $modal->get_id() . '-heading' ) . '"';
|
||||
}
|
||||
?>
|
||||
<article <?php echo $article_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
|
||||
<header class="modal-header">
|
||||
<?php if ( $modal->has_heading() ) : ?>
|
||||
<h2><?php echo esc_html( $modal->get_heading() ) ?></h2>
|
||||
<h2 id="<?php echo esc_attr( $modal->get_id() . '-heading' ); ?>"><?php echo esc_html( $modal->get_heading() ) ?></h2>
|
||||
<?php endif ?>
|
||||
<a href="#" onclick="return false;" class="close" style="text-decoration: none;"><span class="dashicons dashicons-no"></span></a>
|
||||
<button type="button" class="close" aria-label="<?php esc_attr_e( 'Close modal', 'woocommerce-subscriptions' ); ?>"><span class="dashicons dashicons-no"></span></button>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
<th class="subscription-status order-status woocommerce-orders-table__header woocommerce-orders-table__header-order-status woocommerce-orders-table__header-subscription-status"><span class="nobr"><?php esc_html_e( 'Status', 'woocommerce-subscriptions' ); ?></span></th>
|
||||
<th class="subscription-next-payment order-date woocommerce-orders-table__header woocommerce-orders-table__header-order-date woocommerce-orders-table__header-subscription-next-payment"><span class="nobr"><?php echo esc_html_x( 'Next payment', 'table heading', 'woocommerce-subscriptions' ); ?></span></th>
|
||||
<th class="subscription-total order-total woocommerce-orders-table__header woocommerce-orders-table__header-order-total woocommerce-orders-table__header-subscription-total"><span class="nobr"><?php echo esc_html_x( 'Total', 'table heading', 'woocommerce-subscriptions' ); ?></span></th>
|
||||
<th class="subscription-actions order-actions woocommerce-orders-table__header woocommerce-orders-table__header-order-actions woocommerce-orders-table__header-subscription-actions"> </th>
|
||||
<th class="subscription-actions order-actions woocommerce-orders-table__header woocommerce-orders-table__header-order-actions woocommerce-orders-table__header-subscription-actions"><span class="screen-reader-text"><?php esc_html_e( 'Actions', 'woocommerce-subscriptions' ); ?></span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
<th class="order-date woocommerce-orders-table__header woocommerce-orders-table__header-order-date woocommerce-orders-table__header-order-date"><span class="nobr"><?php esc_html_e( 'Date', 'woocommerce-subscriptions' ); ?></span></th>
|
||||
<th class="order-status woocommerce-orders-table__header woocommerce-orders-table__header-order-status"><span class="nobr"><?php esc_html_e( 'Status', 'woocommerce-subscriptions' ); ?></span></th>
|
||||
<th class="order-total woocommerce-orders-table__header woocommerce-orders-table__header-order-total"><span class="nobr"><?php echo esc_html_x( 'Total', 'table heading', 'woocommerce-subscriptions' ); ?></span></th>
|
||||
<th class="order-actions woocommerce-orders-table__header woocommerce-orders-table__header-order-actions"> </th>
|
||||
<th class="order-actions woocommerce-orders-table__header woocommerce-orders-table__header-order-actions"><span class="screen-reader-text"><?php esc_html_e( 'Actions', 'woocommerce-subscriptions' ); ?></span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*
|
||||
* @author Prospress
|
||||
* @category WooCommerce Subscriptions/Templates
|
||||
* @version 7.3.0 - Migrated from WooCommerce Subscriptions v2.6.0
|
||||
* @version 8.2.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
|
|
@ -26,7 +26,14 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ( $subscriptions as $subscription_id => $subscription ) : ?>
|
||||
<?php
|
||||
foreach ( $subscriptions as $subscription_id => $subscription ) {
|
||||
$view_order_label = sprintf(
|
||||
// Translators: %1$d is the subscription number.
|
||||
__( 'View subscription %1$d', 'woocommerce-subscriptions' ),
|
||||
$subscription_id
|
||||
);
|
||||
?>
|
||||
<tr class="order woocommerce-orders-table__row woocommerce-orders-table__row--status-<?php echo esc_attr( $subscription->get_status() ); ?>">
|
||||
<td class="subscription-id order-number woocommerce-orders-table__cell woocommerce-orders-table__cell-subscription-id woocommerce-orders-table__cell-order-number" data-title="<?php esc_attr_e( 'ID', 'woocommerce-subscriptions' ); ?>">
|
||||
<?php // translators: placeholder is a subscription number. ?>
|
||||
|
|
@ -44,10 +51,16 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
<?php echo wp_kses_post( $subscription->get_formatted_order_total() ); ?>
|
||||
</td>
|
||||
<td class="subscription-actions order-actions woocommerce-orders-table__cell woocommerce-orders-table__cell-subscription-actions woocommerce-orders-table__cell-order-actions">
|
||||
<a href="<?php echo esc_url( $subscription->get_view_order_url() ); ?>" class="woocommerce-button button view<?php echo esc_attr( wc_wp_theme_get_element_class_name( 'button' ) ? ' ' . wc_wp_theme_get_element_class_name( 'button' ) : '' ); ?>"><?php echo esc_html_x( 'View', 'view a subscription', 'woocommerce-subscriptions' ); ?></a>
|
||||
<a
|
||||
href="<?php echo esc_url( $subscription->get_view_order_url() ); ?>"
|
||||
class="woocommerce-button button view<?php echo esc_attr( wc_wp_theme_get_element_class_name( 'button' ) ? ' ' . wc_wp_theme_get_element_class_name( 'button' ) : '' ); ?>"
|
||||
aria-label="<?php echo esc_attr( $view_order_label ); ?>"
|
||||
>
|
||||
<?php echo esc_html_x( 'View', 'view a subscription', 'woocommerce-subscriptions' ); ?>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php } // endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
* @author Prospress
|
||||
* @package WooCommerce_Subscription/Templates
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.19
|
||||
* @version 1.0.0 - Migrated from WooCommerce Subscriptions v2.6.5
|
||||
* @version 8.2.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
|
|
@ -43,22 +43,30 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
<td>
|
||||
<div class="wcs-auto-renew-toggle">
|
||||
<?php
|
||||
$is_auto_renew_on = ! $subscription->is_manual();
|
||||
$toggle_classes = array( 'subscription-auto-renew-toggle', 'subscription-auto-renew-toggle--hidden' );
|
||||
$is_duplicate_site = false;
|
||||
|
||||
$toggle_classes = array( 'subscription-auto-renew-toggle', 'subscription-auto-renew-toggle--hidden' );
|
||||
|
||||
if ( $subscription->is_manual() ) {
|
||||
$toggle_label = __( 'Enable auto renew', 'woocommerce-subscriptions' );
|
||||
if ( $is_auto_renew_on ) {
|
||||
$toggle_classes[] = 'subscription-auto-renew-toggle--on';
|
||||
} else {
|
||||
$toggle_classes[] = 'subscription-auto-renew-toggle--off';
|
||||
|
||||
if ( WCS_Staging::is_duplicate_site() ) {
|
||||
$toggle_classes[] = 'subscription-auto-renew-toggle--disabled';
|
||||
$toggle_classes[] = 'subscription-auto-renew-toggle--disabled';
|
||||
$is_duplicate_site = true;
|
||||
}
|
||||
} else {
|
||||
$toggle_label = __( 'Disable auto renew', 'woocommerce-subscriptions' );
|
||||
$toggle_classes[] = 'subscription-auto-renew-toggle--on';
|
||||
}?>
|
||||
<a href="#" class="<?php echo esc_attr( implode( ' ' , $toggle_classes ) ); ?>" aria-label="<?php echo esc_attr( $toggle_label ) ?>"><i class="subscription-auto-renew-toggle__i" aria-hidden="true"></i></a>
|
||||
<?php if ( WCS_Staging::is_duplicate_site() ) : ?>
|
||||
}
|
||||
?>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked="<?php echo $is_auto_renew_on ? 'true' : 'false'; ?>"
|
||||
aria-label="<?php esc_attr_e( 'Auto renew', 'woocommerce-subscriptions' ); ?>"
|
||||
class="<?php echo esc_attr( implode( ' ', $toggle_classes ) ); ?>"
|
||||
<?php disabled( $is_duplicate_site ); ?>
|
||||
><i class="subscription-auto-renew-toggle__i" aria-hidden="true"></i></button>
|
||||
<?php if ( $is_duplicate_site ) : ?>
|
||||
<small class="subscription-auto-renew-toggle-disabled-note"><?php echo esc_html__( 'Using the auto-renewal toggle is disabled while in staging mode.', 'woocommerce-subscriptions' ); ?></small>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
|
@ -88,10 +96,19 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
if ( wc_wp_theme_get_element_class_name( 'button' ) ) {
|
||||
$classes[] = wc_wp_theme_get_element_class_name( 'button' );
|
||||
}
|
||||
|
||||
// Role is used for accessibility purposes. Default role is 'button', because of the default visual styling.
|
||||
$action_role = isset( $action['role'] ) ? $action['role'] : 'button';
|
||||
?>
|
||||
<a
|
||||
href="<?php echo esc_url( $action['url'] ); ?>"
|
||||
role="<?php echo esc_attr( $action_role ); ?>"
|
||||
class="<?php echo esc_attr( trim( implode( ' ', $classes ) ) ); ?>"
|
||||
<?php
|
||||
if ( isset( $action['modal_id'] ) ) {
|
||||
echo ' aria-haspopup="dialog" aria-controls="' . esc_attr( $action['modal_id'] ) . '"';
|
||||
}
|
||||
?>
|
||||
>
|
||||
<?php echo esc_html( $action['name'] ); ?>
|
||||
</a>
|
||||
|
|
@ -114,8 +131,8 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
<div class="woocommerce-OrderUpdate-description description">
|
||||
<?php echo wp_kses_post( wpautop( wptexturize( $note->comment_content ) ) ); ?>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*
|
||||
* @package WooCommerce_Subscription/Templates
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.6.0
|
||||
* @version 7.2.0
|
||||
* @version 8.2.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
|
|
@ -43,8 +43,21 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
<?php if ( $allow_item_removal ) : ?>
|
||||
<td class="remove_item">
|
||||
<?php if ( wcs_can_item_be_removed( $item, $subscription ) ) : ?>
|
||||
<?php $confirm_notice = apply_filters( 'woocommerce_subscriptions_order_item_remove_confirmation_text', __( 'Are you sure you want to remove this item from your subscription?', 'woocommerce-subscriptions' ), $item, $_product, $subscription ); ?>
|
||||
<a href="<?php echo esc_url( WCS_Remove_Item::get_remove_url( $subscription->get_id(), $item_id ) ); ?>" class="remove" onclick="return confirm('<?php printf( esc_html( $confirm_notice ) ); ?>');">×</a>
|
||||
<?php
|
||||
// Translators: %s: product name.
|
||||
$aria_label = sprintf( __( 'Remove %s', 'woocommerce-subscriptions' ), esc_html( $_product->get_name() ) );
|
||||
$confirm_notice = apply_filters( 'woocommerce_subscriptions_order_item_remove_confirmation_text', __( 'Are you sure you want to remove this item from your subscription?', 'woocommerce-subscriptions' ), $item, $_product, $subscription );
|
||||
?>
|
||||
<a
|
||||
href="<?php echo esc_url( WCS_Remove_Item::get_remove_url( $subscription->get_id(), $item_id ) ); ?>"
|
||||
class="remove"
|
||||
role="button"
|
||||
onclick="return confirm('<?php printf( esc_html( $confirm_notice ) ); ?>');"
|
||||
aria-haspopup="dialog"
|
||||
aria-label="<?php echo esc_attr( $aria_label ); ?>"
|
||||
>
|
||||
×
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<?php endif; ?>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ return array(
|
|||
'Automattic\\WooCommerce_Subscriptions\\Internal\\Telemetry\\Orders' => $baseDir . '/src/Internal/Telemetry/Orders.php',
|
||||
'Automattic\\WooCommerce_Subscriptions\\Internal\\Telemetry\\Products' => $baseDir . '/src/Internal/Telemetry/Products.php',
|
||||
'Automattic\\WooCommerce_Subscriptions\\Internal\\Telemetry\\Subscriptions' => $baseDir . '/src/Internal/Telemetry/Subscriptions.php',
|
||||
'Automattic\\WooCommerce_Subscriptions\\Internal\\Utilities\\Request' => $baseDir . '/src/Internal/Utilities/Request.php',
|
||||
'Automattic\\WooCommerce_Subscriptions\\Internal\\Utilities\\Scheduled_Actions' => $baseDir . '/src/Internal/Utilities/Scheduled_Actions.php',
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
'Composer\\Installers\\AglInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/AglInstaller.php',
|
||||
|
|
@ -138,6 +139,7 @@ return array(
|
|||
'WCSG_Template_Loader' => $baseDir . '/includes/gifting/class-wcsg-template-loader.php',
|
||||
'WCS_Gifting' => $baseDir . '/includes/gifting/class-wcs-gifting.php',
|
||||
'WC_Subscription_Downloads' => $baseDir . '/includes/downloads/class-wc-subscription-downloads.php',
|
||||
'WC_Subscription_Downloads_Admin_Welcome_Announcement' => $baseDir . '/includes/downloads/class-wc-subscription-downloads-admin-welcome-announcement.php',
|
||||
'WC_Subscription_Downloads_Ajax' => $baseDir . '/includes/downloads/class-wc-subscription-downloads-ajax.php',
|
||||
'WC_Subscription_Downloads_Install' => $baseDir . '/includes/downloads/class-wc-subscription-downloads-install.php',
|
||||
'WC_Subscription_Downloads_Order' => $baseDir . '/includes/downloads/class-wc-subscription-downloads-order.php',
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ class ComposerStaticInitcb45de1ca955f89ec737c442c7cf101c
|
|||
'Automattic\\WooCommerce_Subscriptions\\Internal\\Telemetry\\Orders' => __DIR__ . '/../..' . '/src/Internal/Telemetry/Orders.php',
|
||||
'Automattic\\WooCommerce_Subscriptions\\Internal\\Telemetry\\Products' => __DIR__ . '/../..' . '/src/Internal/Telemetry/Products.php',
|
||||
'Automattic\\WooCommerce_Subscriptions\\Internal\\Telemetry\\Subscriptions' => __DIR__ . '/../..' . '/src/Internal/Telemetry/Subscriptions.php',
|
||||
'Automattic\\WooCommerce_Subscriptions\\Internal\\Utilities\\Request' => __DIR__ . '/../..' . '/src/Internal/Utilities/Request.php',
|
||||
'Automattic\\WooCommerce_Subscriptions\\Internal\\Utilities\\Scheduled_Actions' => __DIR__ . '/../..' . '/src/Internal/Utilities/Scheduled_Actions.php',
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
'Composer\\Installers\\AglInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/AglInstaller.php',
|
||||
|
|
@ -161,6 +162,7 @@ class ComposerStaticInitcb45de1ca955f89ec737c442c7cf101c
|
|||
'WCSG_Template_Loader' => __DIR__ . '/../..' . '/includes/gifting/class-wcsg-template-loader.php',
|
||||
'WCS_Gifting' => __DIR__ . '/../..' . '/includes/gifting/class-wcs-gifting.php',
|
||||
'WC_Subscription_Downloads' => __DIR__ . '/../..' . '/includes/downloads/class-wc-subscription-downloads.php',
|
||||
'WC_Subscription_Downloads_Admin_Welcome_Announcement' => __DIR__ . '/../..' . '/includes/downloads/class-wc-subscription-downloads-admin-welcome-announcement.php',
|
||||
'WC_Subscription_Downloads_Ajax' => __DIR__ . '/../..' . '/includes/downloads/class-wc-subscription-downloads-ajax.php',
|
||||
'WC_Subscription_Downloads_Install' => __DIR__ . '/../..' . '/includes/downloads/class-wc-subscription-downloads-install.php',
|
||||
'WC_Subscription_Downloads_Order' => __DIR__ . '/../..' . '/includes/downloads/class-wc-subscription-downloads-order.php',
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<?php return array(
|
||||
'root' => array(
|
||||
'name' => 'woocommerce/woocommerce-subscriptions',
|
||||
'pretty_version' => 'dev-release/8.1.0',
|
||||
'version' => 'dev-release/8.1.0',
|
||||
'reference' => '073151600fa05dc26b7244faf4808c0f49e63436',
|
||||
'pretty_version' => 'dev-release/8.3.0',
|
||||
'version' => 'dev-release/8.3.0',
|
||||
'reference' => '38b39c285d0bae952d5746ba460ae914e4ac0f60',
|
||||
'type' => 'wordpress-plugin',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
|
|
@ -29,9 +29,9 @@
|
|||
'dev_requirement' => false,
|
||||
),
|
||||
'woocommerce/woocommerce-subscriptions' => array(
|
||||
'pretty_version' => 'dev-release/8.1.0',
|
||||
'version' => 'dev-release/8.1.0',
|
||||
'reference' => '073151600fa05dc26b7244faf4808c0f49e63436',
|
||||
'pretty_version' => 'dev-release/8.3.0',
|
||||
'version' => 'dev-release/8.3.0',
|
||||
'reference' => '38b39c285d0bae952d5746ba460ae914e4ac0f60',
|
||||
'type' => 'wordpress-plugin',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@
|
|||
* Description: Sell products and services with recurring payments in your WooCommerce Store.
|
||||
* Author: WooCommerce
|
||||
* Author URI: https://woocommerce.com/
|
||||
* Version: 8.1.0
|
||||
* Version: 8.3.0
|
||||
* Requires Plugins: woocommerce
|
||||
*
|
||||
* WC requires at least: 10.2.0
|
||||
* WC tested up to: 10.3.0
|
||||
* WC requires at least: 10.3.0
|
||||
* WC tested up to: 10.4.3
|
||||
* Requires PHP: 7.4
|
||||
*
|
||||
* License: GNU General Public License v3.0
|
||||
|
|
@ -84,7 +84,7 @@ class WC_Subscriptions {
|
|||
public static $plugin_file = __FILE__;
|
||||
|
||||
/** @var string */
|
||||
public static $version = '8.1.0'; // WRCS: DEFINED_VERSION.
|
||||
public static $version = '8.3.0'; // WRCS: DEFINED_VERSION.
|
||||
|
||||
/** @var string */
|
||||
public static $wc_minimum_supported_version = '7.7';
|
||||
|
|
|
|||
Loading…
Reference in New Issue