Compare commits
2 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
56e03c0544 | |
|
|
bfe7fb609d |
|
|
@ -77,6 +77,14 @@ a.close-subscriptions-search {
|
||||||
display: block;
|
display: block;
|
||||||
width: 50%;
|
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 */
|
/* Variation Subscription Product Sync Settings */
|
||||||
.variable_subscription_sync .subscription_sync_annual > .select2-container {
|
.variable_subscription_sync .subscription_sync_annual > .select2-container {
|
||||||
max-width: 48%;
|
max-width: 48%;
|
||||||
|
|
@ -385,6 +393,12 @@ p._subscription_gifting_field_description.form-field .description {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#woocommerce-product-data
|
||||||
|
.form-field._subscription_downloads_field
|
||||||
|
.select2-selection__choice {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Variation Pricing Fields with WooCommerce 3.0+ */
|
/* Variation Pricing Fields with WooCommerce 3.0+ */
|
||||||
.wc-metaboxes-wrapper .variable_subscription_trial label,
|
.wc-metaboxes-wrapper .variable_subscription_trial label,
|
||||||
.wc-metaboxes-wrapper .variable_subscription_pricing label,
|
.wc-metaboxes-wrapper .variable_subscription_pricing label,
|
||||||
|
|
|
||||||
|
|
@ -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(
|
$( '.options_group.pricing ._sale_price_field .description' ).prepend(
|
||||||
'<span id="sale-price-period" style="display: none;"></span>'
|
'<span id="sale-price-period" style="display: none;"></span>'
|
||||||
);
|
);
|
||||||
|
|
@ -653,9 +700,9 @@ jQuery( function ( $ ) {
|
||||||
$( '.options_group.subscription_pricing' )
|
$( '.options_group.subscription_pricing' )
|
||||||
);
|
);
|
||||||
|
|
||||||
// Move the subscription variation pricing and gifting sections to a better location in the DOM on load
|
// Move the subscription variation pricing, gifting and linked downloadable product sections to a better location in
|
||||||
// We do this because these sections are initially loaded at the end of the variable product options section,
|
// the DOM on load. We do this because these sections are initially loaded at the end of the variable product
|
||||||
// which is not the best location for them, so we move to before the "Sale price" section.
|
// options section, which is not the best location for them, so we move to before the "Sale price" section.
|
||||||
if (
|
if (
|
||||||
$( '#variable_product_options .variable_subscription_pricing' ).length >
|
$( '#variable_product_options .variable_subscription_pricing' ).length >
|
||||||
0
|
0
|
||||||
|
|
@ -668,6 +715,7 @@ jQuery( function ( $ ) {
|
||||||
) {
|
) {
|
||||||
$.moveSubscriptionGiftingFields();
|
$.moveSubscriptionGiftingFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
// When a variation is added
|
// When a variation is added
|
||||||
$( '#woocommerce-product-data' ).on(
|
$( '#woocommerce-product-data' ).on(
|
||||||
'woocommerce_variations_added woocommerce_variations_loaded',
|
'woocommerce_variations_added woocommerce_variations_loaded',
|
||||||
|
|
@ -677,6 +725,7 @@ jQuery( function ( $ ) {
|
||||||
$.showHideVariableSubscriptionMeta();
|
$.showHideVariableSubscriptionMeta();
|
||||||
$.showHideSyncOptions();
|
$.showHideSyncOptions();
|
||||||
$.setSubscriptionLengths();
|
$.setSubscriptionLengths();
|
||||||
|
initLinkedDownloadableProductFieldsForVariationProduct();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -690,6 +739,7 @@ jQuery( function ( $ ) {
|
||||||
$.showHideSyncOptions();
|
$.showHideSyncOptions();
|
||||||
$.disableEnableOneTimeShipping();
|
$.disableEnableOneTimeShipping();
|
||||||
$.showHideSubscriptionsPanels();
|
$.showHideSubscriptionsPanels();
|
||||||
|
relocateLinkedDownloadableProductFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update subscription ranges when subscription period or interval is changed
|
// 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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -18,7 +18,8 @@ jQuery( document ).ready( function ( $ ) {
|
||||||
.find( '.recipient_email' )
|
.find( '.recipient_email' )
|
||||||
.trigger( 'focus' );
|
.trigger( 'focus' );
|
||||||
}
|
}
|
||||||
} );
|
} )
|
||||||
|
.removeClass( 'hidden' );
|
||||||
|
|
||||||
const shipToDifferentAddressCheckbox = $( document ).find(
|
const shipToDifferentAddressCheckbox = $( document ).find(
|
||||||
'#ship-to-different-address-checkbox'
|
'#ship-to-different-address-checkbox'
|
||||||
|
|
@ -31,12 +32,14 @@ jQuery( document ).ready( function ( $ ) {
|
||||||
$( this )
|
$( this )
|
||||||
.closest( '.wcsg_add_recipient_fields_container' )
|
.closest( '.wcsg_add_recipient_fields_container' )
|
||||||
.find( '.wcsg_add_recipient_fields' )
|
.find( '.wcsg_add_recipient_fields' )
|
||||||
.slideUp( 250 );
|
.slideUp( 250 )
|
||||||
|
.addClass( 'hidden' );
|
||||||
|
|
||||||
const recipientEmailElement = $( this )
|
const recipientEmailElement = $( this )
|
||||||
.closest( '.wcsg_add_recipient_fields_container' )
|
.closest( '.wcsg_add_recipient_fields_container' )
|
||||||
.find( '.recipient_email' );
|
.find( '.recipient_email' );
|
||||||
recipientEmailElement.val( '' );
|
recipientEmailElement.val( '' );
|
||||||
|
hideValidationErrorForEmailField( recipientEmailElement );
|
||||||
setShippingAddressNoticeVisibility( true );
|
setShippingAddressNoticeVisibility( true );
|
||||||
|
|
||||||
if ( $( 'form.checkout' ).length !== 0 ) {
|
if ( $( 'form.checkout' ).length !== 0 ) {
|
||||||
|
|
|
||||||
|
|
@ -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' => '841d6ab2fe0080f15a21');
|
<?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,18 @@
|
||||||
*** WooCommerce Subscriptions Changelog ***
|
*** 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
|
2025-12-10 - version 8.2.0
|
||||||
* Fix: Various accessibility issues.
|
* Fix: Various accessibility issues.
|
||||||
* Added button roles.
|
* Added button roles.
|
||||||
|
|
|
||||||
|
|
@ -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 = [
|
private const ALLOWED_OPTIONS = [
|
||||||
'woocommerce_subscriptions_gifting_is_welcome_announcement_dismissed',
|
'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_Call_To_Action_Button_Text_Manager::init();
|
||||||
WCS_Subscriber_Role_Manager::init();
|
WCS_Subscriber_Role_Manager::init();
|
||||||
WCS_Upgrade_Notice_Manager::init();
|
WCS_Upgrade_Notice_Manager::init();
|
||||||
|
WCS_Admin_Assets::init();
|
||||||
|
|
||||||
$tracks_events = new WC_Tracks_Events();
|
$tracks_events = new WC_Tracks_Events();
|
||||||
$tracks_events->setup();
|
$tracks_events->setup();
|
||||||
|
|
@ -300,10 +301,16 @@ class WC_Subscriptions_Plugin extends WC_Subscriptions_Core_Plugin {
|
||||||
*/
|
*/
|
||||||
public function init_downloads() {
|
public function init_downloads() {
|
||||||
if (
|
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 )
|
|| 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ class WCS_Autoloader extends WCS_Core_Autoloader {
|
||||||
'wcs_webhooks' => true,
|
'wcs_webhooks' => true,
|
||||||
'wcs_auth' => true,
|
'wcs_auth' => true,
|
||||||
'wcs_upgrade_notice_manager' => true,
|
'wcs_upgrade_notice_manager' => true,
|
||||||
|
'wcs_admin_assets' => true,
|
||||||
'wc_subscriptions_cli' => true,
|
'wc_subscriptions_cli' => true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1171,7 +1171,7 @@ class WCS_Admin_Post_Types {
|
||||||
|
|
||||||
// @phpstan-ignore property.notFound
|
// @phpstan-ignore property.notFound
|
||||||
foreach ( WC()->payment_gateways->get_available_payment_gateways() as $gateway_id => $gateway ) {
|
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>';
|
echo '<option value="_manual_renewal">' . esc_html__( 'Manual Renewal', 'woocommerce-subscriptions' ) . '</option>';
|
||||||
?>
|
?>
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ class WC_Product_Subscription extends WC_Product_Simple {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return apply_filters( 'woocommerce_product_add_to_cart_description', sprintf( $text, $this->get_name() ), $this );
|
return apply_filters( 'woocommerce_product_add_to_cart_description', $text, $this );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -383,17 +383,10 @@ class WC_Subscriptions_Product {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} elseif ( 1 === $billing_interval ) {
|
|
||||||
$subscription_string = sprintf(
|
|
||||||
// translators: 1$: recurring amount, 2$: subscription period (e.g. "month") (e.g. "$15 / month").
|
|
||||||
__( '%1$s / %2$s', 'woocommerce-subscriptions' ),
|
|
||||||
$price,
|
|
||||||
wcs_get_subscription_period_strings( $billing_interval, $billing_period )
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
$subscription_string = sprintf(
|
$subscription_string = sprintf(
|
||||||
// translators: 1$: recurring amount, 2$: subscription period (e.g. "3 months") (e.g. "$15 every 2nd month").
|
// translators: 1$: recurring amount, 2$: subscription period (e.g. "month" or "3 months") (e.g. "$15 / month" or "$15 every 2nd month").
|
||||||
__( '%1$s every %2$s', 'woocommerce-subscriptions' ),
|
_n( '%1$s / %2$s', '%1$s every %2$s', $billing_interval, 'woocommerce-subscriptions' ),
|
||||||
$price,
|
$price,
|
||||||
wcs_get_subscription_period_strings( $billing_interval, $billing_period )
|
wcs_get_subscription_period_strings( $billing_interval, $billing_period )
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,43 @@ class WCS_Limiter {
|
||||||
do_action( 'woocommerce_subscriptions_product_options_advanced' );
|
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.
|
* Canonical is_purchasable method to be called by product classes.
|
||||||
*
|
*
|
||||||
|
|
@ -68,73 +105,62 @@ class WCS_Limiter {
|
||||||
*/
|
*/
|
||||||
public static function is_purchasable( $purchasable, $product ) {
|
public static function is_purchasable( $purchasable, $product ) {
|
||||||
|
|
||||||
// Prevents making a non purchasable product purchasable again.
|
// Check if product is private (for variations, also check parent product)
|
||||||
// This can happen if the product is disabled and limited and the customer is trying to renew the subscription for example.
|
$is_private_product = 'private' === $product->get_status();
|
||||||
if ( ! $purchasable ) {
|
if ( $product->get_parent_id() > 0 ) {
|
||||||
return $purchasable;
|
$parent_product = wc_get_product( $product->get_parent_id() );
|
||||||
|
if ( $parent_product ) {
|
||||||
|
$is_private_product = 'private' === $parent_product->get_status();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ( $product->get_type() ) {
|
// Checks limits for variable subscription products.
|
||||||
case 'subscription':
|
if ( $product->get_type() === 'subscription_variation' && isset( $parent_product ) ) {
|
||||||
case 'variable-subscription':
|
|
||||||
// Checks if the product is limited.
|
if ( $is_private_product ) {
|
||||||
if ( false === self::is_product_limited( $purchasable, $product ) ) {
|
$purchasable = false;
|
||||||
// Product is limited, so it is not purchasable.
|
|
||||||
|
// 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;
|
$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;
|
return $purchasable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -302,12 +328,73 @@ class WCS_Limiter {
|
||||||
return self::$order_awaiting_payment_for_product[ $product_id ];
|
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.
|
* 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.
|
* 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.
|
* @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.
|
* @return array $statuses An empty array if the order contains limited products, otherwise the default statuses are returned.
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ class WCS_Email_Customer_Renewal_Invoice extends WC_Email_Customer_Invoice {
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
function get_subject() {
|
function get_subject() {
|
||||||
return apply_filters( 'woocommerce_subscriptions_email_subject_new_renewal_order', parent::get_subject(), $this->object );
|
return apply_filters( 'woocommerce_subscriptions_email_subject_new_renewal_order', $this->format_string( $this->get_option( 'subject', $this->get_default_subject() ) ), $this->object );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -134,7 +134,7 @@ class WCS_Email_Customer_Renewal_Invoice extends WC_Email_Customer_Invoice {
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
function get_heading() {
|
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 );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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 ) );
|
$transaction_order = wc_get_order( substr( $transaction_details['invoice'], strrpos( $transaction_details['invoice'], '-' ) + 1 ) );
|
||||||
|
|
||||||
// check if the failed signup has been previously recorded
|
// 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;
|
$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();
|
WCS_Plugin_Upgrade_7_8_0::check_gifting_plugin_is_enabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( false && version_compare( self::$stored_plugin_version, '8.2.0', '<' ) ) {
|
if ( version_compare( self::$stored_plugin_version, '8.3.0', '<' ) ) {
|
||||||
// TODO: remove false from the above conditional, once we are ready to make subscription downloads functionality available.
|
WCS_Plugin_Upgrade_8_3_0::check_downloads_plugin_is_enabled();
|
||||||
WCS_Plugin_Upgrade_8_1_0::check_downloads_plugin_is_enabled();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||||
exit; // Exit if accessed directly
|
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.
|
* Check if the Gifting plugin is enabled and update the settings.
|
||||||
|
|
@ -190,22 +190,12 @@ function wcs_price_string( $subscription_details ) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} elseif ( ! empty( $subscription_details['initial_amount'] ) ) {
|
} elseif ( ! empty( $subscription_details['initial_amount'] ) ) {
|
||||||
if ( 1 === $subscription_details['subscription_interval'] ) {
|
// translators: 1$: initial amount, 2$: initial description (e.g. "up front"), 3$: recurring amount, 4$: subscription period (e.g. "month" or "3 months")
|
||||||
// translators: 1$: initial amount, 2$: initial description (e.g. "up front"), 3$: recurring amount, 4$: subscription period (e.g. "month")
|
$subscription_string = sprintf( _n( '%1$s %2$s then %3$s / %4$s', '%1$s %2$s then %3$s every %4$s', $subscription_details['subscription_interval'], 'woocommerce-subscriptions' ), $initial_amount_string, $subscription_details['initial_description'], $recurring_amount_string, $subscription_period_string );
|
||||||
$subscription_string = sprintf( __( '%1$s %2$s then %3$s / %4$s', 'woocommerce-subscriptions' ), $initial_amount_string, $subscription_details['initial_description'], $recurring_amount_string, $subscription_period_string );
|
|
||||||
} else {
|
|
||||||
// translators: 1$: initial amount, 2$: initial description (e.g. "up front"), 3$: recurring amount, 4$: subscription period (e.g. "3 months")
|
|
||||||
$subscription_string = sprintf( __( '%1$s %2$s then %3$s every %4$s', 'woocommerce-subscriptions' ), $initial_amount_string, $subscription_details['initial_description'], $recurring_amount_string, $subscription_period_string );
|
|
||||||
}
|
|
||||||
} elseif ( ! empty( $subscription_details['recurring_amount'] ) || intval( $subscription_details['recurring_amount'] ) === 0 ) {
|
} elseif ( ! empty( $subscription_details['recurring_amount'] ) || intval( $subscription_details['recurring_amount'] ) === 0 ) {
|
||||||
if ( true === $subscription_details['use_per_slash'] ) {
|
if ( true === $subscription_details['use_per_slash'] ) {
|
||||||
if ( 1 === $subscription_details['subscription_interval'] ) {
|
// translators: 1$: recurring amount, 2$: subscription period (e.g. "month" or "3 months") (e.g. "$15 / month" or "$15 every 2nd month")
|
||||||
// translators: 1$: recurring amount, 2$: subscription period (e.g. "month") (e.g. "$15 / month")
|
$subscription_string = sprintf( _n( '%1$s / %2$s', '%1$s every %2$s', $subscription_details['subscription_interval'], 'woocommerce-subscriptions' ), $recurring_amount_string, $subscription_period_string );
|
||||||
$subscription_string = sprintf( __( '%1$s / %2$s', 'woocommerce-subscriptions' ), $recurring_amount_string, $subscription_period_string );
|
|
||||||
} else {
|
|
||||||
// translators: 1$: recurring amount, 2$: subscription period (e.g. "3 months") (e.g. "$15 every 2nd month")
|
|
||||||
$subscription_string = sprintf( __( '%1$s every %2$s', 'woocommerce-subscriptions' ), $recurring_amount_string, $subscription_period_string );
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// translators: %1$: recurring amount (e.g. "$15"), %2$: subscription period (e.g. "month") (e.g. "$15 every 2nd month")
|
// translators: %1$: recurring amount (e.g. "$15"), %2$: subscription period (e.g. "month") (e.g. "$15 every 2nd month")
|
||||||
$subscription_string = sprintf( __( '%1$s every %2$s', 'woocommerce-subscriptions' ), $recurring_amount_string, $subscription_period_string );
|
$subscription_string = sprintf( __( '%1$s every %2$s', 'woocommerce-subscriptions' ), $recurring_amount_string, $subscription_period_string );
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
public function __construct() {
|
||||||
add_action( 'wp_ajax_wc_subscription_downloads_search', array( $this, 'search_subscriptions' ) );
|
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 );
|
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.
|
* WooCommerce Subscription Downloads Products.
|
||||||
*
|
*
|
||||||
* @package WC_Subscription_Downloads_Products
|
* @package WC_Subscription_Downloads_Products
|
||||||
* @category Products
|
|
||||||
* @author WooThemes
|
|
||||||
*/
|
*/
|
||||||
class WC_Subscription_Downloads_Products {
|
class WC_Subscription_Downloads_Products {
|
||||||
|
public const EDITOR_UPDATE = 'wcsubs_subscription_download_relationships';
|
||||||
|
public const RELATIONSHIP_DOWNLOAD_TO_SUB = 'download-to-sub';
|
||||||
|
public const RELATIONSHIP_VAR_DOWNLOAD_TO_SUB = 'var-download-to-sub';
|
||||||
|
public const RELATIONSHIP_SUB_TO_DOWNLOAD = 'sub-to-download';
|
||||||
|
public const RELATIONSHIP_VAR_SUB_TO_DOWNLOAD = 'var-sub-to-download';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Products actions.
|
* Products actions.
|
||||||
*/
|
*/
|
||||||
public function __construct() {
|
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( 'woocommerce_variation_options_download', array( $this, 'variable_write_panel_options' ), 10, 3 );
|
||||||
add_action( 'admin_enqueue_scripts', array( $this, 'scripts' ) );
|
add_action( 'woocommerce_product_options_pricing', array( $this, 'subscription_product_editor_ui' ) );
|
||||||
add_action( 'woocommerce_process_product_meta_simple', array( $this, 'save_simple_product_data' ), 10 );
|
add_action( 'woocommerce_variable_subscription_pricing', array( $this, 'variable_subscription_product_editor_ui' ), 10, 3 );
|
||||||
add_action( 'woocommerce_save_product_variation', array( $this, 'save_variation_product_data' ), 10, 2 );
|
|
||||||
|
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' ) );
|
add_action( 'init', array( $this, 'init' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
public function init() {
|
public function init() {
|
||||||
if ( version_compare( WC_VERSION, '3.0', '<' ) ) {
|
add_action( 'woocommerce_product_duplicate', array( $this, 'save_subscriptions_when_duplicating_product' ), 10, 2 );
|
||||||
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 );
|
/**
|
||||||
|
* 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
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function scripts() {
|
public function handle_product_variation_save( $post_id ) {
|
||||||
$screen = get_current_screen();
|
// 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', '<' ) ) {
|
if ( wp_is_post_revision( $post_id ) ) {
|
||||||
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
|
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(
|
// Handle downloadable variations (they link TO subscriptions).
|
||||||
'wc_subscription_downloads_writepanel',
|
if ( $variation->is_downloadable() ) {
|
||||||
'wc_subscription_downloads_product',
|
$this->handle_downloadable_product_save( $post_id );
|
||||||
array(
|
}
|
||||||
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
|
||||||
'security' => wp_create_nonce( 'search-products' ),
|
// Handle subscription variations (they link TO downloadable products).
|
||||||
)
|
$parent = wc_get_product( $variation->get_parent_id() );
|
||||||
);
|
|
||||||
|
if ( $parent && $parent->is_type( 'variable-subscription' ) ) {
|
||||||
|
$this->handle_subscription_product_save( $post_id );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle save for downloadable products (simple or variation).
|
||||||
|
* These products link TO subscription products.
|
||||||
|
*
|
||||||
|
* @param int $product_id Product or variation ID.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function handle_downloadable_product_save( $product_id ) {
|
||||||
|
$product = wc_get_product( $product_id );
|
||||||
|
|
||||||
|
if ( ! $product ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||||
|
if (
|
||||||
|
isset( $_POST[ self::RELATIONSHIP_VAR_DOWNLOAD_TO_SUB . $product_id ] )
|
||||||
|
&& wp_verify_nonce( $_POST[ self::RELATIONSHIP_VAR_DOWNLOAD_TO_SUB . $product_id ], self::EDITOR_UPDATE )
|
||||||
|
) {
|
||||||
|
$subscription_ids = wc_clean( wp_unslash( $_POST['_variable_subscription_downloads_ids'][ $product_id ] ?? array() ) );
|
||||||
|
$subscription_ids = array_filter( (array) $subscription_ids );
|
||||||
|
$this->update_subscription_downloads( $product_id, $subscription_ids );
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isset( $_POST[ self::RELATIONSHIP_DOWNLOAD_TO_SUB ] )
|
||||||
|
&& wp_verify_nonce( $_POST[ self::RELATIONSHIP_DOWNLOAD_TO_SUB ], self::EDITOR_UPDATE )
|
||||||
|
) {
|
||||||
|
$subscription_ids = wc_clean( wp_unslash( $_POST['_subscription_downloads_ids'] ?? array() ) );
|
||||||
|
$subscription_ids = array_filter( (array) $subscription_ids );
|
||||||
|
$this->update_subscription_downloads( $product_id, $subscription_ids );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe and act on product status changes (regardless of whether they were made from within the product
|
||||||
|
// editor, therefore we don't care about nonce checks here).
|
||||||
|
$this->assess_downloadable_product_status( $product_id );
|
||||||
|
// phpcs:enable
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle save for subscription products (simple subscription or variation).
|
||||||
|
* These products link TO downloadable products.
|
||||||
|
*
|
||||||
|
* @param int $product_id Subscription product or variation ID.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function handle_subscription_product_save( $product_id ) {
|
||||||
|
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||||
|
if (
|
||||||
|
isset( $_POST[ self::RELATIONSHIP_VAR_SUB_TO_DOWNLOAD . $product_id ] )
|
||||||
|
&& wp_verify_nonce( $_POST[ self::RELATIONSHIP_VAR_SUB_TO_DOWNLOAD . $product_id ], self::EDITOR_UPDATE )
|
||||||
|
) {
|
||||||
|
$product_ids = wc_clean( wp_unslash( $_POST[ '_subscription_linked_downloadable_products_' . $product_id ] ?? array() ) );
|
||||||
|
$product_ids = array_filter( (array) $product_ids );
|
||||||
|
$this->update_subscription_products( $product_id, $product_ids );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isset( $_POST[ self::RELATIONSHIP_SUB_TO_DOWNLOAD ] )
|
||||||
|
&& wp_verify_nonce( $_POST[ self::RELATIONSHIP_SUB_TO_DOWNLOAD ], self::EDITOR_UPDATE )
|
||||||
|
) {
|
||||||
|
$product_ids = wc_clean( wp_unslash( (array) $_POST['_subscription_linked_downloadable_products'] ?? array() ) );
|
||||||
|
$product_ids = array_filter( (array) $product_ids );
|
||||||
|
$this->update_subscription_products( $product_id, $product_ids );
|
||||||
|
}
|
||||||
|
// phpcs:enable
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assess downloadable product status and adjust permissions accordingly.
|
||||||
|
* Called when no form data is available (e.g., status change, REST API update, file changes).
|
||||||
|
*
|
||||||
|
* @param int $product_id Product ID.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function assess_downloadable_product_status( $product_id ) {
|
||||||
|
$product = wc_get_product( $product_id );
|
||||||
|
|
||||||
|
if ( ! $product || ! $product->is_downloadable() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status_object = get_post_status_object( $product->get_status() );
|
||||||
|
$is_public = $status_object && $status_object->public;
|
||||||
|
|
||||||
|
// Always revoke existing permissions first to ensure clean state.
|
||||||
|
// This handles file changes and status transitions.
|
||||||
|
$this->revoke_permissions_for_product( $product_id );
|
||||||
|
|
||||||
|
// Grant fresh permissions only if product is public.
|
||||||
|
if ( $is_public ) {
|
||||||
|
$this->grant_permissions_for_product( $product_id );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,8 +212,8 @@ class WC_Subscription_Downloads_Products {
|
||||||
public function simple_write_panel_options() {
|
public function simple_write_panel_options() {
|
||||||
global $post;
|
global $post;
|
||||||
?>
|
?>
|
||||||
<p class="form-field _subscription_downloads_field">
|
<p class="form-field _subscription_downloads_field hide_if_subscription">
|
||||||
<label for="subscription-downloads-ids"><?php esc_html_e( 'Included subscription products', 'woocommerce-subscriptions' ); ?></label>
|
<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%;">
|
<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
|
<?php
|
||||||
|
|
@ -71,17 +221,18 @@ class WC_Subscription_Downloads_Products {
|
||||||
|
|
||||||
if ( $subscriptions_ids ) {
|
if ( $subscriptions_ids ) {
|
||||||
foreach ( $subscriptions_ids as $subscription_id ) {
|
foreach ( $subscriptions_ids as $subscription_id ) {
|
||||||
$_subscription = wc_get_product( $subscription_id );
|
$subscription = wc_get_product( $subscription_id );
|
||||||
|
|
||||||
if ( $_subscription ) {
|
if ( $subscription ) {
|
||||||
echo '<option value="' . esc_attr( $subscription_id ) . '" selected="selected">' . esc_html( wp_strip_all_tags( $_subscription->get_formatted_name() ) ) . '</option>';
|
echo '<option value="' . esc_attr( $subscription_id ) . '" selected="selected">' . esc_html( wp_strip_all_tags( $subscription->get_formatted_name() ) ) . '</option>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</select>
|
</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>
|
</p>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
|
|
@ -94,30 +245,145 @@ class WC_Subscription_Downloads_Products {
|
||||||
?>
|
?>
|
||||||
<tr class="show_if_variation_downloadable">
|
<tr class="show_if_variation_downloadable">
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<p class="form-field form-row form-row-full">
|
<p class="form-field _subscription_downloads_field form-row form-row-full hide_if_variable-subscription">
|
||||||
<label><?php esc_html_e( 'Included subscription products', 'woocommerce-subscriptions' ); ?>:</label>
|
<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' ) ) ); ?>
|
<?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
|
<?php
|
||||||
$subscriptions_ids = WC_Subscription_Downloads::get_subscriptions( $variation->ID );
|
$subscriptions_ids = WC_Subscription_Downloads::get_subscriptions( $variation->ID );
|
||||||
if ( $subscriptions_ids ) {
|
if ( $subscriptions_ids ) {
|
||||||
foreach ( $subscriptions_ids as $subscription_id ) {
|
foreach ( $subscriptions_ids as $subscription_id ) {
|
||||||
$_subscription = wc_get_product( $subscription_id );
|
$subscription = wc_get_product( $subscription_id );
|
||||||
|
|
||||||
if ( $_subscription ) {
|
if ( $subscription ) {
|
||||||
echo '<option value="' . esc_attr( $subscription_id ) . '" selected="selected">' . esc_html( wp_strip_all_tags( $_subscription->get_formatted_name() ) ) . '</option>';
|
echo '<option value="' . esc_attr( $subscription_id ) . '" selected="selected">' . esc_html( wp_strip_all_tags( $subscription->get_formatted_name() ) ) . '</option>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</select>
|
</select>
|
||||||
|
<?php wp_nonce_field( self::EDITOR_UPDATE, self::RELATIONSHIP_VAR_DOWNLOAD_TO_SUB . $variation->ID, false ); ?>
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php
|
<?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.
|
* Search orders from subscription product ID.
|
||||||
*
|
*
|
||||||
|
|
@ -164,9 +430,7 @@ class WC_Subscription_Downloads_Products {
|
||||||
$orders[] = $order->id;
|
$orders[] = $order->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
$orders = apply_filters( 'woocommerce_subscription_downloads_get_orders', $orders, $subscription_product_id );
|
return apply_filters( 'woocommerce_subscription_downloads_get_orders', $orders, $subscription_product_id );
|
||||||
|
|
||||||
return $orders;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -181,37 +445,93 @@ class WC_Subscription_Downloads_Products {
|
||||||
protected function revoke_access_to_download( $download_id, $product_id, $order_id ) {
|
protected function revoke_access_to_download( $download_id, $product_id, $order_id ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$wpdb->query( $wpdb->prepare( "
|
$wpdb->query(
|
||||||
DELETE FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions
|
$wpdb->prepare(
|
||||||
WHERE order_id = %d AND product_id = %d AND download_id = %s;
|
"
|
||||||
", $order_id, $product_id, $download_id ) );
|
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 );
|
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 int $product_id The downloadable product ID.
|
||||||
* @param array $subscriptions
|
* @param array $subscriptions Subscription product IDs.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
protected function update_subscription_downloads( $product_id, $subscriptions ) {
|
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;
|
global $wpdb;
|
||||||
|
|
||||||
$subscriptions = (array) $subscriptions;
|
foreach ( $product_ids as $product_id ) {
|
||||||
$current = WC_Subscription_Downloads::get_subscriptions( $product_id );
|
$product_id = (int) $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->delete(
|
||||||
$wpdb->prefix . 'woocommerce_subscription_downloads',
|
$wpdb->prefix . 'woocommerce_subscription_downloads',
|
||||||
array(
|
array(
|
||||||
'product_id' => $product_id,
|
'product_id' => $product_id,
|
||||||
'subscription_id' => $delete,
|
'subscription_id' => $subscription_id,
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'%d',
|
'%d',
|
||||||
|
|
@ -219,10 +539,10 @@ class WC_Subscription_Downloads_Products {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
$_orders = $this->get_orders( $delete );
|
$orders = $this->get_orders( $subscription_id );
|
||||||
foreach ( $_orders as $order_id ) {
|
foreach ( $orders as $order_id ) {
|
||||||
$_product = wc_get_product( $product_id );
|
$product = wc_get_product( $product_id );
|
||||||
$downloads = $_product->get_downloads();
|
$downloads = $product->get_downloads();
|
||||||
|
|
||||||
// Adds the downloadable files to the order/subscription.
|
// Adds the downloadable files to the order/subscription.
|
||||||
foreach ( array_keys( $downloads ) as $download_id ) {
|
foreach ( array_keys( $downloads ) as $download_id ) {
|
||||||
|
|
@ -231,16 +551,35 @@ class WC_Subscription_Downloads_Products {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add items.
|
/**
|
||||||
$add_ids = array_diff( $subscriptions, $current );
|
* Revoke download permissions for a product across all related subscriptions.
|
||||||
if ( $add_ids ) {
|
*
|
||||||
foreach ( $add_ids as $add ) {
|
* @param int $product_id Product ID.
|
||||||
$wpdb->insert(
|
*
|
||||||
$wpdb->prefix . 'woocommerce_subscription_downloads',
|
* @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(
|
array(
|
||||||
'product_id' => $product_id,
|
'order_id' => $order_id,
|
||||||
'subscription_id' => $add,
|
'product_id' => $product_id,
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'%d',
|
'%d',
|
||||||
|
|
@ -248,23 +587,105 @@ class WC_Subscription_Downloads_Products {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
$_orders = $this->get_orders( $add );
|
do_action( 'woocommerce_revoke_access_to_product_download', $product_id, $order_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
|
* Grant download permissions for a product across all related subscriptions.
|
||||||
// subscription for the same user, causing duplicates
|
*
|
||||||
// to show up
|
* @param int $product_id Product ID.
|
||||||
continue;
|
*
|
||||||
}
|
* @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 );
|
if ( empty( $subscription_product_ids ) || ! $product ) {
|
||||||
$downloads = $_product->get_downloads();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Adds the downloadable files to the order/subscription.
|
$downloads = $product->get_downloads();
|
||||||
foreach ( array_keys( $downloads ) as $download_id ) {
|
|
||||||
wc_downloadable_file_permission( $download_id, $product_id, $order );
|
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 );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -289,26 +710,6 @@ class WC_Subscription_Downloads_Products {
|
||||||
$this->update_subscription_downloads( $product_id, $subscription_downloads_ids );
|
$this->update_subscription_downloads( $product_id, $subscription_downloads_ids );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
||||||
$subscription_download_ids = isset( $_POST['_variable_subscription_downloads_ids'][ $index ] ) ? wc_clean( wp_unslash( $_POST['_variable_subscription_downloads_ids'][ $index ] ) ) : array();
|
|
||||||
$subscription_download_ids = array_filter( $subscription_download_ids ); // nosemgrep: audit.php.lang.misc.array-filter-no-callback -- $subscription_download_ids are already passed through wc_clean() and wp_unslash().
|
|
||||||
|
|
||||||
$this->update_subscription_downloads( $variation_id, $subscription_download_ids );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save subscriptions information when duplicating a product.
|
* Save subscriptions information when duplicating a product.
|
||||||
*
|
*
|
||||||
|
|
@ -370,4 +771,45 @@ class WC_Subscription_Downloads_Products {
|
||||||
|
|
||||||
return (string) wc_get_formatted_variation( $product_variation, true );
|
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' );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,8 +60,8 @@ class WC_Subscription_Downloads_Settings {
|
||||||
'id' => WC_Subscriptions_Admin::$option_prefix . '_downloads_settings',
|
'id' => WC_Subscriptions_Admin::$option_prefix . '_downloads_settings',
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'name' => __( 'Enable product linking to subscriptions', 'woocommerce-subscriptions' ),
|
'name' => __( 'Enable downloadable file sharing', 'woocommerce-subscriptions' ),
|
||||||
'desc' => __( 'Allow simple and variable downloadable products to be included with subscription products.', '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',
|
'id' => WC_Subscriptions_Admin::$option_prefix . '_enable_downloadable_file_linking',
|
||||||
'default' => 'no',
|
'default' => 'no',
|
||||||
'type' => 'checkbox',
|
'type' => 'checkbox',
|
||||||
|
|
|
||||||
|
|
@ -33,32 +33,8 @@ class WCSG_Admin_Welcome_Announcement {
|
||||||
|
|
||||||
$screen = get_current_screen();
|
$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(
|
wp_localize_script(
|
||||||
'wcs-gifting-welcome-announcement',
|
'wcs-admin',
|
||||||
'wcsGiftingSettings',
|
'wcsGiftingSettings',
|
||||||
array(
|
array(
|
||||||
'imagesPath' => plugins_url( '/assets/images', WC_Subscriptions::$plugin_file ),
|
'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,
|
'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'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
<?php
|
<?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)
|
* 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 the "Switch" button to the View Subscription table
|
||||||
add_action( 'woocommerce_order_item_meta_end', array( __CLASS__, 'print_switch_link' ), 10, 3 );
|
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
|
// 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 );
|
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() {
|
public static function subscription_switch_handler() {
|
||||||
global $post;
|
global $post;
|
||||||
|
|
||||||
// If the current user doesn't own the subscription, remove the query arg from the URL
|
$switch_subscription_id = Request::get_var( 'switch-subscription' );
|
||||||
if ( isset( $_GET['switch-subscription'] ) && isset( $_GET['item'] ) ) {
|
$item_id = Request::get_var( 'item' );
|
||||||
|
|
||||||
$subscription = wcs_get_subscription( absint( $_GET['switch-subscription'] ) );
|
// If the current user doesn't own the subscription, remove the query arg from the URL
|
||||||
$line_item = $subscription ? wcs_get_order_item( absint( $_GET['item'] ), $subscription ) : false;
|
if ( $switch_subscription_id && $item_id ) {
|
||||||
$nonce = ! empty( $_GET['_wcsnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wcsnonce'] ) ) : false;
|
|
||||||
|
$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
|
// 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 ) ) {
|
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' ) ) );
|
Request::redirect( remove_query_arg( array( 'switch-subscription', 'auto-switch', 'item', '_wcsnonce' ) ) );
|
||||||
exit();
|
return;
|
||||||
|
|
||||||
} else {
|
} 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' );
|
$switch_message = __( 'You have a subscription to this product. Choosing a new subscription will replace your existing subscription.', 'woocommerce-subscriptions' );
|
||||||
} else {
|
} else {
|
||||||
$switch_message = __( 'Choose a new subscription.', 'woocommerce-subscriptions' );
|
$switch_message = __( 'Choose a new subscription.', 'woocommerce-subscriptions' );
|
||||||
|
|
@ -239,8 +249,8 @@ class WC_Subscriptions_Switcher {
|
||||||
if ( $removed_item_count > 0 ) {
|
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' );
|
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() );
|
Request::redirect( wc_get_cart_url() );
|
||||||
exit();
|
return;
|
||||||
}
|
}
|
||||||
} elseif ( is_product() && $product = wc_get_product( $post ) ) { // Automatically initiate the switch process for limited variable subscriptions
|
} 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 ) ) {
|
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 ) ) );
|
Request::redirect( add_query_arg( 'auto-switch', 'true', self::get_switch_url( $item_id, $item, $subscription ) ) );
|
||||||
exit;
|
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
|
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.
|
* The link for switching a subscription - the product page for variable subscriptions, or grouped product page for grouped subscriptions.
|
||||||
*
|
*
|
||||||
|
|
@ -1382,21 +1416,25 @@ class WC_Subscriptions_Switcher {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if ( ! isset( $_GET['switch-subscription'] ) ) {
|
$switch_subscription_id = Request::get_var( 'switch-subscription' );
|
||||||
|
|
||||||
|
if ( ! $switch_subscription_id ) {
|
||||||
return $is_valid;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$subscription = wcs_get_subscription( absint( $_GET['switch-subscription'] ) );
|
$subscription = wcs_get_subscription( absint( $switch_subscription_id ) );
|
||||||
|
|
||||||
if ( ! $subscription ) {
|
if ( ! $subscription ) {
|
||||||
throw new Exception( __( 'The subscription may have been deleted.', 'woocommerce-subscriptions' ) );
|
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 );
|
$item = wcs_get_order_item( $item_id, $subscription );
|
||||||
|
|
||||||
// Prevent switching to non-subscription product
|
// Prevent switching to non-subscription product
|
||||||
|
|
@ -1468,11 +1506,13 @@ class WC_Subscriptions_Switcher {
|
||||||
public static function set_switch_details_in_cart( $cart_item_data, $product_id, $variation_id ) {
|
public static function set_switch_details_in_cart( $cart_item_data, $product_id, $variation_id ) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ( ! isset( $_GET['switch-subscription'] ) ) {
|
$switch_subscription_id = Request::get_var( 'switch-subscription' );
|
||||||
|
|
||||||
|
if ( ! $switch_subscription_id ) {
|
||||||
return $cart_item_data;
|
return $cart_item_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
$subscription = wcs_get_subscription( absint( $_GET['switch-subscription'] ) );
|
$subscription = wcs_get_subscription( absint( $switch_subscription_id ) );
|
||||||
|
|
||||||
if ( ! $subscription ) {
|
if ( ! $subscription ) {
|
||||||
throw new Exception( __( 'The subscription may have been deleted.', 'woocommerce-subscriptions' ) );
|
throw new Exception( __( 'The subscription may have been deleted.', 'woocommerce-subscriptions' ) );
|
||||||
|
|
@ -1482,11 +1522,11 @@ class WC_Subscriptions_Switcher {
|
||||||
if ( ! current_user_can( 'switch_shop_subscription', $subscription->get_id() ) ) {
|
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_add_notice( __( 'You can not switch this subscription. It appears you do not own the subscription.', 'woocommerce-subscriptions' ), 'error' );
|
||||||
WC()->cart->empty_cart( true );
|
WC()->cart->empty_cart( true );
|
||||||
wp_safe_redirect( get_permalink( $product_id ) );
|
Request::redirect( get_permalink( $product_id ) );
|
||||||
exit();
|
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
|
// Else it's a valid switch
|
||||||
$product = wc_get_product( $item['product_id'] );
|
$product = wc_get_product( $item['product_id'] );
|
||||||
|
|
@ -1538,7 +1578,7 @@ class WC_Subscriptions_Switcher {
|
||||||
|
|
||||||
$cart_item_data['subscription_switch'] = array(
|
$cart_item_data['subscription_switch'] = array(
|
||||||
'subscription_id' => $subscription->get_id(),
|
'subscription_id' => $subscription->get_id(),
|
||||||
'item_id' => absint( $_GET['item'] ),
|
'item_id' => absint( Request::get_var( 'item' ) ),
|
||||||
'next_payment_timestamp' => $next_payment_timestamp,
|
'next_payment_timestamp' => $next_payment_timestamp,
|
||||||
'upgraded_or_downgraded' => '',
|
'upgraded_or_downgraded' => '',
|
||||||
);
|
);
|
||||||
|
|
@ -1549,8 +1589,8 @@ class WC_Subscriptions_Switcher {
|
||||||
|
|
||||||
wc_add_notice( __( 'There was an error locating the switch details.', 'woocommerce-subscriptions' ), 'error' );
|
wc_add_notice( __( 'There was an error locating the switch details.', 'woocommerce-subscriptions' ), 'error' );
|
||||||
WC()->cart->empty_cart( true );
|
WC()->cart->empty_cart( true );
|
||||||
wp_safe_redirect( get_permalink( wc_get_page_id( 'cart' ) ) );
|
Request::redirect( get_permalink( wc_get_page_id( 'cart' ) ) );
|
||||||
exit();
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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+
|
* 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
|
* @version 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.12
|
||||||
*
|
*
|
||||||
* @var int $loop
|
* @var int $loop
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
defined( 'ABSPATH' ) || exit;
|
||||||
$display_heading = true;
|
|
||||||
|
|
||||||
foreach ( WC()->cart->get_coupons() as $code => $coupon ) {
|
foreach ( WC()->cart->get_coupons() as $code => $coupon ) {
|
||||||
|
$display_heading = true;
|
||||||
foreach ( $recurring_carts as $recurring_cart_key => $recurring_cart ) {
|
foreach ( $recurring_carts as $recurring_cart_key => $recurring_cart ) {
|
||||||
foreach ( $recurring_cart->get_coupons() as $recurring_code => $recurring_coupon ) {
|
foreach ( $recurring_cart->get_coupons() as $recurring_code => $recurring_coupon ) {
|
||||||
if ( $recurring_code !== $code ) {
|
if ( $recurring_code !== $code ) {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ return array(
|
||||||
'Automattic\\WooCommerce_Subscriptions\\Internal\\Telemetry\\Orders' => $baseDir . '/src/Internal/Telemetry/Orders.php',
|
'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\\Products' => $baseDir . '/src/Internal/Telemetry/Products.php',
|
||||||
'Automattic\\WooCommerce_Subscriptions\\Internal\\Telemetry\\Subscriptions' => $baseDir . '/src/Internal/Telemetry/Subscriptions.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',
|
'Automattic\\WooCommerce_Subscriptions\\Internal\\Utilities\\Scheduled_Actions' => $baseDir . '/src/Internal/Utilities/Scheduled_Actions.php',
|
||||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||||
'Composer\\Installers\\AglInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/AglInstaller.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',
|
'WCSG_Template_Loader' => $baseDir . '/includes/gifting/class-wcsg-template-loader.php',
|
||||||
'WCS_Gifting' => $baseDir . '/includes/gifting/class-wcs-gifting.php',
|
'WCS_Gifting' => $baseDir . '/includes/gifting/class-wcs-gifting.php',
|
||||||
'WC_Subscription_Downloads' => $baseDir . '/includes/downloads/class-wc-subscription-downloads.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_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_Install' => $baseDir . '/includes/downloads/class-wc-subscription-downloads-install.php',
|
||||||
'WC_Subscription_Downloads_Order' => $baseDir . '/includes/downloads/class-wc-subscription-downloads-order.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\\Orders' => __DIR__ . '/../..' . '/src/Internal/Telemetry/Orders.php',
|
||||||
'Automattic\\WooCommerce_Subscriptions\\Internal\\Telemetry\\Products' => __DIR__ . '/../..' . '/src/Internal/Telemetry/Products.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\\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',
|
'Automattic\\WooCommerce_Subscriptions\\Internal\\Utilities\\Scheduled_Actions' => __DIR__ . '/../..' . '/src/Internal/Utilities/Scheduled_Actions.php',
|
||||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||||
'Composer\\Installers\\AglInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/AglInstaller.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',
|
'WCSG_Template_Loader' => __DIR__ . '/../..' . '/includes/gifting/class-wcsg-template-loader.php',
|
||||||
'WCS_Gifting' => __DIR__ . '/../..' . '/includes/gifting/class-wcs-gifting.php',
|
'WCS_Gifting' => __DIR__ . '/../..' . '/includes/gifting/class-wcs-gifting.php',
|
||||||
'WC_Subscription_Downloads' => __DIR__ . '/../..' . '/includes/downloads/class-wc-subscription-downloads.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_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_Install' => __DIR__ . '/../..' . '/includes/downloads/class-wc-subscription-downloads-install.php',
|
||||||
'WC_Subscription_Downloads_Order' => __DIR__ . '/../..' . '/includes/downloads/class-wc-subscription-downloads-order.php',
|
'WC_Subscription_Downloads_Order' => __DIR__ . '/../..' . '/includes/downloads/class-wc-subscription-downloads-order.php',
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<?php return array(
|
<?php return array(
|
||||||
'root' => array(
|
'root' => array(
|
||||||
'name' => 'woocommerce/woocommerce-subscriptions',
|
'name' => 'woocommerce/woocommerce-subscriptions',
|
||||||
'pretty_version' => 'dev-release/8.2.0',
|
'pretty_version' => 'dev-release/8.3.0',
|
||||||
'version' => 'dev-release/8.2.0',
|
'version' => 'dev-release/8.3.0',
|
||||||
'reference' => '649a58503cd3715ecc5d37281a2cbb62fe5ad320',
|
'reference' => '38b39c285d0bae952d5746ba460ae914e4ac0f60',
|
||||||
'type' => 'wordpress-plugin',
|
'type' => 'wordpress-plugin',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
|
|
@ -29,9 +29,9 @@
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
'woocommerce/woocommerce-subscriptions' => array(
|
'woocommerce/woocommerce-subscriptions' => array(
|
||||||
'pretty_version' => 'dev-release/8.2.0',
|
'pretty_version' => 'dev-release/8.3.0',
|
||||||
'version' => 'dev-release/8.2.0',
|
'version' => 'dev-release/8.3.0',
|
||||||
'reference' => '649a58503cd3715ecc5d37281a2cbb62fe5ad320',
|
'reference' => '38b39c285d0bae952d5746ba460ae914e4ac0f60',
|
||||||
'type' => 'wordpress-plugin',
|
'type' => 'wordpress-plugin',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@
|
||||||
* Description: Sell products and services with recurring payments in your WooCommerce Store.
|
* Description: Sell products and services with recurring payments in your WooCommerce Store.
|
||||||
* Author: WooCommerce
|
* Author: WooCommerce
|
||||||
* Author URI: https://woocommerce.com/
|
* Author URI: https://woocommerce.com/
|
||||||
* Version: 8.2.0
|
* Version: 8.3.0
|
||||||
* Requires Plugins: woocommerce
|
* Requires Plugins: woocommerce
|
||||||
*
|
*
|
||||||
* WC requires at least: 10.3.0
|
* WC requires at least: 10.3.0
|
||||||
* WC tested up to: 10.4.0
|
* WC tested up to: 10.4.3
|
||||||
* Requires PHP: 7.4
|
* Requires PHP: 7.4
|
||||||
*
|
*
|
||||||
* License: GNU General Public License v3.0
|
* License: GNU General Public License v3.0
|
||||||
|
|
@ -84,7 +84,7 @@ class WC_Subscriptions {
|
||||||
public static $plugin_file = __FILE__;
|
public static $plugin_file = __FILE__;
|
||||||
|
|
||||||
/** @var string */
|
/** @var string */
|
||||||
public static $version = '8.2.0'; // WRCS: DEFINED_VERSION.
|
public static $version = '8.3.0'; // WRCS: DEFINED_VERSION.
|
||||||
|
|
||||||
/** @var string */
|
/** @var string */
|
||||||
public static $wc_minimum_supported_version = '7.7';
|
public static $wc_minimum_supported_version = '7.7';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue