Updates to 7.5.0

This commit is contained in:
WooCommerce
2025-05-21 10:18:23 +00:00
parent c2175bfc37
commit 924e4d86bd
389 changed files with 7446 additions and 22486 deletions

1
.nvmrc
View File

@@ -1 +0,0 @@
20.11

View File

@@ -1,19 +0,0 @@
{
"plugins": [
"https://downloads.wordpress.org/plugin/woocommerce.zip",
"https://downloads.wordpress.org/plugin/email-log.zip",
"https://github.com/woocommerce/woocommerce-gateway-dummy/releases/download/1.0.9/woocommerce-gateway-dummy.zip",
".",
"./tests/e2e/test-configuration-plugin"
],
"themes": [
"https://downloads.wordpress.org/theme/storefront.zip"
],
"env": {
"tests": {
"mappings": {
"wp-cli.yml": "./tests/e2e/bin/wp-cli.yml"
}
}
}
}

View File

@@ -90,3 +90,46 @@
font-size: 1.4em;
text-align: center;
}
.woocommerce-subscriptions-related-orders-pagination-links.woocommerce-pagination {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-bottom: 1em;
width: 100%;
}
.woocommerce-subscriptions-related-orders-pagination-links .pagination-links a {
display: inline-block;
}
.woocommerce-subscriptions-related-orders-pagination-links .pagination-links a.disabled:hover {
cursor: default;
text-decoration: none;
}
.woocommerce-subscriptions-related-orders-pagination-links .pagination-links a .symbol {
display: none;
}
.rtl .woocommerce-subscriptions-related-orders-pagination-links {
flex-direction: row-reverse;
}
.rtl .woocommerce-subscriptions-related-orders-pagination-links .pagination-links {
direction: rtl;
}
@media ( max-width: 30em ) {
.woocommerce-subscriptions-related-orders-pagination-links .pagination-links a {
padding: 0.5em 1em;
}
.woocommerce-subscriptions-related-orders-pagination-links .pagination-links a .label {
display: none;
}
.woocommerce-subscriptions-related-orders-pagination-links .pagination-links a .symbol {
display: inherit;
}
}

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

Before

Width:  |  Height:  |  Size: 885 B

After

Width:  |  Height:  |  Size: 885 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

View File

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

View File

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

View File

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

View File

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 172 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

View File

Before

Width:  |  Height:  |  Size: 250 KiB

After

Width:  |  Height:  |  Size: 250 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,134 @@
/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { registerCheckoutFilters } from '@woocommerce/blocks-checkout';
import { getSetting } from '@woocommerce/settings';
/**
* Internal dependencies
*/
import {
getSwitchString,
isOneOffSubscription,
getBillingFrequencyString,
} from '../utils';
/**
* This is the filter integration API, it uses registerCheckoutFilters
* to register its filters, each filter is a key: function pair.
* The key the filter name, and the function is the filter.
*
* Each filter function is passed the previous (or default) value in that filter
* as the first parameter, the second parameter is a object of 3PD registered data.
* For WCS, we register out data with key `subscriptions`.
* Filters must return the previous value or a new value with the same type.
* If an error is thrown, it would be visible for store managers only.
*/
export const registerFilters = () => {
registerCheckoutFilters( 'woocommerce-subscriptions', {
// subscriptions data here comes from register_endpoint_data /cart registration.
totalLabel: ( label, { subscriptions } ) => {
if ( 0 < subscriptions?.length ) {
return __( 'Total due today', 'woocommerce-subscriptions' );
}
return label;
},
// subscriptions data here comes from register_endpoint_data /cart/items registration.
subtotalPriceFormat: ( label, { subscriptions } ) => {
if (
subscriptions?.billing_period &&
subscriptions?.billing_interval
) {
const {
billing_interval: billingInterval,
subscription_length: subscriptionLength,
} = subscriptions;
// We check if we have a length and its equal or less to the billing interval.
// When this is true, it means we don't have a next payment date.
if (
isOneOffSubscription( {
subscriptionLength,
billingInterval,
} )
) {
// An edge case when length is 1 so it doesn't have a length prefix
if ( 1 === subscriptionLength ) {
return getBillingFrequencyString(
subscriptions,
// translators: the word used to describe billing frequency, e.g. "for" 1 day or "for" 1 month.
__( 'for 1', 'woocommerce-subscriptions' ),
label
);
}
return getBillingFrequencyString(
subscriptions,
// translators: the word used to describe billing frequency, e.g. "for" 6 days or "for" 2 weeks.
__( 'for', 'woocommerce-subscriptions' ),
label
);
}
return getBillingFrequencyString(
subscriptions,
// translators: the word used to describe billing frequency, e.g. "every" 6 days or "every" 2 weeks.
__( 'every', 'woocommerce-subscriptions' ),
label
);
}
return label;
},
saleBadgePriceFormat: ( label, { subscriptions } ) => {
if (
subscriptions?.billing_period &&
subscriptions?.billing_interval
) {
return getBillingFrequencyString( subscriptions, '/', label );
}
return label;
},
itemName: ( name, { subscriptions } ) => {
if ( subscriptions?.is_resubscribe ) {
return sprintf(
// translators: %s Product name.
__( '%s (resubscription)', 'woocommerce-subscriptions' ),
name
);
}
if ( subscriptions?.switch_type ) {
return sprintf(
// translators: %1$s Product name, %2$s Switch type (upgraded, downgraded, or crossgraded).
__( '%1$s (%2$s)', 'woocommerce-subscriptions' ),
name,
getSwitchString( subscriptions.switch_type )
);
}
return name;
},
cartItemPrice: ( pricePlaceholder, { subscriptions }, { context } ) => {
if ( subscriptions?.sign_up_fees ) {
return 'cart' === context
? sprintf(
/* translators: %s is the subscription price to pay immediately (ie: $10). */
__( 'Due today %s', 'woocommerce-subscriptions' ),
pricePlaceholder
)
: sprintf(
/* translators: %s is the subscription price to pay immediately (ie: $10). */
__( '%s due today', 'woocommerce-subscriptions' ),
pricePlaceholder
);
}
return pricePlaceholder;
},
placeOrderButtonLabel: ( label ) => {
const subscriptionsData = getSetting( 'subscriptions_data' );
if ( subscriptionsData?.place_order_override ) {
return subscriptionsData?.place_order_override;
}
return label;
},
} );
};

49
assets/src/js/index.js Normal file
View File

@@ -0,0 +1,49 @@
/**
* External dependencies
*/
import { registerPlugin } from '@wordpress/plugins';
import {
ExperimentalOrderMeta,
ExperimentalOrderShippingPackages,
} from '@woocommerce/blocks-checkout';
/**
* Internal dependencies
*/
import { SubscriptionsRecurringTotals } from './recurring-totals';
import { SubscriptionsRecurringPackages } from './recurring-packages';
import { registerFilters } from './filters';
import './index.scss';
/**
* This is the first integration point between WooCommerce Subscriptions
* and Cart and Checkout blocks, it happens on two folds:
* - First, we register our code via `registerPlugin`, this React code
* is then going to be rendered hidden inside Cart and Checkout blocks
* (via <PluginArea /> component).
* - Second, we're using SlotFills[1] to move that code to where we want it
* inside the tree.
*/
const render = () => {
return (
<>
<ExperimentalOrderShippingPackages>
<SubscriptionsRecurringPackages />
</ExperimentalOrderShippingPackages>
<ExperimentalOrderMeta>
<SubscriptionsRecurringTotals />
</ExperimentalOrderMeta>
</>
);
};
registerPlugin( 'woocommerce-subscriptions', {
render,
scope: 'woocommerce-checkout',
} );
/**
* RegisterFilters is the second part of the integration, and it handles filters
* like price, totals, and so on.
*/
registerFilters();

1
assets/src/js/index.scss Normal file
View File

@@ -0,0 +1 @@
// Add styles here.

View File

@@ -0,0 +1,66 @@
/**
* External dependencies
*/
import { useMemo } from '@wordpress/element';
/**
* This component is responsible for rending recurring shippings.
* It has to be the highest level item directly inside the SlotFill
* to receive properties passed from Cart and Checkout.
*
* extensions is data registered into `/cart` endpoint.
*
* @param {Object} props Passed props from SlotFill to this component.
* @param {Object} props.extensions Data registered into `/cart` endpoint.
* @param {boolean} props.collapsible If shipping rates can collapse.
* @param {boolean} props.collapse If shipping rates should collapse.
* @param {boolean} props.showItems If shipping rates should show items inside them.
* @param {Element} props.noResultsMessage Message shown when no rate are found.
* @param {Function} props.renderOption Function that decides how rates are going to render.
* @param {Object} props.components
* @param {string} props.context This will be woocommerce/cart or woocommerce/checkout.
*/
export const SubscriptionsRecurringPackages = ( {
extensions,
collapsible,
collapse,
showItems,
noResultsMessage,
renderOption,
components,
context,
} ) => {
const { subscriptions = [] } = extensions;
const { ShippingRatesControlPackage } = components;
// Flatten all packages from recurring carts.
const packages = useMemo(
() =>
Object.values( subscriptions )
.map( ( recurringCart ) => recurringCart.shipping_rates )
.filter( Boolean )
.flat(),
[ subscriptions ]
);
const shouldCollapse = useMemo( () => 1 < packages.length || collapse, [
packages.length,
collapse,
] );
const shouldShowItems = useMemo( () => 1 < packages.length || showItems, [
packages.length,
showItems,
] );
return packages.map( ( { package_id: packageId, ...packageData } ) => (
<ShippingRatesControlPackage
key={ packageId }
packageId={ packageId }
packageData={ packageData }
collapsible={ collapsible }
collapse={ shouldCollapse }
showItems={ shouldShowItems }
noResultsMessage={ noResultsMessage }
renderOption={ renderOption }
highlightChecked={ 'woocommerce/checkout' === context }
/>
) );
};

View File

@@ -0,0 +1,319 @@
/**
* External dependencies
*/
import { sprintf, __ } from '@wordpress/i18n';
import {
Panel,
Subtotal,
TotalsItem,
TotalsTaxes,
TotalsWrapper,
} from '@woocommerce/blocks-checkout';
import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
import { isWcVersion, getSetting } from '@woocommerce/settings';
/**
* Internal dependencies
*/
import {
getRecurringPeriodString,
getSubscriptionLengthString,
isOneOffSubscription,
} from '../utils';
import './index.scss';
/**
* All data passed in get_script_data is available here, from all
* plugins (e.g WooCommerce Admin, WooCommerce Blocks).
*/
const DISPLAY_CART_PRICES_INCLUDING_TAX = getSetting(
'displayCartPricesIncludingTax',
false
);
/**
* Component responsible for rending the coupons discount totals item.
*
* @param {Object} props Props passed to component.
* @param {Object} props.currency Object containing currency data to format prices.
* @param {Object} props.values Recurring cart totals (shipping, taxes).
*/
const DiscountTotals = ( { currency, values } ) => {
const {
total_discount: totalDiscount,
total_discount_tax: totalDiscountTax,
} = values;
const discountValue = parseInt( totalDiscount, 10 );
if ( ! discountValue ) {
return null;
}
const discountTaxValue = parseInt( totalDiscountTax, 10 );
const discountTotalValue = DISPLAY_CART_PRICES_INCLUDING_TAX
? discountValue + discountTaxValue
: discountValue;
return (
<TotalsItem
className="wc-block-components-totals-discount"
currency={ currency }
label={ __( 'Discount', 'woocommerce-subscriptions' ) }
value={ discountTotalValue * -1 }
/>
);
};
/**
* Component responsible for rending the shipping totals item.
*
* @param {Object} props Props passed to component.
* @param {string|undefined} props.selectedRate Selected shipping method
* name.
* @param {boolean} props.needsShipping Boolean to indicate if we
* need shipping or not.
* @param {boolean} props.calculatedShipping Boolean to indicate if we
* calculated shipping or not.
* @param {Object} props.currency Object containing
* currency data to format prices.
* @param {Object} props.values Recurring cart totals (shipping, taxes).
*/
const ShippingTotal = ( {
values,
currency,
selectedRate,
needsShipping,
calculatedShipping,
} ) => {
if ( ! needsShipping || ! calculatedShipping ) {
return null;
}
const shippingTotals = DISPLAY_CART_PRICES_INCLUDING_TAX
? parseInt( values.total_shipping, 10 ) +
parseInt( values.total_shipping_tax, 10 )
: parseInt( values.total_shipping, 10 );
const valueToShow =
0 === shippingTotals && isWcVersion( '9.0', '>=' ) ? (
<strong>{ __( 'Free', 'woocommerce-subscriptions' ) }</strong>
) : (
shippingTotals
);
return (
<TotalsItem
value={ valueToShow }
label={ __( 'Shipping', 'woocommerce-subscriptions' ) }
currency={ currency }
description={
!! selectedRate &&
sprintf(
// translators: %s selected shipping rate (ex: flat rate)
__( 'via %s', 'woocommerce-subscriptions' ),
selectedRate
)
}
/>
);
};
/**
* Component responsible for rendering recurring cart description.
*
* @param {Object} props Props passed to component.
* @param {string} props.nextPaymentDate Formatted next payment date.
* @param {number} props.subscriptionLength Subscription length.
* @param {string} props.billingPeriod Recurring cart period (day, week, month, year).
* @param {number} props.billingInterval Recurring cart interval (1 - 6).
*/
const SubscriptionDescription = ( {
nextPaymentDate,
subscriptionLength,
billingPeriod,
billingInterval,
} ) => {
const subscriptionLengthString = getSubscriptionLengthString( {
subscriptionLength,
billingPeriod,
} );
const firstPaymentString = isOneOffSubscription( {
subscriptionLength,
billingInterval,
} )
? sprintf(
/* Translators: %1$s is a date. */
__( 'Due: %1$s', 'woocommerce-subscriptions' ),
nextPaymentDate
)
: sprintf(
/* Translators: %1$s is a date. */
__( 'Starting: %1$s', 'woocommerce-subscriptions' ),
nextPaymentDate
);
return (
// Only render this section if we have a next payment date.
<span>
{ !! nextPaymentDate && firstPaymentString }{ ' ' }
{ !! subscriptionLength &&
subscriptionLength >= billingInterval && (
<span className="wcs-recurring-totals__subscription-length">
{ subscriptionLengthString }
</span>
) }
</span>
);
};
/**
* Component responsible for rendering recurring cart heading.
*
* @param {Object} props Props passed to component.
* @param {Object} props.currency Object containing currency data to format prices.
* @param {number} props.billingInterval Recurring cart interval (1 - 6).
* @param {string} props.billingPeriod Recurring cart period (day, week, month, year).
* @param {string} props.nextPaymentDate Formatted next payment date.
* @param {number} props.subscriptionLength Subscription length.
* @param {Object} props.totals Recurring cart totals (shipping, taxes).
*/
const TabHeading = ( {
currency,
billingInterval,
billingPeriod,
nextPaymentDate,
subscriptionLength,
totals,
} ) => {
// For future one off subscriptions, we show "Total" instead of a recurring title.
const title = isOneOffSubscription( {
billingInterval,
subscriptionLength,
} )
? __( 'Total', 'woocommerce-subscriptions' )
: getRecurringPeriodString( {
billingInterval,
billingPeriod,
} );
return (
<TotalsItem
className="wcs-recurring-totals-panel__title"
currency={ currency }
label={ title }
value={ totals }
description={
<SubscriptionDescription
nextPaymentDate={ nextPaymentDate }
subscriptionLength={ subscriptionLength }
billingInterval={ billingInterval }
billingPeriod={ billingPeriod }
/>
}
/>
);
};
/**
* Component responsible for rendering a single recurring total panel.
* We render several ones depending on how many recurring carts we have.
*
* @param {Object} props Props passed to component.
* @param {Object} props.subscription Recurring cart data that we registered
* with ExtendRestApi.
* @param {boolean} props.needsShipping Boolean to indicate if we need
* shipping or not.
* @param {boolean} props.calculatedShipping Boolean to indicate if we calculated
* shipping or not.
*/
const RecurringSubscription = ( {
subscription,
needsShipping,
calculatedShipping,
} ) => {
const {
totals,
billing_interval: billingInterval,
billing_period: billingPeriod,
next_payment_date: nextPaymentDate,
subscription_length: subscriptionLength,
shipping_rates: shippingRates,
} = subscription;
// We skip one off subscriptions
if ( ! nextPaymentDate ) {
return null;
}
const selectedRate = shippingRates?.[ 0 ]?.shipping_rates?.find(
( { selected } ) => selected
)?.name;
const currency = getCurrencyFromPriceResponse( totals );
return (
<div className="wcs-recurring-totals-panel">
<TabHeading
billingInterval={ billingInterval }
billingPeriod={ billingPeriod }
nextPaymentDate={ nextPaymentDate }
subscriptionLength={ subscriptionLength }
totals={ parseInt( totals.total_price, 10 ) }
currency={ currency }
/>
<Panel
className="wcs-recurring-totals-panel__details"
initialOpen={ false }
title={ __( 'Details', 'woocommerce-subscriptions' ) }
>
<TotalsWrapper>
<Subtotal currency={ currency } values={ totals } />
<DiscountTotals currency={ currency } values={ totals } />
</TotalsWrapper>
<TotalsWrapper className="wc-block-components-totals-shipping">
<ShippingTotal
currency={ currency }
needsShipping={ needsShipping }
calculatedShipping={ calculatedShipping }
values={ totals }
selectedRate={ selectedRate }
/>
</TotalsWrapper>
{ ! DISPLAY_CART_PRICES_INCLUDING_TAX && (
<TotalsWrapper>
<TotalsTaxes currency={ currency } values={ totals } />
</TotalsWrapper>
) }
<TotalsWrapper>
<TotalsItem
className="wcs-recurring-totals-panel__details-total"
currency={ currency }
label={ __( 'Total', 'woocommerce-subscriptions' ) }
value={ parseInt( totals.total_price, 10 ) }
/>
</TotalsWrapper>
</Panel>
</div>
);
};
/**
* This component is responsible for rending recurring totals.
* It has to be the highest level item directly inside the SlotFill
* to receive properties passed from Cart and Checkout.
*
* extensions is data registered into `/cart` endpoint.
*
* @param {Object} props Passed props from SlotFill to this component.
* @param {Object} props.extensions data registered into `/cart` endpoint.
* @param {Object} props.cart cart endpoint data in readonly mode.
*/
export const SubscriptionsRecurringTotals = ( { extensions, cart } ) => {
const { subscriptions } = extensions;
const { cartNeedsShipping, cartHasCalculatedShipping } = cart;
if ( ! subscriptions || 0 === subscriptions.length ) {
return null;
}
return subscriptions.map( ( { key, ...subscription } ) => (
<RecurringSubscription
subscription={ subscription }
needsShipping={ cartNeedsShipping }
calculatedShipping={ cartHasCalculatedShipping }
key={ key }
/>
) );
};

View File

@@ -0,0 +1,72 @@
// Shows a border with the current color and a custom opacity. That can't be achieved
// with normal border because `currentColor` doesn't allow tweaking the opacity, and
// setting the opacity of the entire element would change the children's opacity too.
@mixin with-translucent-border( $border-width: 1px, $opacity: 0.3 ) {
position: relative;
&::after {
border-style: solid;
border-width: $border-width;
bottom: 0;
content: '';
display: block;
left: 0;
opacity: $opacity;
pointer-events: none;
position: absolute;
right: 0;
top: 0;
}
}
.wcs-recurring-totals-panel {
@include with-translucent-border( 1px 0 );
padding: 1em 0 0;
+ .wcs-recurring-totals-panel::after {
border-top-width: 0;
}
.wc-block-components-panel .wc-block-components-totals-item {
padding-left: 0;
padding-right: 0;
}
.wc-block-components-totals-item__label::first-letter {
text-transform: capitalize;
}
.wcs-recurring-totals-panel__title .wc-block-components-totals-item__label {
font-weight: 700;
}
}
.wcs-recurring-totals-panel__title {
margin: 0;
}
.wcs-recurring-totals-panel__details {
.wc-block-components-panel__button,
.wc-block-components-panel__button:hover,
.wc-block-components-panel__button:focus {
font-size: 0.875em;
}
.wc-block-components-panel__content > .wc-block-components-totals-item {
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
}
.wcs-recurring-totals-panel__details-total
.wc-block-components-totals-item__label {
font-weight: 700;
}
}
.wcs-recurring-totals__subscription-length {
float: right;
}

View File

@@ -0,0 +1,207 @@
/**
* External dependencies
*/
import { sprintf, __, _nx } from '@wordpress/i18n';
export function getAvailablePeriods( number ) {
return {
day: _nx(
'day',
'days',
number,
'Used in recurring totals section in Cart. 2+ will need plural, 1 will need singular.',
'woocommerce-subscriptions'
),
week: _nx(
'week',
'weeks',
number,
'Used in recurring totals section in Cart. 2+ will need plural, 1 will need singular.',
'woocommerce-subscriptions'
),
month: _nx(
'month',
'months',
number,
'Used in recurring totals section in Cart. 2+ will need plural, 1 will need singular.',
'woocommerce-subscriptions'
),
year: _nx(
'year',
'years',
number,
'Used in recurring totals section in Cart. 2+ will need plural, 1 will need singular.',
'woocommerce-subscriptions'
),
};
}
/**
* Creates a recurring string from a subscription
*
* Examples
* period recurring total
* Daily recurring total
* Weekly recurring total
* Monthly recurring total
* etc
* If subscription bills at non standard intervals, then the order is transposed, and the line reads:
* Recurring total every X day | week | month | quarter | year
* Recurring total every 3rd day
* Recurring total every 2nd week
* Recurring total every 4th month
* etc
*
* @param {Object} subscription Subscription object.
* @param {string} subscription.billingPeriod Period (month, day, week, year).
* @param {number} subscription.billingInterval Internal (1 month, 5 day, 4 week, 6 year).
*/
export function getRecurringPeriodString( { billingInterval, billingPeriod } ) {
switch ( billingInterval ) {
case 1:
if ( 'day' === billingPeriod ) {
return __(
'Daily recurring total',
'woocommerce-subscriptions'
);
} else if ( 'week' === billingPeriod ) {
return __(
'Weekly recurring total',
'woocommerce-subscriptions'
);
} else if ( 'month' === billingPeriod ) {
return __(
'Monthly recurring total',
'woocommerce-subscriptions'
);
} else if ( 'year' === billingPeriod ) {
return __(
'Yearly recurring total',
'woocommerce-subscriptions'
);
}
break;
case 2:
return sprintf(
/* translators: %1$s is week, month, year */
__(
'Recurring total every 2nd %1$s',
'woocommerce-subscriptions'
),
billingPeriod
);
case 3:
return sprintf(
/* Translators: %1$s is week, month, year */
__(
'Recurring total every 3rd %1$s',
'woocommerce-subscriptions'
),
billingPeriod
);
default:
return sprintf(
/* Translators: %1$d is number of weeks, months, days, years. %2$s is week, month, year */
__(
'Recurring total every %1$dth %2$s',
'woocommerce-subscriptions'
),
billingInterval,
billingPeriod
);
}
}
export function getSubscriptionLengthString( {
subscriptionLength,
billingPeriod,
} ) {
const periodsStings = getAvailablePeriods( subscriptionLength );
return sprintf(
'For %1$d %2$s',
subscriptionLength,
periodsStings[ billingPeriod ],
'woocommerce-subscriptions'
);
}
/**
* Creates a billing frequency string from a subscription
*
* Examples
* Every 6th week
* Every day
* Every month
* / day
* Each Week
* etc
*
* @param {Object} subscription Subscription object.
* @param {string} subscription.billing_period Period (month, day, week, year).
* @param {number} subscription.billing_interval Internal (1 month, 5 day, 4 week, 6 year).
* @param {string} separator A string to be prepended to frequency. followed by a space. Eg: (every, each, /)
* @param {string} price This is the string representation of the price of the product.
*/
export function getBillingFrequencyString(
{ billing_interval: billingInterval, billing_period: billingPeriod },
separator,
price
) {
const periodsStings = getAvailablePeriods( billingInterval );
const translatedPeriod = periodsStings[ billingPeriod ];
separator = separator.trim();
switch ( billingInterval ) {
case 1:
return `${ price } ${ separator } ${ translatedPeriod }`;
default:
return sprintf(
/*
* translators: %1$s is the price of the product. %2$s is the separator used e.g "every" or "/",
* %3$d is the length, %4$s is week, month, year
*/
__( `%1$s %2$s %3$d %4$s`, 'woocommerce-subscriptions' ),
price,
separator,
billingInterval,
translatedPeriod
);
}
}
/**
* Returns a switch string
*
* @param {string} switchType The switch type (upgraded, downgraded, crossgraded).
*
* @return {string} Translation ready switch name.
*/
export function getSwitchString( switchType ) {
switch ( switchType ) {
case 'upgraded':
return __( 'Upgrade', 'woocommerce-subscriptions' );
case 'downgraded':
return __( 'Downgrade', 'woocommerce-subscriptions' );
case 'crossgraded':
return __( 'Crossgrade', 'woocommerce-subscriptions' );
default:
return '';
}
}
/**
* Checks weather a subscription is a one off or not.
*
* @param {Object} subscription Subscription object data.
* @param {number} subscription.subscriptionLength Subscription length.
* @param {number} subscription.billingInterval Billing interval
* @return {boolean} whether this is a one off subscription or not.
*/
export function isOneOffSubscription( {
subscriptionLength,
billingInterval,
} ) {
return subscriptionLength === billingInterval;
}

2
build/index-rtl.css Normal file
View File

@@ -0,0 +1,2 @@
.wcs-recurring-totals-panel{padding:1em 0 0;position:relative}.wcs-recurring-totals-panel:after{border-style:solid;border-width:1px 0;bottom:0;content:"";display:block;right:0;opacity:.3;pointer-events:none;position:absolute;left:0;top:0}.wcs-recurring-totals-panel+.wcs-recurring-totals-panel:after{border-top-width:0}.wcs-recurring-totals-panel .wc-block-components-panel .wc-block-components-totals-item{padding-right:0;padding-left:0}.wcs-recurring-totals-panel .wc-block-components-totals-item__label:first-letter{text-transform:capitalize}.wcs-recurring-totals-panel .wcs-recurring-totals-panel__title .wc-block-components-totals-item__label{font-weight:700}.wcs-recurring-totals-panel__title{margin:0}.wcs-recurring-totals-panel__details .wc-block-components-panel__button,.wcs-recurring-totals-panel__details .wc-block-components-panel__button:focus,.wcs-recurring-totals-panel__details .wc-block-components-panel__button:hover{font-size:.875em}.wcs-recurring-totals-panel__details .wc-block-components-panel__content>.wc-block-components-totals-item:first-child{margin-top:0}.wcs-recurring-totals-panel__details .wc-block-components-panel__content>.wc-block-components-totals-item:last-child{margin-bottom:0}.wcs-recurring-totals-panel__details .wcs-recurring-totals-panel__details-total .wc-block-components-totals-item__label{font-weight:700}.wcs-recurring-totals__subscription-length{float:left}

1
build/index.asset.php Normal file
View File

@@ -0,0 +1 @@
<?php return array('dependencies' => array('react', 'wc-blocks-checkout', 'wc-price-format', 'wc-settings', 'wp-element', 'wp-i18n', 'wp-plugins'), 'version' => 'b49e17b919b0ba384261');

2
build/index.css Normal file
View File

@@ -0,0 +1,2 @@
.wcs-recurring-totals-panel{padding:1em 0 0;position:relative}.wcs-recurring-totals-panel:after{border-style:solid;border-width:1px 0;bottom:0;content:"";display:block;left:0;opacity:.3;pointer-events:none;position:absolute;right:0;top:0}.wcs-recurring-totals-panel+.wcs-recurring-totals-panel:after{border-top-width:0}.wcs-recurring-totals-panel .wc-block-components-panel .wc-block-components-totals-item{padding-left:0;padding-right:0}.wcs-recurring-totals-panel .wc-block-components-totals-item__label:first-letter{text-transform:capitalize}.wcs-recurring-totals-panel .wcs-recurring-totals-panel__title .wc-block-components-totals-item__label{font-weight:700}.wcs-recurring-totals-panel__title{margin:0}.wcs-recurring-totals-panel__details .wc-block-components-panel__button,.wcs-recurring-totals-panel__details .wc-block-components-panel__button:focus,.wcs-recurring-totals-panel__details .wc-block-components-panel__button:hover{font-size:.875em}.wcs-recurring-totals-panel__details .wc-block-components-panel__content>.wc-block-components-totals-item:first-child{margin-top:0}.wcs-recurring-totals-panel__details .wc-block-components-panel__content>.wc-block-components-totals-item:last-child{margin-bottom:0}.wcs-recurring-totals-panel__details .wcs-recurring-totals-panel__details-total .wc-block-components-totals-item__label{font-weight:700}.wcs-recurring-totals__subscription-length{float:right}

35
build/index.js Normal file
View File

@@ -0,0 +1,35 @@
(()=>{"use strict";var e={20:(e,r,s)=>{var t=s(609),n=Symbol.for("react.element"),i=Symbol.for("react.fragment"),o=Object.prototype.hasOwnProperty,c=t.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,l={key:!0,ref:!0,__self:!0,__source:!0};function a(e,r,s){var t,i={},a=null,p=null;for(t in void 0!==s&&(a=""+s),void 0!==r.key&&(a=""+r.key),void 0!==r.ref&&(p=r.ref),r)o.call(r,t)&&!l.hasOwnProperty(t)&&(i[t]=r[t]);if(e&&e.defaultProps)for(t in r=e.defaultProps)void 0===i[t]&&(i[t]=r[t]);return{$$typeof:n,type:e,key:a,ref:p,props:i,_owner:c.current}}r.Fragment=i,r.jsx=a,r.jsxs=a},609:e=>{e.exports=window.React},848:(e,r,s)=>{e.exports=s(20)}},r={};const s=window.wp.plugins,t=window.wc.blocksCheckout,n=window.wp.i18n,i=window.wc.priceFormat,o=window.wc.wcSettings;function c(e){return{day:(0,n._nx)("day","days",e,"Used in recurring totals section in Cart. 2+ will need plural, 1 will need singular.","woocommerce-subscriptions"),week:(0,n._nx)("week","weeks",e,"Used in recurring totals section in Cart. 2+ will need plural, 1 will need singular.","woocommerce-subscriptions"),month:(0,n._nx)("month","months",e,"Used in recurring totals section in Cart. 2+ will need plural, 1 will need singular.","woocommerce-subscriptions"),year:(0,n._nx)("year","years",e,"Used in recurring totals section in Cart. 2+ will need plural, 1 will need singular.","woocommerce-subscriptions")}}function l({billing_interval:e,billing_period:r},s,t){const i=c(e)[r];return s=s.trim(),1===e?`${t} ${s} ${i}`:(0,n.sprintf)(
/*
* translators: %1$s is the price of the product. %2$s is the separator used e.g "every" or "/",
* %3$d is the length, %4$s is week, month, year
*/
/*
* translators: %1$s is the price of the product. %2$s is the separator used e.g "every" or "/",
* %3$d is the length, %4$s is week, month, year
*/
(0,n.__)("%1$s %2$s %3$d %4$s","woocommerce-subscriptions"),t,s,e,i)}function a({subscriptionLength:e,billingInterval:r}){return e===r}var p=function s(t){var n=r[t];if(void 0!==n)return n.exports;var i=r[t]={exports:{}};return e[t](i,i.exports,s),i.exports}(848);const u=(0,o.getSetting)("displayCartPricesIncludingTax",!1),m=({currency:e,values:r})=>{const{total_discount:s,total_discount_tax:i}=r,o=parseInt(s,10);if(!o)return null;const c=parseInt(i,10),l=u?o+c:o;return(0,p.jsx)(t.TotalsItem,{className:"wc-block-components-totals-discount",currency:e,label:(0,n.__)("Discount","woocommerce-subscriptions"),value:-1*l})},_=({values:e,currency:r,selectedRate:s,needsShipping:i,calculatedShipping:c})=>{if(!i||!c)return null;const l=u?parseInt(e.total_shipping,10)+parseInt(e.total_shipping_tax,10):parseInt(e.total_shipping,10),a=0===l&&(0,o.isWcVersion)("9.0",">=")?(0,p.jsx)("strong",{children:(0,n.__)("Free","woocommerce-subscriptions")}):l;return(0,p.jsx)(t.TotalsItem,{value:a,label:(0,n.__)("Shipping","woocommerce-subscriptions"),currency:r,description:!!s&&(0,n.sprintf)(
// translators: %s selected shipping rate (ex: flat rate)
// translators: %s selected shipping rate (ex: flat rate)
(0,n.__)("via %s","woocommerce-subscriptions"),s)})},g=({nextPaymentDate:e,subscriptionLength:r,billingPeriod:s,billingInterval:t})=>{const i=function({subscriptionLength:e,billingPeriod:r}){const s=c(e);return(0,n.sprintf)("For %1$d %2$s",e,s[r],"woocommerce-subscriptions")}({subscriptionLength:r,billingPeriod:s}),o=a({subscriptionLength:r,billingInterval:t})?(0,n.sprintf)(/* Translators: %1$s is a date. */ /* Translators: %1$s is a date. */
(0,n.__)("Due: %1$s","woocommerce-subscriptions"),e):(0,n.sprintf)(/* Translators: %1$s is a date. */ /* Translators: %1$s is a date. */
(0,n.__)("Starting: %1$s","woocommerce-subscriptions"),e);return(0,p.jsxs)("span",{children:[!!e&&o," ",!!r&&r>=t&&(0,p.jsx)("span",{className:"wcs-recurring-totals__subscription-length",children:i})]})},d=({currency:e,billingInterval:r,billingPeriod:s,nextPaymentDate:i,subscriptionLength:o,totals:c})=>{const l=a({billingInterval:r,subscriptionLength:o})?(0,n.__)("Total","woocommerce-subscriptions"):function({billingInterval:e,billingPeriod:r}){switch(e){case 1:if("day"===r)return(0,n.__)("Daily recurring total","woocommerce-subscriptions");if("week"===r)return(0,n.__)("Weekly recurring total","woocommerce-subscriptions");if("month"===r)return(0,n.__)("Monthly recurring total","woocommerce-subscriptions");if("year"===r)return(0,n.__)("Yearly recurring total","woocommerce-subscriptions");break;case 2:return(0,n.sprintf)(/* translators: %1$s is week, month, year */ /* translators: %1$s is week, month, year */
(0,n.__)("Recurring total every 2nd %1$s","woocommerce-subscriptions"),r);case 3:return(0,n.sprintf)(/* Translators: %1$s is week, month, year */ /* Translators: %1$s is week, month, year */
(0,n.__)("Recurring total every 3rd %1$s","woocommerce-subscriptions"),r);default:return(0,n.sprintf)(/* Translators: %1$d is number of weeks, months, days, years. %2$s is week, month, year */ /* Translators: %1$d is number of weeks, months, days, years. %2$s is week, month, year */
(0,n.__)("Recurring total every %1$dth %2$s","woocommerce-subscriptions"),e,r)}}({billingInterval:r,billingPeriod:s});return(0,p.jsx)(t.TotalsItem,{className:"wcs-recurring-totals-panel__title",currency:e,label:l,value:c,description:(0,p.jsx)(g,{nextPaymentDate:i,subscriptionLength:o,billingInterval:r,billingPeriod:s})})},b=({subscription:e,needsShipping:r,calculatedShipping:s})=>{const{totals:o,billing_interval:c,billing_period:l,next_payment_date:a,subscription_length:g,shipping_rates:b}=e;if(!a)return null;const w=b?.[0]?.shipping_rates?.find((({selected:e})=>e))?.name,h=(0,i.getCurrencyFromPriceResponse)(o);return(0,p.jsxs)("div",{className:"wcs-recurring-totals-panel",children:[(0,p.jsx)(d,{billingInterval:c,billingPeriod:l,nextPaymentDate:a,subscriptionLength:g,totals:parseInt(o.total_price,10),currency:h}),(0,p.jsxs)(t.Panel,{className:"wcs-recurring-totals-panel__details",initialOpen:!1,title:(0,n.__)("Details","woocommerce-subscriptions"),children:[(0,p.jsxs)(t.TotalsWrapper,{children:[(0,p.jsx)(t.Subtotal,{currency:h,values:o}),(0,p.jsx)(m,{currency:h,values:o})]}),(0,p.jsx)(t.TotalsWrapper,{className:"wc-block-components-totals-shipping",children:(0,p.jsx)(_,{currency:h,needsShipping:r,calculatedShipping:s,values:o,selectedRate:w})}),!u&&(0,p.jsx)(t.TotalsWrapper,{children:(0,p.jsx)(t.TotalsTaxes,{currency:h,values:o})}),(0,p.jsx)(t.TotalsWrapper,{children:(0,p.jsx)(t.TotalsItem,{className:"wcs-recurring-totals-panel__details-total",currency:h,label:(0,n.__)("Total","woocommerce-subscriptions"),value:parseInt(o.total_price,10)})})]})]})},w=({extensions:e,cart:r})=>{const{subscriptions:s}=e,{cartNeedsShipping:t,cartHasCalculatedShipping:n}=r;return s&&0!==s.length?s.map((({key:e,...r})=>(0,p.jsx)(b,{subscription:r,needsShipping:t,calculatedShipping:n},e))):null},h=window.wp.element,x=({extensions:e,collapsible:r,collapse:s,showItems:t,noResultsMessage:n,renderOption:i,components:o,context:c})=>{const{subscriptions:l=[]}=e,{ShippingRatesControlPackage:a}=o,u=(0,h.useMemo)((()=>Object.values(l).map((e=>e.shipping_rates)).filter(Boolean).flat()),[l]),m=(0,h.useMemo)((()=>1<u.length||s),[u.length,s]),_=(0,h.useMemo)((()=>1<u.length||t),[u.length,t]);return u.map((({package_id:e,...s})=>(0,p.jsx)(a,{packageId:e,packageData:s,collapsible:r,collapse:m,showItems:_,noResultsMessage:n,renderOption:i,highlightChecked:"woocommerce/checkout"===c},e)))};(0,s.registerPlugin)("woocommerce-subscriptions",{render:()=>(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)(t.ExperimentalOrderShippingPackages,{children:(0,p.jsx)(x,{})}),(0,p.jsx)(t.ExperimentalOrderMeta,{children:(0,p.jsx)(w,{})})]}),scope:"woocommerce-checkout"}),(0,t.registerCheckoutFilters)("woocommerce-subscriptions",{totalLabel:(e,{subscriptions:r})=>0<r?.length?(0,n.__)("Total due today","woocommerce-subscriptions"):e,subtotalPriceFormat:(e,{subscriptions:r})=>{if(r?.billing_period&&r?.billing_interval){const{billing_interval:s,subscription_length:t}=r;return a({subscriptionLength:t,billingInterval:s})?l(r,1===t?
// translators: the word used to describe billing frequency, e.g. "for" 1 day or "for" 1 month.
// translators: the word used to describe billing frequency, e.g. "for" 1 day or "for" 1 month.
(0,n.__)("for 1","woocommerce-subscriptions"):
// translators: the word used to describe billing frequency, e.g. "for" 6 days or "for" 2 weeks.
// translators: the word used to describe billing frequency, e.g. "for" 6 days or "for" 2 weeks.
(0,n.__)("for","woocommerce-subscriptions"),e):l(r,
// translators: the word used to describe billing frequency, e.g. "every" 6 days or "every" 2 weeks.
// translators: the word used to describe billing frequency, e.g. "every" 6 days or "every" 2 weeks.
(0,n.__)("every","woocommerce-subscriptions"),e)}return e},saleBadgePriceFormat:(e,{subscriptions:r})=>r?.billing_period&&r?.billing_interval?l(r,"/",e):e,itemName:(e,{subscriptions:r})=>r?.is_resubscribe?(0,n.sprintf)(
// translators: %s Product name.
// translators: %s Product name.
(0,n.__)("%s (resubscription)","woocommerce-subscriptions"),e):r?.switch_type?(0,n.sprintf)(
// translators: %1$s Product name, %2$s Switch type (upgraded, downgraded, or crossgraded).
// translators: %1$s Product name, %2$s Switch type (upgraded, downgraded, or crossgraded).
(0,n.__)("%1$s (%2$s)","woocommerce-subscriptions"),e,function(e){switch(e){case"upgraded":return(0,n.__)("Upgrade","woocommerce-subscriptions");case"downgraded":return(0,n.__)("Downgrade","woocommerce-subscriptions");case"crossgraded":return(0,n.__)("Crossgrade","woocommerce-subscriptions");default:return""}}(r.switch_type)):e,cartItemPrice:(e,{subscriptions:r},{context:s})=>r?.sign_up_fees?"cart"===s?(0,n.sprintf)(/* translators: %s is the subscription price to pay immediately (ie: $10). */ /* translators: %s is the subscription price to pay immediately (ie: $10). */
(0,n.__)("Due today %s","woocommerce-subscriptions"),e):(0,n.sprintf)(/* translators: %s is the subscription price to pay immediately (ie: $10). */ /* translators: %s is the subscription price to pay immediately (ie: $10). */
(0,n.__)("%s due today","woocommerce-subscriptions"),e):e,placeOrderButtonLabel:e=>{const r=(0,o.getSetting)("subscriptions_data");return r?.place_order_override?r?.place_order_override:e}})})();

View File

@@ -1,5 +1,11 @@
*** WooCommerce Subscriptions Changelog ***
2025-05-20 - version 7.5.0
* Update: Add pagination to the related orders list in the My Account > View Subscription page.
* Fix: Infinite loop when trying to load subscription related orders meta after cache busting.
* Fix: Replace blogname with site_title on translation files.
* Dev: Make the `wcs_get_subscriptions_for_order()` function more robust. It should not return any subscriptions if presented with an invalid order.
2025-04-14 - version 7.4.0
* Update: Increase the number of args accepted by wcs_get_subscriptions(), to bring about parity with wc_get_orders().
* Dev: Update wcs_maybe_prefix_key() and wcs_maybe_unprefix_key() to support an array of keys.

View File

@@ -71,7 +71,7 @@ class WCS_Admin_Reports {
/**
* Add the 'Subscriptions' report type to the WooCommerce reports screen.
*
* @param array Array of Report types & their labels, excluding the Subscription product type.
* @param array $reports Array of Report types & their labels, excluding the Subscription product type.
* @return array Array of Report types & their labels, including the Subscription product type.
* @since 2.1
*/

View File

@@ -122,7 +122,7 @@ class WCS_Report_Cache_Manager {
* This function is attached as a callback on the events in the $update_events_and_classes property.
*
* @since 2.1
* @return null
* @return void
*/
public function set_reports_to_update() {
if ( isset( $this->update_events_and_classes[ current_filter() ] ) ) {
@@ -175,7 +175,7 @@ class WCS_Report_Cache_Manager {
}
// Use the index to space out caching of each report to make them 5 minutes apart so that on large sites, where we assume they'll get a request at least once every few minutes, we don't try to update the caches of all reports in the same request
as_schedule_single_action( gmdate( 'U' ) + MINUTE_IN_SECONDS * ( $index + 1 ) * 5, $this->cron_hook, $cron_args );
as_schedule_single_action( (int) gmdate( 'U' ) + MINUTE_IN_SECONDS * ( $index + 1 ) * 5, $this->cron_hook, $cron_args );
}
}
}
@@ -185,7 +185,6 @@ class WCS_Report_Cache_Manager {
* Update the cache data for a given report, as specified with $report_class, by call it's get_data() method.
*
* @since 2.1
* @return null
*/
public function update_cache( $report_class ) {
/**
@@ -217,7 +216,10 @@ class WCS_Report_Cache_Manager {
// Load report class dependencies
require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
require_once( WC()->plugin_path() . '/includes/admin/reports/class-wc-admin-report.php' );
$wc_core_dir = getenv( 'CI' ) ? ( getenv( 'WC_CORE_DIR' ) ? getenv( 'WC_CORE_DIR' ) : '/tmp/woocommerce' ) : WC()->plugin_path() . '/woocommerce';
require_once( $wc_core_dir . '/includes/admin/reports/class-wc-admin-report.php' );
$reflector = new ReflectionMethod( $report_class, 'get_data' );

View File

@@ -38,7 +38,7 @@ class WCS_Report_Retention_Rate extends WC_Admin_Report {
* subscription may not have been active all of that time. Instead, it may have been on-hold for part of it.
*
* @since 2.1
* @return null
* @return void
*/
private function query_report_data() {
global $wpdb;
@@ -128,7 +128,6 @@ class WCS_Report_Retention_Rate extends WC_Admin_Report {
* Use a custom report as we don't need the date filters provided by the WooCommerce html-report-by-date.php template.
*
* @since 2.1
* @return null
*/
public function output_report() {
include( WC_Subscriptions_Plugin::instance()->get_plugin_directory( 'includes/admin/views/html-report-by-period.php' ) );
@@ -138,7 +137,6 @@ class WCS_Report_Retention_Rate extends WC_Admin_Report {
* Output the HTML and JavaScript to plot the chart
*
* @since 2.1
* @return null
*/
public function get_main_chart() {
@@ -153,6 +151,8 @@ class WCS_Report_Retention_Rate extends WC_Admin_Report {
);
}
$x_axes_label = '';
switch ( $this->report_data->interval_period ) {
case 'day':
$x_axes_label = _x( 'Number of days after sign-up', 'X axis label on retention rate graph', 'woocommerce-subscriptions' );
@@ -214,7 +214,7 @@ class WCS_Report_Retention_Rate extends WC_Admin_Report {
color: '#aaa',
position: "bottom",
tickDecimals: 0,
axisLabel: "<?php echo esc_js( $x_axes_label ); ?>",
axisLabel: "<?php esc_js( $x_axes_label ); ?>",
axisLabelPadding: 18,
font: {
color: "#aaa"

View File

@@ -57,11 +57,11 @@ class WCS_Report_Subscription_By_Product extends WP_List_Table {
case 'product_name':
// If the product is a subscription variation, use the parent product's edit post link
if ( $report_item->parent_product_id > 0 ) {
return edit_post_link( $report_item->product_name, ' - ', null, $report_item->parent_product_id );
edit_post_link( $report_item->product_name, ' - ', null, $report_item->parent_product_id );
} else {
return edit_post_link( $report_item->product_name, null, null, $report_item->product_id );
edit_post_link( $report_item->product_name, null, null, $report_item->product_id );
}
break;
case 'subscription_count':
return sprintf( '<a href="%s%d">%d</a>', admin_url( 'edit.php?post_type=shop_subscription&_wcs_product=' ), $report_item->product_id, $report_item->subscription_count );

View File

@@ -36,7 +36,7 @@ class WCS_Report_Subscription_Events_By_Date extends WC_Admin_Report {
/**
* Get report data
* @return array
* @return stdClass
*/
public function get_report_data() {
@@ -807,7 +807,7 @@ class WCS_Report_Subscription_Events_By_Date extends WC_Admin_Report {
/**
* Get the main chart
*
* @return string
* @return void
*/
public function get_main_chart() {
global $wp_locale;

View File

@@ -18,7 +18,7 @@ class WCS_Report_Subscription_Payment_Retry extends WC_Admin_Report {
/**
* Get report data
* @return array
* @return stdClass
*/
public function get_report_data() {
@@ -210,7 +210,7 @@ class WCS_Report_Subscription_Payment_Retry extends WC_Admin_Report {
/**
* Get the main chart
*
* @return string
* @return void
*/
public function get_main_chart() {
global $wp_locale;

View File

@@ -74,7 +74,7 @@ class WCS_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
}
}
} while ( $next_payment_timestamp > 0 && $next_payment_timestamp <= $this->end_date
&& isset( $key, $scheduled_ends[ $key ] )
&& isset( $scheduled_ends[ $key ] )
&& ( 0 == $scheduled_ends[ $key ] || $next_payment_timestamp < strtotime( $scheduled_ends[ $key ] ) ) );
}
}
@@ -233,7 +233,7 @@ class WCS_Report_Upcoming_Recurring_Revenue extends WC_Admin_Report {
/**
* Get the main chart
* @return string
* @return void
*/
public function get_main_chart() {
global $wp_locale;

View File

@@ -79,6 +79,7 @@ class WC_REST_Subscription_System_Status_Manager {
private static function get_payment_gateway_feature_support() {
$gateway_features = array();
// @phpstan-ignore property.notFound
foreach ( WC()->payment_gateways->get_available_payment_gateways() as $gateway_id => $gateway ) {
// Some gateways include array keys. For consistency, only send the values.
$gateway_features[ $gateway_id ] = array_values( (array) apply_filters( 'woocommerce_subscriptions_payment_gateway_features_list', $gateway->supports, $gateway ) );

View File

@@ -94,7 +94,7 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_Controller {
*
* @since 3.1.0
*
* @param WC_Data $object Subscription object.
* @param WC_Subscription $object Subscription object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response
*/

View File

@@ -15,7 +15,6 @@ if ( ! defined( 'ABSPATH' ) ) {
* REST API Subscription Notes controller class.
*
* @package WooCommerce_Subscriptions/API
* @extends WC_REST_Order_Notes_Controller
*/
class WC_REST_Subscription_Notes_Controller extends WC_REST_Order_Notes_Controller {

View File

@@ -16,7 +16,6 @@ if ( ! defined( 'ABSPATH' ) ) {
* REST API Subscriptions controller class.
*
* @package WooCommerce_Subscriptions/API
* @extends WC_REST_Orders_Controller
*/
class WC_REST_Subscriptions_Controller extends WC_REST_Orders_Controller {
@@ -76,7 +75,7 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_Controller {
*
* @since 2.1
* @param WP_REST_Response $response
* @param WP_POST $post
* @param WP_Post $post
* @param WP_REST_Request $request
*/
public function filter_get_subscription_response( $response, $post, $request ) {
@@ -123,7 +122,7 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_Controller {
*
* @since 2.1
* @param WP_REST_Request $request
* @param WP_POST $post
* @param WP_Post $post
*/
protected function update_order( $request, $post ) {
try {

View File

@@ -15,7 +15,6 @@ if ( ! defined( 'ABSPATH' ) ) {
* REST API Subscription Notes controller class.
*
* @package WooCommerce_Subscriptions/API
* @extends WC_REST_Order_Notes_Controller
*/
class WC_REST_Subscription_Notes_V1_Controller extends WC_REST_Order_Notes_V1_Controller {

View File

@@ -17,7 +17,6 @@ if ( ! defined( 'ABSPATH' ) ) {
* REST API Subscriptions controller class.
*
* @package WooCommerce_Subscriptions/API
* @extends WC_REST_Orders_Controller
*/
class WC_REST_Subscriptions_V1_Controller extends WC_REST_Orders_V1_Controller {
@@ -75,7 +74,7 @@ class WC_REST_Subscriptions_V1_Controller extends WC_REST_Orders_V1_Controller {
*
* @since 2.1
* @param WP_REST_Response $response
* @param WP_POST $post
* @param WP_Post $post
* @param WP_REST_Request $request
*/
public function filter_get_subscription_response( $response, $post, $request ) {
@@ -232,7 +231,6 @@ class WC_REST_Subscriptions_V1_Controller extends WC_REST_Orders_V1_Controller {
*
* @since 2.1
* @param WP_REST_Request $request
* @param WP_POST $post
*/
protected function update_order( $request ) {
try {

View File

@@ -12,7 +12,6 @@ if ( ! defined( 'ABSPATH' ) ) {
* REST API Subscription Notes controller class.
*
* @package WooCommerce_Subscriptions/API
* @extends WC_REST_Order_Notes_Controller
*/
class WC_REST_Subscription_Notes_V2_Controller extends WC_REST_Order_Notes_V2_Controller {

View File

@@ -89,7 +89,7 @@ class WC_REST_Subscriptions_V2_Controller extends WC_REST_Orders_V2_Controller {
*
* @since 6.4.0
*
* @param WC_Data $object Subscription object.
* @param WC_Subscription $object Subscription object.
* @param WP_REST_Request $request Request object.
*
* @return WP_REST_Response

View File

@@ -18,7 +18,7 @@ class WCS_API {
add_filter( 'woocommerce_api_classes', array( __CLASS__, 'includes' ) );
add_action( 'rest_api_init', array( __CLASS__, 'register_routes' ), 15 );
add_action( 'rest_api_init', array( __CLASS__, 'register_route_overrides' ), 15 );
add_action( 'woocommerce_rest_set_order_item', [ __CLASS__, 'add_sign_up_fee_to_order_item' ], 15, 2 );
add_action( 'woocommerce_rest_set_order_item', array( __CLASS__, 'add_sign_up_fee_to_order_item' ), 15, 2 );
}
/**
@@ -63,6 +63,7 @@ class WCS_API {
);
foreach ( $endpoint_classes as $class ) {
// @phpstan-ignore class.nameCase
$controller = new $class();
$controller->register_routes();
}
@@ -87,7 +88,7 @@ class WCS_API {
*
* @since 6.3.0
*
* @param WC_Order_Item $item Order item object.
* @param WC_Order_Item_Product $item Order item object.
* @param array $item_request_data Data posted to the API about the order item.
*/
public static function add_sign_up_fee_to_order_item( $item, $item_request_data = array() ) {
@@ -119,7 +120,13 @@ class WCS_API {
$price = (float) $product->get_price() + $sign_up_fee;
}
$total = wc_get_price_excluding_tax( $product, [ 'qty' => $item->get_quantity(), 'price' => $price ] );
$total = wc_get_price_excluding_tax(
$product,
array(
'qty' => $item->get_quantity(),
'price' => $price,
)
);
$item->set_total( $total );
$item->set_subtotal( $total );
@@ -165,6 +172,7 @@ class WCS_API {
*/
public static function get_wc_api_endpoint_data( $endpoint ) {
if ( wcs_is_woocommerce_pre( '9.0.0' ) ) {
// @phpstan-ignore-next-line Call to deprecated method.
return WC()->api->get_endpoint_data( $endpoint );
}

View File

@@ -15,7 +15,7 @@ class WCS_Call_To_Action_Button_Text_Manager {
*/
public static function init() {
add_filter( 'woocommerce_subscription_settings', array( __CLASS__, 'add_settings' ), 5 );
add_filter( 'wc_subscription_product_add_to_cart_text', array( __CLASS__, 'filter_add_to_cart_text' ), 10, 2 );
add_filter( 'wc_subscription_product_add_to_cart_text', array( __CLASS__, 'filter_add_to_cart_text' ) );
add_filter( 'wcs_place_subscription_order_text', array( __CLASS__, 'filter_place_subscription_order_text' ) );
}
@@ -72,7 +72,6 @@ class WCS_Call_To_Action_Button_Text_Manager {
* @since 4.0.0
*
* @param string $add_to_cart_text The product's add to cart text.
* @param WC_Abstract_Product $product The product.
*
* @return string
*/

View File

@@ -313,13 +313,14 @@ class WCS_Limited_Recurring_Coupon_Manager {
}
// Bail early if there are no limited coupons applied to the recurring cart or if there is no discount provided.
// @phpstan-ignore property.notFound
if ( empty( $limited_recurring_coupons ) || ! $recurring_cart->discount_cart ) {
return false;
}
$has_expiring_coupon = false;
$subscription_length = wcs_cart_pluck( $recurring_cart, 'subscription_length' );
$subscription_payments = $subscription_length / wcs_cart_pluck( $recurring_cart, 'subscription_period_interval' );
$subscription_payments = (int) $subscription_length / (int) wcs_cart_pluck( $recurring_cart, 'subscription_period_interval' );
// Limited recurring coupons will always expire at some point on subscriptions with no length.
if ( empty( $subscription_length ) ) {
@@ -381,7 +382,6 @@ class WCS_Limited_Recurring_Coupon_Manager {
*
* @since 4.0.0
*
* @param string $message The current message indicating there are no payment methods available..
* @return string The filtered message indicating there are no payment methods available.
*/
public static function no_available_payment_methods_message() {

View File

@@ -22,7 +22,7 @@ class WCS_Manual_Renewal_Manager {
*
* @since 4.0.0
* @param $settings The full subscription settings array.
* @return $settings.
* @return array
*/
public static function add_settings( $settings ) {

View File

@@ -26,8 +26,10 @@ class WCS_Subscriber_Role_Manager {
* @return array Subscriptions settings.
*/
public static function add_settings( $settings ) {
$roles_options = array();
if ( ! function_exists( 'get_editable_roles' ) ) {
require_once( ABSPATH . 'wp-admin/includes/user.php' );
require_once ABSPATH . 'wp-admin/includes/user.php';
}
foreach ( get_editable_roles() as $role => $details ) {

View File

@@ -103,7 +103,7 @@ class WCS_Upgrade_Notice_Manager {
// translators: placeholder is Subscription version string ('3.1')
$notice->set_heading( sprintf( __( 'Welcome to WooCommerce Subscriptions %s!', 'woocommerce-subscriptions' ), $version ) );
$notice->set_content_template( 'update-welcome-notice.php', WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory() . '/includes/upgrades/templates/', array(
$notice->set_content_template( 'update-welcome-notice.php', WC_Subscriptions_Plugin::instance()->get_plugin_directory() . '/includes/upgrades/templates/', array(
'version' => $version,
'features' => $features,
) );

View File

@@ -133,17 +133,21 @@ class WCS_Webhooks {
switch ( $webhook->get_api_version() ) {
case 'legacy_v3':
// @phpstan-ignore-next-line Ignore legacy referencies.
if ( is_null( wc()->api ) ) {
throw new \Exception( 'The Legacy REST API plugin is not installed on this site. More information: https://developer.woocommerce.com/2023/10/03/the-legacy-rest-api-will-move-to-a-dedicated-extension-in-woocommerce-9-0/ ' );
}
// @phpstan-ignore-next-line
WC()->api->WC_API_Subscriptions->register_routes( array() );
// @phpstan-ignore-next-line
$payload = WC()->api->WC_API_Subscriptions->get_subscription( $resource_id );
break;
case 'wp_api_v1':
case 'wp_api_v2':
// There is no v2 subscritpion endpoint support so they fall back to v1.
$request = new WP_REST_Request( 'GET' );
// @phpstan-ignore class.nameCase
$controller = new WC_REST_Subscriptions_v1_Controller();
$request->set_param( 'id', $resource_id );

View File

@@ -60,7 +60,7 @@ class WCS_Zero_Initial_Payment_Checkout_Manager {
*/
public static function cart_needs_payment( $cart_needs_payment ) {
if ( ! self::zero_initial_checkout_requires_payment() ) {
remove_filter( 'woocommerce_cart_needs_payment', 'WC_Subscriptions_Cart::cart_needs_payment', 10, 2 );
remove_filter( 'woocommerce_cart_needs_payment', 'WC_Subscriptions_Cart::cart_needs_payment' );
}
return $cart_needs_payment;
@@ -77,7 +77,7 @@ class WCS_Zero_Initial_Payment_Checkout_Manager {
*/
public static function order_needs_payment( $needs_payment ) {
if ( ! self::zero_initial_checkout_requires_payment() ) {
remove_filter( 'woocommerce_order_needs_payment', 'WC_Subscriptions_Order::order_needs_payment', 10, 3 );
remove_filter( 'woocommerce_order_needs_payment', 'WC_Subscriptions_Order::order_needs_payment' );
}
return $needs_payment;

View File

@@ -461,7 +461,7 @@ class WC_Subscriptions_Admin {
$billing_period = 'month';
}
include WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory( 'templates/admin/html-variation-price.php' );
include WC_Subscriptions_Plugin::instance()->get_plugin_directory( 'templates/admin/html-variation-price.php' );
wp_nonce_field( 'wcs_subscription_variations', '_wcsnonce_save_variations', false );
@@ -919,7 +919,7 @@ class WC_Subscriptions_Admin {
$script_params['ajaxUrl'] = admin_url( 'admin-ajax.php' );
$script_params['isWCPre24'] = var_export( wcs_is_woocommerce_pre( '2.4' ), true );
wp_enqueue_script( 'woocommerce_subscriptions_admin', WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory_url( 'assets/js/admin/admin.js' ), $dependencies, filemtime( WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory( 'assets/js/admin/admin.js' ) ) );
wp_enqueue_script( 'woocommerce_subscriptions_admin', WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory_url( 'assets/js/admin/admin.js' ), $dependencies, filemtime( dirname( WC_Subscriptions::$plugin_file ) . '/assets/js/admin/admin.js' ) );
wp_localize_script( 'woocommerce_subscriptions_admin', 'WCSubscriptions', apply_filters( 'woocommerce_subscriptions_admin_script_parameters', $script_params ) );
// Maybe add the pointers for first timers
@@ -1702,7 +1702,7 @@ class WC_Subscriptions_Admin {
'paginate' => false,
),
'',
WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory( 'templates/' )
WC_Subscriptions_Plugin::instance()->get_plugin_directory( 'templates/' )
);
return ob_get_clean();

View File

@@ -33,7 +33,7 @@ class WCS_Admin_Empty_List_Content_Manager {
'html-admin-empty-list-table.php',
[],
'',
WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory( 'templates/admin/' )
WC_Subscriptions_Plugin::instance()->get_plugin_directory( 'templates/admin/' )
);
}

View File

@@ -91,7 +91,7 @@ class WCS_Admin_Notice {
}
$template_name = 'html-admin-notice.php';
$template_path = WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory( 'templates/admin/' );
$template_path = WC_Subscriptions_Plugin::instance()->get_plugin_directory( 'templates/admin/' );
if ( function_exists( 'wc_get_template' ) ) {
wc_get_template( $template_name, array( 'notice' => $this ), '', $template_path );

View File

@@ -686,7 +686,12 @@ class WCS_Admin_Post_Types {
public static function get_date_column_content( $subscription, $column ) {
$date_type_map = array( 'last_payment_date' => 'last_order_date_created' );
$date_type = array_key_exists( $column, $date_type_map ) ? $date_type_map[ $column ] : $column;
if ( 'last_payment_date' === $column ) {
$date_timestamp = self::get_last_payment_date( $subscription );
} else {
$date_timestamp = $subscription->get_time( $date_type );
}
if ( 0 === $date_timestamp ) {
return '-';
@@ -1834,6 +1839,26 @@ class WCS_Admin_Post_Types {
return $pieces;
}
/**
* Get the last payment date for a subscription.
*
* @param WC_Subscription $subscription The subscription object.
* @return int The last payment date timestamp.
*/
private static function get_last_payment_date( $subscription ) {
$last_order_date_created = $subscription->get_last_order_date_created();
if ( ! empty( $last_order_date_created ) ) {
return $last_order_date_created;
}
$date_timestamp = $subscription->get_time( 'last_order_date_created' );
$subscription->set_last_order_date_created( $date_timestamp );
$subscription->save();
return $date_timestamp;
}
/**
* Adds order table query clauses to sort the subscriptions list table by last payment date.
*

View File

@@ -123,7 +123,7 @@ class WCS_Admin_System_Status {
$section_tooltip = $section['tooltip'];
$debug_data = $section['data'];
include WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory( 'templates/admin/status.php' );
include WC_Subscriptions_Plugin::instance()->get_plugin_directory( 'templates/admin/status.php' );
}
}
@@ -216,7 +216,7 @@ class WCS_Admin_System_Status {
* @return array Theme override data.
*/
private static function get_theme_overrides() {
$wcs_template_dir = WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory( 'templates/' );
$wcs_template_dir = WC_Subscriptions_Plugin::instance()->get_plugin_directory( 'templates/' );
$wc_template_path = trailingslashit( wc()->template_path() );
$theme_root = trailingslashit( get_theme_root() );
$overridden = array();

Some files were not shown because too many files have changed in this diff Show More