diff --git a/.wp-env.json b/.wp-env.json index 027ecfc..1ffd5ab 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -2,7 +2,7 @@ "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.8/woocommerce-gateway-dummy.zip", + "https://github.com/woocommerce/woocommerce-gateway-dummy/releases/download/1.0.9/woocommerce-gateway-dummy.zip", ".", "./tests/e2e/test-configuration-plugin" ], diff --git a/bin/install-wp-tests.sh b/bin/install-wp-tests.sh index ad26312..c0515c9 100755 --- a/bin/install-wp-tests.sh +++ b/bin/install-wp-tests.sh @@ -173,6 +173,10 @@ install_db() { mysqladmin create $DB_NAME $MYSQLADMIN_FLAGS } +install_legacy_rest_api() { + wp plugin install https://downloads.wordpress.org/plugin/woocommerce-legacy-rest-api.1.0.4.zip --activate +} + install_woocommerce() { WC_INSTALL_EXTRA='' INSTALLED_WC_VERSION=$(wp plugin get woocommerce --field=version) @@ -202,4 +206,5 @@ install_wp install_db configure_wp install_test_suite +install_legacy_rest_api install_woocommerce diff --git a/changelog.txt b/changelog.txt index 4ff4cf9..7471c63 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,16 @@ *** WooCommerce Subscriptions Changelog *** +2024-11-14 - version 6.9.0 +* Add: New Customer Notification feature - sends reminder emails for upcoming subscription renewals, trials ending, and subscription expirations. +* Fix: Prevent adding products to the cart if a subscription renewal is already present. +* Update: Improved performance of wcs_get_subscription() when querying by product and customer or order. +* Update: Improved performance when checking limited subscription product availability. +* Update: Deprecate upgrading from versions of WooCommerce Subscriptions prior to 3.0.0 (released Jan 2020). +* Dev: Minor refactoring of `init` method in `WC_Subscriptions_Upgrader` class. +* Dev: Introduce the filter `woocommerce_subscriptions_synced_first_renewal_payment_timestamp` to enable plugins to modify the first renewal date of synced subscriptions. +* Dev: Update `get_post_meta()` calls to fetch product meta using CRUD getters. +* Dev: Update subscriptions-core to 7.7.1 + 2024-10-14 - version 6.8.0 * Fix: Restore Retry icon in Orders table for HPOS-enabled stores. * Fix: Correctly updates a subscription status to `cancelled` during a payment failure call when the current status is `pending-cancel`. diff --git a/includes/payment-retry/data-stores/class-wcs-retry-post-store.php b/includes/payment-retry/data-stores/class-wcs-retry-post-store.php index 41555c3..1b804ad 100644 --- a/includes/payment-retry/data-stores/class-wcs-retry-post-store.php +++ b/includes/payment-retry/data-stores/class-wcs-retry-post-store.php @@ -150,6 +150,7 @@ public function get_retries( $args = array(), $return = 'objects' ) { 'limit' => -1, ) ); + // We need to keep this call to `get_posts` since this is a custom post type (`payment_retry`), not a custom order type. $retry_post_ids = get_posts( array( 'posts_per_page' => $args['limit'], 'post_type' => self::$post_type, diff --git a/includes/payment-retry/emails/class-wcs-retry-email.php b/includes/payment-retry/emails/class-wcs-retry-email.php index 8aa12af..6ca0f82 100644 --- a/includes/payment-retry/emails/class-wcs-retry-email.php +++ b/includes/payment-retry/emails/class-wcs-retry-email.php @@ -20,14 +20,13 @@ class WCS_Retry_Email { * @since 2.1 */ public static function init() { + add_action( 'woocommerce_email_classes', array( __CLASS__, 'add_emails' ), 12, 1 ); - add_action( 'woocommerce_email_classes', __CLASS__ . '::add_emails', 12, 1 ); + add_action( 'woocommerce_subscriptions_after_apply_retry_rule', array( __CLASS__, 'send_email' ), 0, 2 ); - add_action( 'woocommerce_subscriptions_after_apply_retry_rule', __CLASS__ . '::send_email', 0, 2 ); + add_action( 'woocommerce_order_status_failed', array( __CLASS__, 'maybe_detach_email' ), 9 ); - add_action( 'woocommerce_order_status_failed', __CLASS__ . '::maybe_detach_email', 9 ); - - add_action( 'woocommerce_order_status_changed', __CLASS__ . '::maybe_reattach_email', 100, 3 ); + add_action( 'woocommerce_order_status_changed', array( __CLASS__, 'maybe_reattach_email' ), 100, 3 ); } /** @@ -47,8 +46,8 @@ public static function add_emails( $email_classes ) { * * Attached to 'woocommerce_subscriptions_after_apply_retry_rule' with a low priority. * - * @param WCS_Retry_Rule The retry rule applied. - * @param WC_Order The order to which the retry rule was applied. + * @param WCS_Retry_Rule $retry_rule The retry rule applied. + * @param WC_Order $last_order The order to which the retry rule was applied. * @since 2.1 */ public static function send_email( $retry_rule, $last_order ) { @@ -60,7 +59,7 @@ public static function send_email( $retry_rule, $last_order ) { $email_class = $retry_rule->get_email_template( $recipient ); if ( class_exists( $email_class ) ) { $email = new $email_class(); - $email->trigger( wcs_get_objects_property( $last_order, 'id' ), $last_order ); + $email->trigger( $last_order->get_id(), $last_order ); } } } diff --git a/includes/switching/class-wc-subscriptions-switcher.php b/includes/switching/class-wc-subscriptions-switcher.php index 0d73f62..ff7fe0e 100644 --- a/includes/switching/class-wc-subscriptions-switcher.php +++ b/includes/switching/class-wc-subscriptions-switcher.php @@ -1200,7 +1200,7 @@ public static function update_shipping_methods( $subscription, $recurring_cart ) WC_Subscriptions_Checkout::add_shipping( $subscription, $recurring_cart ); // Now update subscription object order_shipping to reflect updated values so it doesn't stay 0 - $subscription->order_shipping = get_post_meta( $subscription->get_id(), '_order_shipping', true ); + $subscription->order_shipping = $subscription->get_meta( '_order_shipping', true ); } /** @@ -2460,12 +2460,10 @@ public static function subscription_switch_autocomplete( $new_order_status, $ord * @deprecated 2.1 */ public static function maybe_set_payment_method( $payment_processing_result, $order_id ) { - _deprecated_function( __METHOD__, '2.1', __CLASS__ . '::maybe_set_payment_method_after_switch( $order )' ); - if ( wcs_order_contains_switch( $order_id ) && false != get_post_meta( $order_id, '_paid_date', true ) ) { - - $order = wc_get_order( $order_id ); + $order = wc_get_order( $order_id ); + if ( wcs_order_contains_switch( $order_id ) && false !== (bool) $order->get_meta( '_paid_date', true ) ) { self::maybe_set_payment_method_after_switch( $order ); } @@ -2808,13 +2806,10 @@ public static function get_first_payment_date( $next_payment_date, $subscription _deprecated_function( __METHOD__, '2.0' ); $subscription = wcs_get_subscription_from_key( $subscription_key ); - if ( $subscription->has_status( 'active' ) && $subscription->get_parent_id() && wcs_order_contains_switch( $subscription->get_parent_id() ) && 1 >= $subscription->get_payment_count() ) { - - $first_payment_timestamp = get_post_meta( $subscription->get_parent_id(), '_switched_subscription_first_payment_timestamp', true ); - - if ( 0 != $first_payment_timestamp ) { - $next_payment_date = ( 'mysql' == $type ) ? gmdate( 'Y-m-d H:i:s', $first_payment_timestamp ) : $first_payment_timestamp; + $first_payment_timestamp = (int) $subscription->get_meta( '_switched_subscription_first_payment_timestamp', true ); + if ( 0 !== $first_payment_timestamp ) { + $next_payment_date = ( 'mysql' === $type ) ? gmdate( 'Y-m-d H:i:s', $first_payment_timestamp ) : $first_payment_timestamp; } } diff --git a/languages/woocommerce-subscriptions.pot b/languages/woocommerce-subscriptions.pot index db5efdd..01f9054 100644 --- a/languages/woocommerce-subscriptions.pot +++ b/languages/woocommerce-subscriptions.pot @@ -2,14 +2,14 @@ # This file is distributed under the same license as the WooCommerce Subscriptions plugin. msgid "" msgstr "" -"Project-Id-Version: WooCommerce Subscriptions 6.8.0\n" +"Project-Id-Version: WooCommerce Subscriptions 6.9.0\n" "Report-Msgid-Bugs-To: https://woocommerce.com/contact-us\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2024-10-14T07:54:53+00:00\n" +"POT-Creation-Date: 2024-11-14T02:05:39+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.11.0\n" "X-Domain: woocommerce-subscriptions\n" @@ -50,14 +50,14 @@ msgstr "" #: includes/admin/class-wcs-admin-reports.php:81 #: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:922 #: includes/api/class-wc-rest-subscriptions-settings.php:29 -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1027 -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1179 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1028 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1180 #: vendor/woocommerce/subscriptions-core/includes/admin/class-wcs-admin-system-status.php:59 #: vendor/woocommerce/subscriptions-core/includes/admin/class-wcs-wc-admin-manager.php:41 #: vendor/woocommerce/subscriptions-core/includes/admin/class-wcs-wc-admin-manager.php:51 #: vendor/woocommerce/subscriptions-core/includes/admin/class-wcs-wc-admin-manager.php:93 -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:376 -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:389 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:392 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:405 #: vendor/woocommerce/subscriptions-core/includes/class-wcs-query.php:108 #: vendor/woocommerce/subscriptions-core/includes/class-wcs-query.php:133 #: vendor/woocommerce/subscriptions-core/includes/class-wcs-query.php:289 @@ -99,14 +99,14 @@ msgid "Report Cache Enabled" msgstr "" #: includes/admin/reports/class-wcs-report-cache-manager.php:316 -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1721 -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1790 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1722 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1791 #: vendor/woocommerce/subscriptions-core/includes/admin/class-wcs-admin-system-status.php:98 msgid "Yes" msgstr "" #: includes/admin/reports/class-wcs-report-cache-manager.php:316 -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1721 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1722 #: vendor/woocommerce/subscriptions-core/includes/admin/class-wcs-admin-system-status.php:98 msgid "No" msgstr "" @@ -1076,7 +1076,7 @@ msgid "Add a Subscription Product" msgstr "" #: includes/class-wc-subscriptions-plugin.php:227 -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:552 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:573 #: vendor/woocommerce/subscriptions-core/includes/upgrades/templates/wcs-about-2-0.php:35 #: vendor/woocommerce/subscriptions-core/includes/upgrades/templates/wcs-about.php:34 msgid "Settings" @@ -1111,8 +1111,8 @@ msgstr "" #: includes/class-wcs-call-to-action-button-text-manager.php:55 #: includes/class-wcs-call-to-action-button-text-manager.php:58 #: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-checkout.php:657 -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:1204 -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:1236 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:1199 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:1231 msgid "Sign up now" msgstr "" @@ -1272,6 +1272,7 @@ msgid "Welcome to WooCommerce Subscriptions %s!" msgstr "" #: includes/class-wcs-upgrade-notice-manager.php:112 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email-notifications.php:345 #: vendor/woocommerce/subscriptions-core/includes/class-wcs-failed-scheduled-action-manager.php:232 msgid "Learn more" msgstr "" @@ -1803,7 +1804,7 @@ msgstr "" #: includes/switching/class-wc-subscriptions-switcher.php:450 #: includes/switching/class-wc-subscriptions-switcher.php:552 -#: includes/switching/class-wc-subscriptions-switcher.php:2693 +#: includes/switching/class-wc-subscriptions-switcher.php:2691 msgid "Upgrade or Downgrade" msgstr "" @@ -1888,21 +1889,21 @@ msgid "Customer added %s." msgstr "" #: includes/switching/class-wc-subscriptions-switcher.php:2417 -#: includes/switching/class-wc-subscriptions-switcher.php:2970 +#: includes/switching/class-wc-subscriptions-switcher.php:2965 msgid "Switch Order" msgstr "" #: includes/switching/class-wc-subscriptions-switcher.php:2432 -#: includes/switching/class-wc-subscriptions-switcher.php:2985 +#: includes/switching/class-wc-subscriptions-switcher.php:2980 msgid "Switched Subscription" msgstr "" -#: includes/switching/class-wc-subscriptions-switcher.php:2650 +#: includes/switching/class-wc-subscriptions-switcher.php:2648 msgctxt "add to cart button text while switching a subscription" msgid "Switch subscription" msgstr "" -#: includes/switching/class-wc-subscriptions-switcher.php:2834 +#: includes/switching/class-wc-subscriptions-switcher.php:2829 #: vendor/woocommerce/subscriptions-core/wcs-functions.php:223 msgctxt "Subscription status" msgid "Switched" @@ -2007,42 +2008,42 @@ msgstr "" #. translators: %s: currency symbol. #. translators: placeholder is a currency symbol / code -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:314 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:315 #: vendor/woocommerce/subscriptions-core/templates/admin/html-variation-price.php:44 msgid "Subscription price (%s)" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:318 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:319 msgctxt "example price" msgid "e.g. 5.90" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:319 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:320 msgid "Subscription interval" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:325 -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:481 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:326 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:482 msgid "Subscription period" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:341 -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:482 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:342 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:483 #: vendor/woocommerce/subscriptions-core/templates/admin/html-variation-price.php:66 msgid "Stop renewing after" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:344 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:345 msgid "Automatically stop renewing the subscription after this length of time. This length is in addition to any free trial or amount of time provided before a synchronised first renewal date." msgstr "" #. translators: %s is a currency symbol / code -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:355 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:356 #: vendor/woocommerce/subscriptions-core/templates/admin/html-variation-price.php:20 msgid "Sign-up fee (%s)" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:356 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:357 #: vendor/woocommerce/subscriptions-core/templates/admin/deprecated/html-variation-price.php:31 #: vendor/woocommerce/subscriptions-core/templates/admin/deprecated/html-variation-price.php:86 #: vendor/woocommerce/subscriptions-core/templates/admin/html-variation-price.php:21 @@ -2051,90 +2052,90 @@ msgctxt "example price" msgid "e.g. 9.90" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:357 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:358 msgid "Optionally include an amount to be charged at the outset of the subscription. The sign-up fee will be charged immediately, even if the product has a free trial or the payment dates are synced." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:371 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:372 #: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-cart.php:2479 #: vendor/woocommerce/subscriptions-core/templates/admin/html-variation-price.php:25 msgid "Free trial" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:374 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:375 #: vendor/woocommerce/subscriptions-core/templates/admin/deprecated/html-variation-price.php:115 msgid "Subscription Trial Period" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:414 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:415 msgid "One time shipping" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:415 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:416 msgid "Shipping for subscription products is normally charged on the initial order and all renewal orders. Enable this to only charge shipping once on the initial order. Note: for this setting to be enabled the subscription must not have a free trial or a synced renewal date." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:478 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:479 msgid "Subscription pricing" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:479 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:480 msgid "Subscription sign-up fee" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:480 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:481 msgid "Subscription billing interval" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:483 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:484 msgid "Free trial length" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:484 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:485 msgid "Free trial period" msgstr "" #. translators: %s: subscription status. -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:807 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:808 msgid "Unable to change subscription status to \"%s\". Please assign a customer to the subscription to activate it." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:861 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:862 msgid "Trashing this order will also trash the subscriptions purchased with the order." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:874 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:875 msgid "Enter the new period, either day, week, month or year:" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:875 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:876 msgid "Enter a new length (e.g. 5):" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:876 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:877 msgid "Enter a new interval as a single number (e.g. to charge every 2nd month, enter 2):" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:877 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:878 msgid "Delete all variations without a subscription" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:883 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:884 msgid "An error occurred determining if that variation can be deleted. Please try again." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:884 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:885 msgid "That variation can not be removed because it is associated with active subscriptions. To remove this variation, please cancel and delete the subscriptions for it." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:889 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:890 msgid "" "You are about to trash one or more orders which contain a subscription.\n" "\n" "Trashing the orders will also trash the subscriptions purchased with these orders." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:897 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:898 msgid "" "WARNING: Bad things are about to happen!\n" "\n" @@ -2143,179 +2144,179 @@ msgid "" "Changes to the billing period, recurring discount, recurring tax or recurring total may not be reflected in the amount charged by the payment gateway." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:898 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:899 msgid "You are deleting a subscription item. You will also need to manually cancel and trash the subscription on the Manage Subscriptions screen." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:905 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:906 msgid "" "Warning: Deleting a user will also delete the user's subscriptions. The user's orders will remain but be reassigned to the 'Guest' user.\n" "\n" "Do you want to continue to delete this user and any associated subscriptions?" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:909 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:910 msgid "PayPal Standard has a number of limitations and does not support all subscription features." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:909 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:910 msgid "Because of this, it is not recommended as a payment method for Subscriptions unless it is the only available option for your country." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:912 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:913 msgid "This action cannot be reversed. Are you sure you wish to erase personal data from the selected subscriptions?" msgstr "" #. translators: placeholders are for HTML tags. They are 1$: "

", 2$: "

", 3$: "

", 4$: "", 5$: "", 6$: "", 7$: "", 8$: "

" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:929 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:930 msgctxt "used in admin pointer script params in javascript as type pointer content" msgid "%1$sChoose Subscription%2$s%3$sThe WooCommerce Subscriptions extension adds two new subscription product types - %4$sSimple subscription%5$s and %6$sVariable subscription%7$s.%8$s" msgstr "" #. translators: placeholders are for HTML tags. They are 1$: "

", 2$: "

", 3$: "

", 4$: "

" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:931 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:932 msgctxt "used in admin pointer script params in javascript as price pointer content" msgid "%1$sSet a Price%2$s%3$sSubscription prices are a little different to other product prices. For a subscription, you can set a billing period, length, sign-up fee and free trial.%4$s" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:957 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:958 msgid "Active subscriber?" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1001 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1002 msgid "Manage Subscriptions" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1005 -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:385 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1006 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:401 msgid "Search Subscriptions" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1231 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1232 msgctxt "options section heading" msgid "Miscellaneous" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1238 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1239 msgid "Mixed Checkout" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1239 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1240 msgid "Allow multiple subscriptions and products to be purchased simultaneously." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1243 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1244 msgid "Allow a subscription product to be purchased with other products and subscriptions in the same transaction." msgstr "" #. translators: placeholder is a number #. translators: placeholder is a subscription ID. -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1345 -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1587 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1346 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1588 msgid "We can't find a subscription with ID #%d. Perhaps it was deleted?" msgstr "" #. translators: Placeholders are opening and closing link tags. -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1438 -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1500 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1439 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1501 msgid "We weren't able to locate the set of report results you requested. Please regenerate the link from the %1$sSubscription Reports screen%2$s." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1555 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1556 msgid "We can't find a paid subscription order for this user." msgstr "" #. translators: placeholders are opening link tag, ID of sub, and closing link tag -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1594 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1595 msgid "Showing orders for %1$sSubscription %2$s%3$s" msgstr "" #. translators: number of 1$: days, 2$: weeks, 3$: months, 4$: years -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1617 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1618 msgid "The trial period can not exceed: %1$s, %2$s, %3$s or %4$s." msgstr "" #. translators: placeholder is a time period (e.g. "4 weeks") -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1622 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1623 msgid "The trial period can not exceed %s." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1647 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1648 msgid "Please log in to your account to view your subscriptions." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1684 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1685 msgid "No subscriptions found for that customer." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1686 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1687 msgid "You do not have permission to view those subscriptions." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1720 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1721 #: vendor/woocommerce/subscriptions-core/includes/admin/class-wcs-admin-system-status.php:96 msgctxt "label that indicates whether debugging is turned on for the plugin" msgid "WCS_DEBUG" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1726 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1727 #: vendor/woocommerce/subscriptions-core/includes/admin/class-wcs-admin-system-status.php:110 msgctxt "Live or Staging, Label on WooCommerce -> System Status page" msgid "Subscriptions Mode" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1727 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1728 #: vendor/woocommerce/subscriptions-core/includes/admin/class-wcs-admin-system-status.php:112 msgctxt "refers to staging site" msgid "Staging" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1727 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1728 #: vendor/woocommerce/subscriptions-core/includes/admin/class-wcs-admin-system-status.php:112 msgctxt "refers to live site" msgid "Live" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1757 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1758 msgid "Automatic Recurring Payments" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1790 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1791 msgid "Supports automatic renewal payments." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1871 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1872 msgid "Subscription items can no longer be edited." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1876 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1877 msgid "This subscription is no longer editable because the payment gateway does not allow modification of recurring amounts." msgstr "" #. translators: $1-2: opening and closing tags of a link that takes to Woo marketplace / Stripe product page -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1895 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1896 msgid "No payment gateways capable of processing automatic subscription payments are enabled. If you would like to process automatic payments, we recommend the %1$sfree Stripe extension%2$s." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1902 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1903 msgid "Recurring Payments" msgstr "" #. translators: placeholders are opening and closing link tags -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1910 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:1911 msgid "Payment gateways which don't support automatic recurring payments can be used to process %1$smanual subscription renewal payments%2$s." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:2030 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:2031 msgid "Note that purchasing a subscription still requires an account." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:2044 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:2045 msgid "The product type can not be changed because this product is associated with subscriptions." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:2101 #: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:2102 +#: vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php:2103 msgid "Allow subscription customers to create an account during checkout" msgstr "" @@ -2490,7 +2491,7 @@ msgid "Status" msgstr "" #: vendor/woocommerce/subscriptions-core/includes/admin/class-wcs-admin-post-types.php:469 -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:377 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:393 #: vendor/woocommerce/subscriptions-core/templates/emails/cancelled-subscription.php:21 #: vendor/woocommerce/subscriptions-core/templates/emails/expired-subscription.php:21 #: vendor/woocommerce/subscriptions-core/templates/emails/on-hold-subscription.php:21 @@ -2677,12 +2678,12 @@ msgid "Delete Permanently" msgstr "" #: vendor/woocommerce/subscriptions-core/includes/admin/class-wcs-admin-post-types.php:1406 -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:768 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:762 msgid "Restore this item from the Trash" msgstr "" #: vendor/woocommerce/subscriptions-core/includes/admin/class-wcs-admin-post-types.php:1408 -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:769 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:763 msgid "Restore" msgstr "" @@ -2802,6 +2803,65 @@ msgstr "" msgid "Edit Subscription" msgstr "" +#: vendor/woocommerce/subscriptions-core/includes/admin/debug-tools/class-wcs-notifications-debug-tool-processor.php:274 +#: vendor/woocommerce/subscriptions-core/includes/class-wcs-notifications-batch-processor.php:253 +msgid "Background process for updating subscription notifications already started, nothing done." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/admin/debug-tools/class-wcs-notifications-debug-tool-processor.php:279 +msgid "Background process for updating subscription notifications started" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/admin/debug-tools/class-wcs-notifications-debug-tool-processor.php:290 +#: vendor/woocommerce/subscriptions-core/includes/class-wcs-notifications-batch-processor.php:268 +msgid "Background process for updating subscription notifications not started, nothing done." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/admin/debug-tools/class-wcs-notifications-debug-tool-processor.php:294 +msgid "Background process for updating subscription notifications stopped" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/admin/debug-tools/class-wcs-notifications-debug-tool-processor.php:308 +#: vendor/woocommerce/subscriptions-core/includes/admin/debug-tools/class-wcs-notifications-debug-tool-processor.php:330 +#: vendor/woocommerce/subscriptions-core/includes/admin/debug-tools/class-wcs-notifications-debug-tool-processor.php:340 +msgid "Regenerate subscription notifications" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/admin/debug-tools/class-wcs-notifications-debug-tool-processor.php:309 +#: vendor/woocommerce/subscriptions-core/includes/admin/debug-tools/class-wcs-notifications-debug-tool-processor.php:341 +msgid "Regenerate notifications" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/admin/debug-tools/class-wcs-notifications-debug-tool-processor.php:313 +msgid "This tool will add notifications to pending, active, and on-hold subscriptions. These updates will occur gradually in the background using Action Scheduler." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/admin/debug-tools/class-wcs-notifications-debug-tool-processor.php:314 +#: vendor/woocommerce/subscriptions-core/templates/emails/email-order-details.php:61 +msgid "Note:" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/admin/debug-tools/class-wcs-notifications-debug-tool-processor.php:315 +msgid "Notifications are currently turned off. To activate them, check the \"Enable customer renewal reminder notification emails.\" option (via WooCommerce > Settings > Subscriptions > Customer Notifications)." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/admin/debug-tools/class-wcs-notifications-debug-tool-processor.php:317 +msgid "Manage settings." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/admin/debug-tools/class-wcs-notifications-debug-tool-processor.php:331 +msgid "Stop regenerating notifications" +msgstr "" + +#. translators: %1$d=count of total entries needing conversion +#: vendor/woocommerce/subscriptions-core/includes/admin/debug-tools/class-wcs-notifications-debug-tool-processor.php:334 +msgid "Stopping this will halt the background process that adds notifications to pending, active, and on-hold subscriptions. %1$d subscriptions remain to be processed." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/admin/debug-tools/class-wcs-notifications-debug-tool-processor.php:342 +msgid "This tool will regenerate notifications to pending, active, and on-hold subscriptions. These updates will occur gradually in the background using Action Scheduler." +msgstr "" + #: vendor/woocommerce/subscriptions-core/includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php:97 msgctxt "relation to order" msgid "Subscription" @@ -3033,7 +3093,7 @@ msgstr "" #. translators: placeholder is human time diff (e.g. "3 weeks") #: vendor/woocommerce/subscriptions-core/includes/class-wc-subscription.php:1314 -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-manager.php:2405 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-manager.php:2402 msgid "In %s" msgstr "" @@ -3093,7 +3153,7 @@ msgid "The \"all\" value for $order_type parameter is deprecated. It was a misno msgstr "" #: vendor/woocommerce/subscriptions-core/includes/class-wc-subscription.php:2247 -#: vendor/woocommerce/subscriptions-core/wcs-functions.php:834 +#: vendor/woocommerce/subscriptions-core/wcs-functions.php:853 msgid "Payment method meta must be an array." msgstr "" @@ -3198,7 +3258,11 @@ msgstr "" msgid "Your cart has been emptied of subscription products. Only one subscription product can be purchased at a time." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-cart-validator.php:130 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-cart-validator.php:137 +msgid "That product can not be added to your cart as it already contains a subscription renewal." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-cart-validator.php:179 #: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-cart.php:1539 msgid "That subscription product can not be added to your cart as it already contains a subscription renewal." msgstr "" @@ -3404,53 +3468,53 @@ msgstr "" msgid "Purchasing a subscription product requires an account. Please go to the %1$sMy Account%2$s page to login or contact us if you need assistance." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:378 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:394 msgctxt "custom post type setting" msgid "Add Subscription" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:379 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:395 msgctxt "custom post type setting" msgid "Add New Subscription" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:380 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:396 msgctxt "custom post type setting" msgid "Edit" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:381 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:397 msgctxt "custom post type setting" msgid "Edit Subscription" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:382 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:398 msgctxt "custom post type setting" msgid "New Subscription" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:383 -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:384 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:399 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:400 msgctxt "custom post type setting" msgid "View Subscription" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:387 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:403 msgctxt "custom post type setting" msgid "No Subscriptions found in trash" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:388 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:404 msgctxt "custom post type setting" msgid "Parent Subscriptions" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:391 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:407 msgid "This is where subscriptions are stored." msgstr "" #. translators: placeholder is a post count. -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:449 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:465 msgctxt "post status label including post count" msgid "Active (%s)" msgid_plural "Active (%s)" @@ -3458,7 +3522,7 @@ msgstr[0] "" msgstr[1] "" #. translators: placeholder is a post count. -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:451 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:467 msgctxt "post status label including post count" msgid "Switched (%s)" msgid_plural "Switched (%s)" @@ -3466,7 +3530,7 @@ msgstr[0] "" msgstr[1] "" #. translators: placeholder is a post count. -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:453 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:469 msgctxt "post status label including post count" msgid "Expired (%s)" msgid_plural "Expired (%s)" @@ -3474,29 +3538,29 @@ msgstr[0] "" msgstr[1] "" #. translators: placeholder is a post count. -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:455 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:471 msgctxt "post status label including post count" msgid "Pending Cancellation (%s)" msgid_plural "Pending Cancellation (%s)" msgstr[0] "" msgstr[1] "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:504 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:520 msgid "Variable Subscription" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:553 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:574 #: vendor/woocommerce/subscriptions-core/includes/upgrades/templates/wcs-about-2-0.php:36 msgctxt "short for documents" msgid "Docs" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:554 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:575 msgid "Support" msgstr "" #. translators: placeholders are opening and closing tags. Leads to docs on upgrading WooCommerce Subscriptions -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:577 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:598 msgid "Warning! Version 2.0 is a major update to the WooCommerce Subscriptions extension. Before updating, please create a backup, update all WooCommerce extensions and test all plugins, custom code and payment gateways with version 2.0 on a staging site. %1$sLearn more about updating older versions of WooCommerce Subscriptions »%2$s" msgstr "" @@ -3575,6 +3639,61 @@ msgstr "" msgid "Discount" msgstr "" +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email-notifications.php:237 +msgid "Send trial is ending notification" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email-notifications.php:241 +msgid "Send upcoming subscription expiration notification" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email-notifications.php:245 +msgid "Send upcoming renewal notification" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email-notifications.php:261 +msgid "Customer Notifications" +msgstr "" + +#. translators: Link to WC Settings > Email. +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email-notifications.php:265 +msgid "To enable/disable individual notifications and customize templates, visit the Email settings." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email-notifications.php:268 +msgid "Enable Reminders" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email-notifications.php:269 +msgid "Send notification emails to customers for subscription renewals and expirations." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email-notifications.php:278 +msgid "Reminder Timing" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email-notifications.php:279 +msgid "How long before the event should the notification be sent." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email-notifications.php:284 +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php:60 +#: vendor/woocommerce/subscriptions-core/includes/privacy/class-wcs-privacy.php:272 +msgid "N/A" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email-notifications.php:333 +msgid "WooCommerce Subscriptions: Introducing customer email notifications!" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email-notifications.php:334 +msgid "You can now send email notifications for subscription renewals, expirations, and free trials. Go to the \"Customer Notifications\" settings section to configure when your customers receive these important updates." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email-notifications.php:340 +msgid "Manage settings" +msgstr "" + #: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-extend-store-endpoint.php:185 msgid "Subscription Product length." msgstr "" @@ -3762,7 +3881,7 @@ msgstr "" #: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-manager.php:114 #: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-manager.php:2019 -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-manager.php:2037 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-manager.php:2034 msgctxt "used in order note as reason for why subscription status changed" msgid "Subscription renewal payment due:" msgstr "" @@ -3882,23 +4001,23 @@ msgid "Change" msgstr "" #. translators: placeholder is subscription ID -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-manager.php:2287 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-manager.php:2284 msgid "Failed sign-up for subscription %s." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-manager.php:2378 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-manager.php:2375 msgid "Invalid security token, please reload the page and try again." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-manager.php:2382 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-manager.php:2379 msgid "Only store managers can edit payment dates." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-manager.php:2386 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-manager.php:2383 msgid "Please enter all date fields." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-manager.php:2411 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-manager.php:2408 msgid "Date Changed" msgstr "" @@ -3924,65 +4043,65 @@ msgid "Payment completed on order after subscription was cancelled." msgstr "" #. translators: $1: opening link tag, $2: order number, $3: closing link tag -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:1110 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:1111 msgid "Subscription cancelled for refunded order %1$s#%2$s%3$s." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:2406 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:2407 msgctxt "An order type" msgid "Original" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:2407 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:2408 msgctxt "An order type" msgid "Subscription Parent" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:2408 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:2409 msgctxt "An order type" msgid "Subscription Renewal" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:2409 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:2410 msgctxt "An order type" msgid "Subscription Resubscribe" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:2410 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:2411 msgctxt "An order type" msgid "Subscription Switch" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:2411 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:2412 msgctxt "An order type" msgid "Non-subscription" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:2420 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:2421 msgid "All orders types" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:2447 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:2448 msgid "Renewal Order" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:2449 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:2450 msgid "Resubscribe Order" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:2451 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php:2452 msgid "Parent Order" msgstr "" #. translators: %1$s refers to the price. This string is meant to prefix another string below, e.g. "$5 now, and $5 on March 15th each year" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:293 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:287 msgid "%1$s now, and " msgstr "" #. translators: 1$: recurring amount string, 2$: day of the week (e.g. "$10 every Wednesday"). #. translators: 1$: recurring amount string, 2$: day of the week (e.g. "$10 every Wednesday") #. translators: %1$: recurring amount (e.g. "$15"), %2$: subscription period (e.g. "month") (e.g. "$15 every 2nd month") -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:302 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:296 #: vendor/woocommerce/subscriptions-core/includes/wcs-formatting-functions.php:116 #: vendor/woocommerce/subscriptions-core/includes/wcs-formatting-functions.php:201 msgid "%1$s every %2$s" @@ -3990,56 +4109,56 @@ msgstr "" #. translators: 1$: recurring amount string, 2$: period, 3$: day of the week (e.g. "$10 every 2nd week on Wednesday"). #. translators: 1$: recurring amount string, 2$: period, 3$: day of the week (e.g. "$10 every 2nd week on Wednesday") -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:306 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:300 #: vendor/woocommerce/subscriptions-core/includes/wcs-formatting-functions.php:125 msgid "%1$s every %2$s on %3$s" msgstr "" #. translators: placeholder is recurring amount. #. translators: placeholder is recurring amount -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:317 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:311 #: vendor/woocommerce/subscriptions-core/includes/wcs-formatting-functions.php:143 msgid "%s on the last day of each month" msgstr "" #. translators: 1$: recurring amount, 2$: day of the month (e.g. "23rd") (e.g. "$5 every 23rd of each month"). #. translators: 1$: recurring amount, 2$: day of the month (e.g. "23rd") (e.g. "$5 every 23rd of each month") -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:321 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:315 #: vendor/woocommerce/subscriptions-core/includes/wcs-formatting-functions.php:146 msgid "%1$s on the %2$s of each month" msgstr "" #. translators: 1$: recurring amount, 2$: interval (e.g. "3rd") (e.g. "$10 on the last day of every 3rd month"). #. translators: 1$: recurring amount, 2$: interval (e.g. "3rd") (e.g. "$10 on the last day of every 3rd month") -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:330 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:324 #: vendor/woocommerce/subscriptions-core/includes/wcs-formatting-functions.php:162 msgid "%1$s on the last day of every %2$s month" msgstr "" #. translators: 1$: on the, 2$: day of every, 3$: month (e.g. "$10 on the 23rd day of every 2nd month"). #. translators: 1$: recurring amount, 2$: day of the month (e.g. "23rd") (e.g. "$5 every 23rd of each month") -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:337 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:331 #: vendor/woocommerce/subscriptions-core/includes/wcs-formatting-functions.php:165 msgid "%1$s on the %2$s day of every %3$s month" msgstr "" #. translators: 1$: on, 2$: , 3$: each year (e.g. "$15 on March 15th each year"). #. translators: 1$: recurring amount, 2$: month (e.g. "March"), 3$: day of the month (e.g. "23rd") (e.g. "$15 on March 15th every 3rd year") -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:349 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:343 #: vendor/woocommerce/subscriptions-core/includes/wcs-formatting-functions.php:178 msgid "%1$s on %2$s %3$s each year" msgstr "" #. translators: 1$: recurring amount, 2$: month (e.g. "March"), 3$: day of the month (e.g. "23rd"). #. translators: 1$: recurring amount, 2$: month (e.g. "March"), 3$: day of the month (e.g. "23rd") (e.g. "$15 on March 15th every 3rd year") -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:357 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:351 #: vendor/woocommerce/subscriptions-core/includes/wcs-formatting-functions.php:187 msgid "%1$s on %2$s %3$s every %4$s year" msgstr "" #. 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" or "3 months") (e.g. "$15 / month" or "$15 every 2nd month") -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:369 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:363 #: vendor/woocommerce/subscriptions-core/includes/wcs-formatting-functions.php:198 msgid "%1$s / %2$s" msgid_plural "%1$s every %2$s" @@ -4047,28 +4166,28 @@ msgstr[0] "" msgstr[1] "" #. translators: billing period (e.g. "every week"). -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:379 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:373 msgid "every %s" msgstr "" #. translators: 1$: subscription string (e.g. "$10 up front then $5 on March 23rd every 3rd year"), 2$: length (e.g. "4 years"). #. translators: 1$: subscription string (e.g. "$10 up front then $5 on March 23rd every 3rd year"), 2$: length (e.g. "4 years") -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:389 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:383 #: vendor/woocommerce/subscriptions-core/includes/wcs-formatting-functions.php:209 msgid "%1$s for %2$s" msgstr "" #. translators: 1$: subscription string (e.g. "$15 on March 15th every 3 years for 6 years"), 2$: trial length (e.g.: "with 4 months free trial"). -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:395 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:389 msgid "%1$s with %2$s free trial" msgstr "" #. translators: 1$: subscription string (e.g. "$15 on March 15th every 3 years for 6 years with 2 months free trial"), 2$: signup fee price (e.g. "and a $30 sign-up fee"). -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:400 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:394 msgid "%1$s and a %2$s sign-up fee" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:977 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php:972 msgid "This variation can not be removed because it is associated with existing subscriptions. To remove this variation, please permanently delete any related subscriptions." msgstr "" @@ -4140,40 +4259,40 @@ msgstr "" msgid "Subscriptions created within this many days prior to the Renewal Day will not be charged at sign-up. Set to zero for all new Subscriptions to be charged the full recurring amount. Must be a positive number." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php:307 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php:308 msgid "Month for Synchronisation" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php:753 -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php:770 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php:754 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php:771 msgid "Do not synchronise" msgstr "" #. translators: placeholder is a day of the week -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php:778 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php:779 msgid "%s each week" msgstr "" #. translators: placeholder is a number of day with language specific suffix applied (e.g. "1st", "3rd", "5th", etc...) -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php:784 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php:785 msgid "%s day of the month" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php:786 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php:787 msgid "Last day of the month" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php:834 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php:835 msgid "Today!" msgstr "" #. translators: placeholder is a date -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php:841 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php:842 msgid "First payment prorated. Next payment: %s" msgstr "" #. translators: placeholder is a date -#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php:844 +#: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php:845 msgid "First payment: %s" msgstr "" @@ -4247,7 +4366,7 @@ msgctxt "Used in WooCommerce by removed item notification: \"_All linked subscri msgid "All linked subscription items were" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/class-wcs-cart-renewal.php:1548 +#: vendor/woocommerce/subscriptions-core/includes/class-wcs-cart-renewal.php:1554 msgctxt "The place order button text while renewing a subscription" msgid "Renew subscription" msgstr "" @@ -4365,6 +4484,14 @@ msgstr "" msgid "That payment method cannot be deleted because it is linked to an automatic subscription. Please choose a %1$sdefault%2$s payment method%3$s, before trying again." msgstr "" +#: vendor/woocommerce/subscriptions-core/includes/class-wcs-notifications-batch-processor.php:257 +msgid "Background process for updating subscription notifications started." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/class-wcs-notifications-batch-processor.php:272 +msgid "Background process for updating subscription notifications stopped." +msgstr "" + #. translators: 1$-2$: opening and closing tags. #: vendor/woocommerce/subscriptions-core/includes/class-wcs-permalink-manager.php:91 msgid "Error saving Subscriptions endpoints: %1$sSubscriptions%2$s, %1$sView subscription%2$s and %1$sSubscription payment method%2$s cannot be the same. The changes have been reverted." @@ -4682,6 +4809,7 @@ msgid "Email type" msgstr "" #: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-cancelled-subscription.php:177 +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php:68 #: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-expired-subscription.php:173 #: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-on-hold-subscription.php:173 msgid "Choose which format of email to send." @@ -4762,6 +4890,176 @@ msgstr "" msgid "Your {blogname} subscription change from {order_date} is complete - download your files" msgstr "" +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-auto-renewal.php:25 +msgid "Customer Notification: Automatic renewal notice" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-auto-renewal.php:26 +msgid "Customer Notification: Automatic renewal notice emails are sent when customer's subscription is about to be renewed automatically." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-auto-renewal.php:28 +msgid "Automatic renewal notice" +msgstr "" + +#. translators: $1: {site_title}, $2: {customers_first_name}, $3: {time_until_renewal}, variables that will be substituted when email is sent out +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-auto-renewal.php:32 +msgctxt "default email subject for subscription's automatic renewal notice" +msgid "[%1$s] %2$s, your subscription automatically renews in %3$s!" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-auto-renewal.php:58 +msgid "Thank you for being a loyal customer, {customers_first_name} — we appreciate your business." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-auto-trial-expiration.php:25 +msgid "Customer Notification: Free trial expiration: automatic payment notice" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-auto-trial-expiration.php:26 +msgid "Free trial expiry notification emails are sent when customer's free trial for an automatically renewd subscription is about to expire." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-auto-trial-expiration.php:28 +msgid "Free trial expiration: automatic payment notice" +msgstr "" + +#. translators: $1: {site_title}, $2: {customers_first_name}, variables that will be substituted when email is sent out +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-auto-trial-expiration.php:30 +msgctxt "default email subject for free trial expiry notification emails sent to the customer" +msgid "[%1$s] %2$s, your paid subscription starts soon!" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-manual-renewal.php:25 +msgid "Customer Notification: Manual renewal notice" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-manual-renewal.php:26 +msgid "Customer Notification: Manual renewal notice are sent when customer's subscription needs to be manually renewed." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-manual-renewal.php:28 +msgid "Manual renewal notice" +msgstr "" + +#. translators: $1: {site_title}, $2: {customers_first_name}, variables that will be substituted when email is sent out +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-manual-renewal.php:30 +msgctxt "default email subject for notification for a manually renewed subscription sent to the customer" +msgid "[%1$s] %2$s, your subscription is ready to be renewed!" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-manual-renewal.php:52 +msgid "Thanks again for choosing {site_title}." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-manual-trial-expiration.php:26 +msgid "Customer Notification: Free trial expiration: manual payment required" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-manual-trial-expiration.php:27 +msgid "Free trial expiry notification emails are sent when customer's free trial for a manually renewed subscription is about to expire." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-manual-trial-expiration.php:29 +msgid "Free trial expiration: manual payment required" +msgstr "" + +#. translators: $1: {site_title}, $2: {customers_first_name}, variables that will be substituted when email is sent out. +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-manual-trial-expiration.php:31 +msgctxt "default email subject for an email notification for a manually renewed subscription with free trial expiry emails sent to the customer" +msgid "[%1$s] %2$s, your free trial is almost up!" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-subscription-expiration.php:25 +msgid "Customer Notification: Subscription expiration notice" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-subscription-expiration.php:26 +msgid "Subscription expiration notification emails are sent when customer's subscription is about to expire." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-subscription-expiration.php:28 +msgid "Subscription expiration notice" +msgstr "" + +#. translators: $1: {site_title}, $2: {customers_first_name}, variables that will be substituted when email is sent out +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-subscription-expiration.php:30 +msgctxt "default email subject for subscription expiry notification email sent to the customer" +msgid "[%1$s] %2$s, your subscription is about to expire!" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-subscription-expiration.php:52 +msgid "Thank you for choosing {site_title}, {customers_first_name}." +msgstr "" + +#. translators: %s: list of placeholders +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php:32 +msgid "Available placeholders: %s" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php:35 +msgid "Enable/Disable" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php:37 +msgid "Enable this email notification. Disabled automatically on staging sites." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php:41 +msgid "Subject" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php:49 +msgid "Email heading" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php:57 +msgid "Additional content" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php:58 +msgid "Text to appear below the main email content." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php:66 +msgid "Email type" +msgstr "" + +#. translators: 1: Notification type, 2: customer's email. +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php:101 +msgid "%1$s was successfully sent to %2$s." +msgstr "" + +#. translators: 1: Notification type, 2: customer's email. +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php:104 +msgid "Attempt to send %1$s to %2$s failed." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php:253 +msgid "Thank you for choosing {site_title}!" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php:273 +msgid "Reminder emails disabled." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php:276 +msgid "Not a production site" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php:280 +msgid "Recipient not found" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php:284 +msgid "Subscription billing cycle too short" +msgstr "" + +#. translators: %1$s: email title, %2$s: list of reasons why email was skipped. +#: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php:290 +msgid "Skipped sending \"%1$s\": %2$s" +msgstr "" + #: vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-on-hold-renewal-order.php:23 msgid "On-hold Renewal Order" msgstr "" @@ -5087,40 +5385,40 @@ msgid "IPN subscription sign up completed." msgstr "" #: vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:332 -#: vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:417 +#: vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:416 msgid "IPN subscription payment completed." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:379 +#: vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:378 msgid "IPN subscription failing payment method changed." msgstr "" #. translators: placeholder is payment status (e.g. "completed") -#: vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:427 -#: vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:436 +#: vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:426 +#: vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:435 msgctxt "used in order note" msgid "IPN subscription payment %s." msgstr "" #. translators: 1: payment status (e.g. "completed"), 2: pending reason. -#: vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:440 +#: vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:439 msgctxt "used in order note" msgid "IPN subscription payment %1$s for reason: %2$s." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:469 +#: vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:468 msgid "IPN subscription suspended." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:492 +#: vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:491 msgid "IPN subscription cancelled." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:508 +#: vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:507 msgid "IPN subscription payment failure." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:650 +#: vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php:649 msgid "Invalid PayPal IPN Payload: unable to find matching subscription." msgstr "" @@ -5293,55 +5591,60 @@ msgstr "" msgid "Retain ended subscriptions and their related orders for a specified duration before anonymizing the personal data within them." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/privacy/class-wcs-privacy.php:272 -msgid "N/A" -msgstr "" - #: vendor/woocommerce/subscriptions-core/includes/privacy/class-wcs-privacy.php:313 msgid "Customers with a subscription are excluded from this setting." msgstr "" +#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:184 +msgid "A database upgrade is required to use Subscriptions. Upgrades from the previously installed version is no longer supported. You will need to install an older version of WooCommerce Subscriptions or WooCommerce Payments to proceed with the upgrade before you can use a newer version." +msgstr "" + +#. translators: 1-2: opening/closing tags, 3: active version of Subscriptions, 4: current version of Subscriptions, 5-6: opening/closing tags linked to ticket form, 7-8: opening/closing tags linked to documentation. +#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:246 +msgid "%1$sWarning!%2$s It appears that you have downgraded %1$sWooCommerce Subscriptions%2$s from %3$s to %4$s. Downgrading the plugin in this way may cause issues. Please update to %3$s or higher, or %5$sopen a new support ticket%6$s for further assistance. %7$sLearn more »%8$s" +msgstr "" + #. translators: placeholder is a list of version numbers (e.g. "1.3 & 1.4 & 1.5") -#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:360 +#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:385 msgid "Database updated to version %s" msgstr "" #. translators: placeholder is number of upgraded subscriptions -#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:368 +#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:393 msgctxt "used in the subscriptions upgrader" msgid "Marked %s subscription products as \"sold individually\"." msgstr "" #. translators: 1$: number of action scheduler hooks upgraded, 2$: "{execution_time}", will be replaced on front end with actual time -#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:377 +#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:402 msgid "Migrated %1$s subscription related hooks to the new scheduler (in %2$s seconds)." msgstr "" #. translators: 1$: number of subscriptions upgraded, 2$: "{execution_time}", will be replaced on front end with actual time it took -#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:389 +#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:414 msgid "Migrated %1$s subscriptions to the new structure (in %2$s seconds)." msgstr "" #. translators: placeholder is "{time_left}", will be replaced on front end with actual time -#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:392 -#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:438 +#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:417 +#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:463 msgctxt "Message that gets sent to front end." msgid "Estimated time left (minutes:seconds): %s" msgstr "" #. translators: 1$: error message, 2$: opening link tag, 3$: closing link tag, 4$: break tag -#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:402 +#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:427 msgid "Unable to upgrade subscriptions.%4$sError: %1$s%4$sPlease refresh the page and try again. If problem persists, %2$scontact support%3$s." msgstr "" #. translators: placeholder is the number of subscriptions repaired -#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:417 +#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:442 msgctxt "Repair message that gets sent to front end." msgid "Repaired %d subscriptions with incorrect dates, line tax data or missing customer notes." msgstr "" #. translators: placeholder is number of subscriptions that were checked and did not need repairs. There's a space at the beginning! -#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:423 +#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:448 msgctxt "Repair message that gets sent to front end." msgid " %d other subscription was checked and did not need any repairs." msgid_plural "%d other subscriptions were checked and did not need any repairs." @@ -5349,46 +5652,41 @@ msgstr[0] "" msgstr[1] "" #. translators: placeholder is "{execution_time}", which will be replaced on front end with actual time -#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:427 +#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:452 msgctxt "Repair message that gets sent to front end." msgid "(in %s seconds)" msgstr "" #. translators: $1: "Repaired x subs with incorrect dates...", $2: "X others were checked and no repair needed", $3: "(in X seconds)". Ordering for RTL languages. -#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:430 +#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:455 msgctxt "The assembled repair message that gets sent to front end." msgid "%1$s%2$s %3$s" msgstr "" #. translators: 1$: error message, 2$: opening link tag, 3$: closing link tag, 4$: break tag -#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:449 +#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:474 msgctxt "Error message that gets sent to front end when upgrading Subscriptions" msgid "Unable to repair subscriptions.%4$sError: %1$s%4$sPlease refresh the page and try again. If problem persists, %2$scontact support%3$s." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:653 +#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:681 msgid "Welcome to WooCommerce Subscriptions 2.1" msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:653 +#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:681 msgid "About WooCommerce Subscriptions" msgstr "" #. translators: 1-2: opening/closing tags, 3-4: opening/closing tags linked to ticket form. -#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:884 +#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:915 msgid "%1$sWarning!%2$s We discovered an issue in %1$sWooCommerce Subscriptions 2.3.0 - 2.3.2%2$s that may cause your subscription renewal order and customer subscription caches to contain invalid data. For information about how to update the cached data, please %3$sopen a new support ticket%4$s." msgstr "" -#. translators: 1-2: opening/closing tags, 3: active version of Subscriptions, 4: current version of Subscriptions, 5-6: opening/closing tags linked to ticket form, 7-8: opening/closing tags linked to documentation. -#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php:963 -msgid "%1$sWarning!%2$s It appears that you have downgraded %1$sWooCommerce Subscriptions%2$s from %3$s to %4$s. Downgrading the plugin in this way may cause issues. Please update to %3$s or higher, or %5$sopen a new support ticket%6$s for further assistance. %7$sLearn more »%8$s" -msgstr "" - -#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-suspended-paypal-subscriptions.php:51 +#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-suspended-paypal-subscriptions.php:54 msgid "Subscription suspended by Database repair script. This subscription was suspended via PayPal." msgstr "" -#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-2-2-7.php:60 +#: vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-2-2-7.php:63 msgid "Subscription end date in the past" msgstr "" @@ -5944,7 +6242,7 @@ msgid "First renewal: %s" msgstr "" #. translators: placeholder is either subscription key or a subscription id, or, failing that, empty (e.g. "145_21" or "145") -#: vendor/woocommerce/subscriptions-core/includes/wcs-deprecated-functions.php:180 +#: vendor/woocommerce/subscriptions-core/includes/wcs-deprecated-functions.php:179 msgid "Could not get subscription. Most likely the subscription key does not refer to a subscription. The key was: \"%s\"." msgstr "" @@ -6497,6 +6795,96 @@ msgstr "" msgid "You have successfully changed your subscription items. Your new order and subscription details are shown below for your reference:" msgstr "" +#. translators: %s: Customer first name +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-auto-renewal.php:24 +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-manual-renewal.php:24 +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-auto-renewal.php:19 +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-manual-renewal.php:19 +msgid "Hi %s." +msgstr "" + +#. translators: %1$s: human readable time difference (eg 3 days, 1 day), %2$s: date in local format. +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-auto-renewal.php:37 +msgid "Your subscription will automatically renew in %1$s — that’s %2$s." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-auto-renewal.php:51 +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-auto-trial-ending.php:65 +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-expiring-subscription.php:51 +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-manual-renewal.php:92 +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-auto-renewal.php:40 +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-auto-trial-ending.php:46 +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-expiring-subscription.php:40 +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-manual-renewal.php:61 +msgid "Here are the details:" +msgstr "" + +#. translators: %s: link to subscription detail in the customer's dashboard. +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-auto-renewal.php:67 +msgid "You can manage this subscription from your %s" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-auto-renewal.php:68 +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-auto-trial-ending.php:56 +msgid "account dashboard" +msgstr "" + +#. translators: %s: Customer first name +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-auto-trial-ending.php:24 +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-auto-trial-ending.php:19 +msgid "Hi, %s." +msgstr "" + +#. translators: %1$s: human readable time difference (eg 3 days, 1 day), %2$s: date in local format. +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-auto-trial-ending.php:37 +msgid "Your paid subscription begins when your free trial expires in %1$s — that’s %2$s." +msgstr "" + +#. translators: %1$s: link to account dashboard. +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-auto-trial-ending.php:55 +msgid "Payment will be deducted using the payment method on file. You can manage this subscription from your %1$s." +msgstr "" + +#. translators: %s: Customer first name +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-expiring-subscription.php:24 +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-manual-trial-ending.php:24 +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-expiring-subscription.php:19 +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-manual-trial-ending.php:19 +msgid "Heads up, %s." +msgstr "" + +#. translators: %1$s: human readable time difference (eg 3 days, 1 day), %2$s: date in local format. +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-expiring-subscription.php:37 +msgid "Your subscription expires in %1$s — that’s %2$s." +msgstr "" + +#. translators: %1$s: human readable time difference (eg 3 days, 1 day), %2$s: date in local format. +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-manual-renewal.php:37 +msgid "Your subscription is up for renewal in %1$s — that’s %2$s." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-manual-renewal.php:52 +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-manual-renewal.php:40 +msgid "This subscription will not renew automatically." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-manual-renewal.php:58 +msgid "You can renew it manually in a few short steps via the Subscriptions tab in your account dashboard." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-manual-renewal.php:75 +msgid "Renew Subscription" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-manual-renewal.php:77 +msgid "Manage Subscription" +msgstr "" + +#. translators: %1$s: human readable time difference (eg 3 days, 1 day), %2$s: date in local format. +#: vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-manual-trial-ending.php:37 +msgid "Your free trial expires in %1$s — that’s %2$s." +msgstr "" + #: vendor/woocommerce/subscriptions-core/templates/emails/customer-on-hold-renewal-order.php:18 #: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-on-hold-renewal-order.php:17 msgid "Thanks for your renewal order. It’s on-hold until we confirm that payment has been received. In the meantime, here’s a reminder of your order:" @@ -6552,10 +6940,6 @@ msgctxt "Used in email notification" msgid "Subscription %1$s#%2$s%3$s" msgstr "" -#: vendor/woocommerce/subscriptions-core/templates/emails/email-order-details.php:61 -msgid "Note:" -msgstr "" - #. translators: $1: customer's billing first name and last name #: vendor/woocommerce/subscriptions-core/templates/emails/expired-subscription.php:16 #: vendor/woocommerce/subscriptions-core/templates/emails/plain/expired-subscription.php:16 @@ -6612,6 +6996,52 @@ msgstr "" msgid "View your subscription: %s" msgstr "" +#. translators: %1$s: human readable time difference (eg 3 days, 1 day), %2$s: date in local format. +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-auto-renewal.php:29 +msgid "Your subscription will automatically renew in %1$s — that’s %2$s." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-auto-renewal.php:45 +msgid "You can manage this subscription from your account dashboard: " +msgstr "" + +#. translators: %1$s: human readable time difference (eg 3 days, 1 day), %2$s: date in local format. +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-auto-trial-ending.php:29 +msgid "Your paid subscription begins when your free trial expires in %1$s — that’s %2$s." +msgstr "" + +#. translators: %1$s: link to account dashboard. +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-auto-trial-ending.php:41 +msgid "Payment will be deducted using the payment method on file. You can manage this subscription from your account dashboard: " +msgstr "" + +#. translators: %1$s: human readable time difference (eg 3 days, 1 day), %2$s: date in local format. +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-expiring-subscription.php:29 +msgid "Your subscription expires in %1$s — that’s %2$s." +msgstr "" + +#. translators: %1$s: human readable time difference (eg 3 days, 1 day), %2$s: date in local format. +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-manual-renewal.php:29 +msgid "Your subscription is up for renewal in %1$s — that’s %2$s." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-manual-renewal.php:43 +msgid "You can renew it manually in a few short steps via the Subscriptions tab in your account dashboard." +msgstr "" + +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-manual-renewal.php:52 +msgid "Renew my subscription: " +msgstr "" + +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-manual-renewal.php:55 +msgid "Manage my subscription: " +msgstr "" + +#. translators: %1$s: human readable time difference (eg 3 days, 1 day), %2$s: date in local format. +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-manual-trial-ending.php:29 +msgid "Your free trial expires in %1$s — that’s %2$s." +msgstr "" + #. translators: %1$s: link to checkout payment url, note: no full stop due to url at the end #: vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-payment-retry.php:21 msgctxt "In customer renewal invoice email" @@ -6686,8 +7116,7 @@ msgstr "" msgid "Next payment: %s" msgstr "" -#. Translators: Placeholder is the My Account URL. -#: vendor/woocommerce/subscriptions-core/templates/emails/plain/subscription-info.php:52 +#: vendor/woocommerce/subscriptions-core/templates/emails/plain/subscription-info.php:54 msgid "This subscription is set to renew automatically using your payment method on file. You can manage or cancel this subscription from your my account page. %s" msgid_plural "These subscriptions are set to renew automatically using your payment method on file. You can manage or cancel your subscriptions from your my account page. %s" msgstr[0] "" @@ -6723,10 +7152,9 @@ msgctxt "Used as end date for an indefinite subscription" msgid "When cancelled" msgstr "" -#. Translators: Placeholders are opening and closing My Account link tags. -#: vendor/woocommerce/subscriptions-core/templates/emails/subscription-info.php:58 -msgid "This subscription is set to renew automatically using your payment method on file. You can manage or cancel this subscription from your %smy account page%s." -msgid_plural "These subscriptions are set to renew automatically using your payment method on file. You can manage or cancel your subscriptions from your %smy account page%s." +#: vendor/woocommerce/subscriptions-core/templates/emails/subscription-info.php:63 +msgid "This subscription is set to renew automatically using your payment method on file. You can manage or cancel this subscription from your %1$smy account page%2$s." +msgid_plural "These subscriptions are set to renew automatically using your payment method on file. You can manage or cancel your subscriptions from your %1$smy account page%2$s." msgstr[0] "" msgstr[1] "" diff --git a/vendor/autoload.php b/vendor/autoload.php index ae7c20f..c4dc292 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -22,4 +22,4 @@ require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInit13fede8eb22b3733f757156a27f6ebe3::getLoader(); +return ComposerAutoloaderInitf715afa45653daac47d4c3d21522dae0::getLoader(); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index 9bcdab2..f22f1ba 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -2,7 +2,7 @@ // autoload_real.php @generated by Composer -class ComposerAutoloaderInit13fede8eb22b3733f757156a27f6ebe3 +class ComposerAutoloaderInitf715afa45653daac47d4c3d21522dae0 { private static $loader; @@ -24,12 +24,12 @@ public static function getLoader() require __DIR__ . '/platform_check.php'; - spl_autoload_register(array('ComposerAutoloaderInit13fede8eb22b3733f757156a27f6ebe3', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInitf715afa45653daac47d4c3d21522dae0', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); - spl_autoload_unregister(array('ComposerAutoloaderInit13fede8eb22b3733f757156a27f6ebe3', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInitf715afa45653daac47d4c3d21522dae0', 'loadClassLoader')); require __DIR__ . '/autoload_static.php'; - call_user_func(\Composer\Autoload\ComposerStaticInit13fede8eb22b3733f757156a27f6ebe3::getInitializer($loader)); + call_user_func(\Composer\Autoload\ComposerStaticInitf715afa45653daac47d4c3d21522dae0::getInitializer($loader)); $loader->register(true); diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 8b53e43..936d376 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -4,7 +4,7 @@ namespace Composer\Autoload; -class ComposerStaticInit13fede8eb22b3733f757156a27f6ebe3 +class ComposerStaticInitf715afa45653daac47d4c3d21522dae0 { public static $prefixLengthsPsr4 = array ( 'C' => @@ -129,9 +129,9 @@ class ComposerStaticInit13fede8eb22b3733f757156a27f6ebe3 public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInit13fede8eb22b3733f757156a27f6ebe3::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInit13fede8eb22b3733f757156a27f6ebe3::$prefixDirsPsr4; - $loader->classMap = ComposerStaticInit13fede8eb22b3733f757156a27f6ebe3::$classMap; + $loader->prefixLengthsPsr4 = ComposerStaticInitf715afa45653daac47d4c3d21522dae0::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitf715afa45653daac47d4c3d21522dae0::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInitf715afa45653daac47d4c3d21522dae0::$classMap; }, null, ClassLoader::class); } diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index ac0fe59..9e9e76e 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -156,17 +156,17 @@ }, { "name": "woocommerce/subscriptions-core", - "version": "7.6.0", - "version_normalized": "7.6.0.0", + "version": "7.7.1", + "version_normalized": "7.7.1.0", "source": { "type": "git", "url": "https://github.com/Automattic/woocommerce-subscriptions-core.git", - "reference": "6bfbf519df799dde8cd9f39e6c93e820e5170352" + "reference": "07bf070a5b2c9716bb20280055c4f3f07d83faed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/woocommerce-subscriptions-core/zipball/6bfbf519df799dde8cd9f39e6c93e820e5170352", - "reference": "6bfbf519df799dde8cd9f39e6c93e820e5170352", + "url": "https://api.github.com/repos/Automattic/woocommerce-subscriptions-core/zipball/07bf070a5b2c9716bb20280055c4f3f07d83faed", + "reference": "07bf070a5b2c9716bb20280055c4f3f07d83faed", "shasum": "" }, "require": { @@ -179,7 +179,7 @@ "woocommerce/woocommerce-sniffs": "0.1.0", "yoast/phpunit-polyfills": "1.1.0" }, - "time": "2024-10-14T04:42:25+00:00", + "time": "2024-11-13T23:26:20+00:00", "type": "wordpress-plugin", "extra": { "phpcodesniffer-search-depth": 2 @@ -209,7 +209,7 @@ "description": "Sell products and services with recurring payments in your WooCommerce Store.", "homepage": "https://github.com/Automattic/woocommerce-subscriptions-core", "support": { - "source": "https://github.com/Automattic/woocommerce-subscriptions-core/tree/7.6.0", + "source": "https://github.com/Automattic/woocommerce-subscriptions-core/tree/7.7.1", "issues": "https://github.com/Automattic/woocommerce-subscriptions-core/issues" }, "install-path": "../woocommerce/subscriptions-core" diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 490bfd2..02fab87 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -1,9 +1,9 @@ array( 'name' => 'woocommerce/woocommerce-subscriptions', - 'pretty_version' => 'dev-release/6.8.0', - 'version' => 'dev-release/6.8.0', - 'reference' => '08730421b262b38602c261c857ac113aa46d7cbf', + 'pretty_version' => 'dev-release/6.9.0', + 'version' => 'dev-release/6.9.0', + 'reference' => 'a8ffc66efa71aa0598db1c342818a4cdccfc85cb', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -32,18 +32,18 @@ ), ), 'woocommerce/subscriptions-core' => array( - 'pretty_version' => '7.6.0', - 'version' => '7.6.0.0', - 'reference' => '6bfbf519df799dde8cd9f39e6c93e820e5170352', + 'pretty_version' => '7.7.1', + 'version' => '7.7.1.0', + 'reference' => '07bf070a5b2c9716bb20280055c4f3f07d83faed', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../woocommerce/subscriptions-core', 'aliases' => array(), 'dev_requirement' => false, ), 'woocommerce/woocommerce-subscriptions' => array( - 'pretty_version' => 'dev-release/6.8.0', - 'version' => 'dev-release/6.8.0', - 'reference' => '08730421b262b38602c261c857ac113aa46d7cbf', + 'pretty_version' => 'dev-release/6.9.0', + 'version' => 'dev-release/6.9.0', + 'reference' => 'a8ffc66efa71aa0598db1c342818a4cdccfc85cb', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), diff --git a/vendor/woocommerce/subscriptions-core/assets/css/admin.css b/vendor/woocommerce/subscriptions-core/assets/css/admin.css index 913d38b..c5da627 100644 --- a/vendor/woocommerce/subscriptions-core/assets/css/admin.css +++ b/vendor/woocommerce/subscriptions-core/assets/css/admin.css @@ -877,6 +877,10 @@ span.product-type.variable-subscription::before { width: 400px; margin-bottom: 1em; } +table.form-table input#woocommerce_subscriptions_customer_notifications_offset { + /* Aligns with .woocommerce table.form-table select */ + line-height: 32px; +} /* Reports Page */ .woocommerce-reports-wide .postbox .chart-legend li a { diff --git a/vendor/woocommerce/subscriptions-core/assets/js/admin/admin.js b/vendor/woocommerce/subscriptions-core/assets/js/admin/admin.js index 8c54c93..9b9fea1 100644 --- a/vendor/woocommerce/subscriptions-core/assets/js/admin/admin.js +++ b/vendor/woocommerce/subscriptions-core/assets/js/admin/admin.js @@ -932,6 +932,9 @@ jQuery( function ( $ ) { ), $syncRenewals = $( document.getElementById( 'woocommerce_subscriptions_sync_payments' ) + ), + $customerNotifications = $( + document.getElementById( 'woocommerce_subscriptions_customer_notifications_enabled' ) ); // We're on the Subscriptions settings page @@ -954,6 +957,9 @@ jQuery( function ( $ ) { ).parents( 'tr' ), $suspensionExtensionRow = $( '#woocommerce_subscriptions_recoup_suspension' + ).parents( 'tr' ), + $customerNotificationOffsetRow = $( + '#woocommerce_subscriptions_customer_notifications_offset' ).parents( 'tr' ); // No animation for initial hiding when switching is disabled. @@ -1009,6 +1015,19 @@ jQuery( function ( $ ) { $daysNoFeeRow.fadeOut(); } } ); + + // No animation when initially hiding customer notification offset row. + if ( ! $customerNotifications.is( ':checked' ) ) { + $customerNotificationOffsetRow.hide(); + } + // Watch the enable/disable customer notifications checkbox for changes. + $customerNotifications.on( 'change', function () { + if ( $( this ).is( ':checked' ) ) { + $customerNotificationOffsetRow.fadeIn(); + } else { + $customerNotificationOffsetRow.fadeOut(); + } + } ); } // Don't display the variation notice for variable subscription products diff --git a/vendor/woocommerce/subscriptions-core/assets/js/wcs-upgrade.js b/vendor/woocommerce/subscriptions-core/assets/js/wcs-upgrade.js index 83d2f48..0edcf47 100644 --- a/vendor/woocommerce/subscriptions-core/assets/js/wcs-upgrade.js +++ b/vendor/woocommerce/subscriptions-core/assets/js/wcs-upgrade.js @@ -1,3 +1,6 @@ +/** + * @deprecated subscriptions-core 7.7.0 This file is no longer in use and can be removed in future. + */ jQuery( function ( $ ) { var upgrade_start_time = null, total_subscriptions = wcs_update_script_data.subscription_count; diff --git a/vendor/woocommerce/subscriptions-core/changelog.txt b/vendor/woocommerce/subscriptions-core/changelog.txt index c7bef83..48a3c1e 100644 --- a/vendor/woocommerce/subscriptions-core/changelog.txt +++ b/vendor/woocommerce/subscriptions-core/changelog.txt @@ -1,5 +1,19 @@ *** WooCommerce Subscriptions Core Changelog *** += 7.7.1 - 2024-11-13 = +* Fix - Only show the individual subscription information in customer notification emails, not all subscriptions purchased in the initial order. +* Fix - Resolved issues with Customer Notification emails not being sent due to unsupported emoji used in the default email subject. + += 7.7.0 - 2024-11-13 = +* Add - New Customer Notification feature: sends reminder emails for upcoming subscription renewals, trials ending, and subscription expirations. +* Fix - Prevent adding products to the cart if a subscription renewal is already present. +* Update - Improved performance of wcs_get_subscription() when querying by product and customer or order. +* Update - Improved performance when checking limited subscription product availability. +* Update - Deprecate upgrading from versions of WooCommerce Subscriptions prior to 3.0.0 (released Jan 2020). +* Dev - Minor refactoring of `init` method in `WC_Subscriptions_Upgrader` class. +* Dev - Introduce the filter `woocommerce_subscriptions_synced_first_renewal_payment_timestamp` to enable plugins to modify the first renewal date of synced subscriptions. +* Dev - Update `get_post_meta()` calls to fetch product meta using CRUD getters. + = 7.6.0 - 2024-10-14 = * Fix - Correctly updates a subscription status to `cancelled` during a payment failure call when the current status is `pending-cancel`. * Fix - Clear the `cancelled_email_sent` meta when a subscription is reactivated to allow the customer to receive future cancellation emails. diff --git a/vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php b/vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php index a612224..0d11ac4 100644 --- a/vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php +++ b/vendor/woocommerce/subscriptions-core/includes/admin/class-wc-subscriptions-admin.php @@ -300,7 +300,8 @@ public static function subscription_pricing_fields() { $trial_tooltip = sprintf( _x( 'An optional period of time to wait before charging the first recurring payment. Any sign up fee will still be charged at the outset of the subscription. %s', 'Trial period field tooltip on Edit Product administration screen', 'woocommerce-subscriptions' ), self::get_trial_period_validation_message() ); // Set month as the default billing period - if ( ! $chosen_period = get_post_meta( $post->ID, '_subscription_period', true ) ) { + $chosen_period = get_post_meta( $post->ID, '_subscription_period', true ); + if ( ! $chosen_period ) { $chosen_period = 'month'; } diff --git a/vendor/woocommerce/subscriptions-core/includes/admin/class-wcs-admin-notice.php b/vendor/woocommerce/subscriptions-core/includes/admin/class-wcs-admin-notice.php index 4642b36..025ce8e 100644 --- a/vendor/woocommerce/subscriptions-core/includes/admin/class-wcs-admin-notice.php +++ b/vendor/woocommerce/subscriptions-core/includes/admin/class-wcs-admin-notice.php @@ -191,7 +191,7 @@ public function print_attributes() { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0 */ public function print_dismiss_url() { - echo esc_attr( $this->dismiss_url ); + echo esc_url( $this->dismiss_url ); } /* Getters */ diff --git a/vendor/woocommerce/subscriptions-core/includes/admin/debug-tools/class-wcs-notifications-debug-tool-processor.php b/vendor/woocommerce/subscriptions-core/includes/admin/debug-tools/class-wcs-notifications-debug-tool-processor.php new file mode 100644 index 0000000..2903c2b --- /dev/null +++ b/vendor/woocommerce/subscriptions-core/includes/admin/debug-tools/class-wcs-notifications-debug-tool-processor.php @@ -0,0 +1,350 @@ +get_subscription_statuses(); + $placeholders = implode( ', ', array_fill( 0, count( $allowed_statuses ), '%s' ) ); + + if ( wcs_is_custom_order_tables_usage_enabled() ) { + $total_subscriptions = $wpdb->get_var( + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber + // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare + $wpdb->prepare( + "SELECT + COUNT(id) + FROM {$wpdb->prefix}wc_orders + WHERE type='shop_subscription' + AND status IN ($placeholders) + ", + ...$allowed_statuses + ) + ); + } else { + $total_subscriptions = $wpdb->get_var( + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber + // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare + $wpdb->prepare( + "SELECT + COUNT(ID) + FROM {$wpdb->prefix}posts + WHERE post_type='shop_subscription' + AND post_status IN ($placeholders) + ", + ...$allowed_statuses + ) + ); + } + + $state = $this->get_tool_state(); + if ( isset( $state['last_offset'] ) ) { + $total_subscriptions -= (int) $state['last_offset']; + } + + return $total_subscriptions; + } + + /** + * Returns the next batch of items that need to be processed. + * + * A batch item can be anything needed to identify the actual processing to be done, + * but whenever possible items should be numbers (e.g. database record ids) + * or at least strings, to ease troubleshooting and logging in case of problems. + * + * The size of the batch returned can be less than $size if there aren't that + * many items pending processing (and it can be zero if there isn't anything to process), + * but the size should always be consistent with what 'get_total_pending_count' returns + * (i.e. the size of the returned batch shouldn't be larger than the pending items count). + * + * @param int $size Maximum size of the batch to be returned. + * + * @return array Batch of items to process, containing $size or less items. + */ + public function get_next_batch_to_process( int $size ): array { + global $wpdb; + + $allowed_statuses = $this->get_subscription_statuses(); + $placeholders = implode( ', ', array_fill( 0, count( $allowed_statuses ), '%s' ) ); + $state = $this->get_tool_state(); + $offset = isset( $state['last_offset'] ) ? (int) $state['last_offset'] : 0; + + $args = array_merge( + $allowed_statuses, + array( $size ), + array( $offset ), + ); + + if ( wcs_is_custom_order_tables_usage_enabled() ) { + $subscriptions_to_process = $wpdb->get_col( + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber + $wpdb->prepare( + "SELECT + id + FROM {$wpdb->prefix}wc_orders + WHERE type='shop_subscription' + AND status IN ($placeholders) + ORDER BY id ASC + LIMIT %d + OFFSET %d", + ...$args + ) + ); + } else { + $subscriptions_to_process = $wpdb->get_col( + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber + $wpdb->prepare( + "SELECT + ID + FROM {$wpdb->prefix}posts + WHERE post_type='shop_subscription' + AND post_status IN ($placeholders) + ORDER BY ID ASC + LIMIT %d + OFFSET %d", + ...$args + ) + ); + } + + // Reset the tool state if there are no more subscriptions to process. + if ( empty( $subscriptions_to_process ) ) { + $this->delete_tool_state(); + } + + return $subscriptions_to_process; + } + + /** + * Process data for the supplied batch. + * + * This method should be prepared to receive items that don't actually need processing + * (because they have been processed before) and ignore them, but if at least + * one of the batch items that actually need processing can't be processed, an exception should be thrown. + * + * Once an item has been processed it shouldn't be counted in 'get_total_pending_count' + * nor included in 'get_next_batch_to_process' anymore (unless something happens that causes it + * to actually require further processing). + * + * @throw \Exception Something went wrong while processing the batch. + * + * @param array $batch Batch to process, as returned by 'get_next_batch_to_process'. + */ + public function process_batch( array $batch ): void { + + $subscriptions_notifications = WC_Subscriptions_Core_Plugin::instance()->notifications_scheduler; + + foreach ( $batch as $subscription_id ) { + $subscription = wcs_get_subscription( $subscription_id ); + + if ( ! $subscription ) { + continue; + } + + if ( WC_Subscriptions_Email_Notifications::notifications_globally_enabled() ) { + $subscriptions_notifications->update_status( $subscription, $subscription->get_status(), null ); + } else { + $subscriptions_notifications->unschedule_all_notifications( $subscription ); + } + + // Update the subscription's update time to mark it as updated. + $subscription->set_date_modified( time() ); + $subscription->save(); + } + + // Update tool state. + $state = $this->get_tool_state(); + $state['last_offset'] = isset( $state['last_offset'] ) ? absint( $state['last_offset'] ) + count( $batch ) : count( $batch ); + $this->update_tool_state( $state ); + } + + /** + * Default (preferred) batch size to pass to 'get_next_batch_to_process'. + * The controller will pass this size unless it's externally configured + * to use a different size. + * + * @return int Default batch size. + */ + public function get_default_batch_size(): int { + return 20; + } + + /** + * Start the background process for batch processing subscription notifications updates. + * + * @return string Informative string to show after the tool is triggered in UI. + */ + public function enqueue(): string { + $batch_processor = WCS_Batch_Processing_Controller::instance(); + if ( $batch_processor->is_enqueued( self::class ) ) { + return __( 'Background process for updating subscription notifications already started, nothing done.', 'woocommerce-subscriptions' ); + } + + $batch_processor->enqueue_processor( self::class ); + + return __( 'Background process for updating subscription notifications started', 'woocommerce-subscriptions' ); + } + + /** + * Stop the background process for batch processing subscription notifications updates. + * + * @return string Informative string to show after the tool is triggered in UI. + */ + public function dequeue(): string { + $batch_processor = WCS_Batch_Processing_Controller::instance(); + if ( ! $batch_processor->is_enqueued( self::class ) ) { + return __( 'Background process for updating subscription notifications not started, nothing done.', 'woocommerce-subscriptions' ); + } + + $batch_processor->remove_processor( self::class ); + return __( 'Background process for updating subscription notifications stopped', 'woocommerce-subscriptions' ); + } + + /** + * Add the tool to start or stop the background process that manages notification batch processing. + * + * @param array $tools Old tools array. + * @return array Updated tools array. + */ + public function handle_woocommerce_debug_tools( array $tools ): array { + + if ( ! WC_Subscriptions_Email_Notifications::notifications_globally_enabled() ) { + + $tools['start_add_subscription_notifications'] = array( + 'name' => __( 'Regenerate subscription notifications', 'woocommerce-subscriptions' ), + 'button' => __( 'Regenerate notifications', 'woocommerce-subscriptions' ), + 'disabled' => true, + 'desc' => sprintf( + '%1$s
%2$s %3$s %5$s', + __( 'This tool will add notifications to pending, active, and on-hold subscriptions. These updates will occur gradually in the background using Action Scheduler.', 'woocommerce-subscriptions' ), + __( 'Note:', 'woocommerce-subscriptions' ), + __( 'Notifications are currently turned off. To activate them, check the "Enable customer renewal reminder notification emails." option (via WooCommerce > Settings > Subscriptions > Customer Notifications).', 'woocommerce-subscriptions' ), + esc_url( admin_url( 'admin.php?page=wc-settings&tab=subscriptions' ) ), + __( 'Manage settings.', 'woocommerce-subscriptions' ), + ), + 'requires_refresh' => true, + ); + return $tools; + } + + $batch_processor = WCS_Batch_Processing_Controller::instance(); + + if ( $batch_processor->is_enqueued( self::class ) ) { + + $pending_count = $this->get_total_pending_count(); + $tools['stop_add_subscription_notifications'] = array( + 'name' => __( 'Regenerate subscription notifications', 'woocommerce-subscriptions' ), + 'button' => __( 'Stop regenerating notifications', 'woocommerce-subscriptions' ), + 'desc' => + /* translators: %1$d=count of total entries needing conversion */ + sprintf( __( 'Stopping this will halt the background process that adds notifications to pending, active, and on-hold subscriptions. %1$d subscriptions remain to be processed.', 'woocommerce-subscriptions' ), $pending_count ), + 'callback' => array( $this, 'dequeue' ), + 'requires_refresh' => true, + ); + } else { + $tools['start_add_subscription_notifications'] = array( + 'name' => __( 'Regenerate subscription notifications', 'woocommerce-subscriptions' ), + 'button' => __( 'Regenerate notifications', 'woocommerce-subscriptions' ), + 'desc' => __( 'This tool will regenerate notifications to pending, active, and on-hold subscriptions. These updates will occur gradually in the background using Action Scheduler.', 'woocommerce-subscriptions' ), + 'callback' => array( $this, 'enqueue' ), + 'requires_refresh' => true, + ); + } + + return $tools; + } +} diff --git a/vendor/woocommerce/subscriptions-core/includes/class-wc-subscription-query-controller.php b/vendor/woocommerce/subscriptions-core/includes/class-wc-subscription-query-controller.php new file mode 100644 index 0000000..5b3adac --- /dev/null +++ b/vendor/woocommerce/subscriptions-core/includes/class-wc-subscription-query-controller.php @@ -0,0 +1,129 @@ +query_vars = $query_vars; + } + + /** + * Determines if the query is for a specific product or variation. + * + * @return bool True if the query is for a specific product or variation, otherwise false. + */ + public function has_product_query() { + return ( 0 !== $this->query_vars['product_id'] && is_numeric( $this->query_vars['product_id'] ) ) || ( 0 !== $this->query_vars['variation_id'] && is_numeric( $this->query_vars['variation_id'] ) ); + } + + /** + * Determines if the wcs_get_subscription() query should filter the results by product ID or variation ID after the query has been run. + * + * If the wcs_get_subscriptions() query is substantially limited (eg to a customer or order) we know that the results will be small. In these cases, we can + * filter the results by product ID or variation ID after the query has been run for better performance. + * + * @return bool True if the subscriptions should be queried by product ID, otherwise false. + */ + public function should_filter_query_results() { + $can_filter_results = false; + $order_id = $this->query_vars['order_id'] ?? 0; + $customer_id = $this->query_vars['customer_id'] ?? 0; + + // If we're querying by order ID or customer ID, we can filter the results by product ID after the query has been run. + if ( ! empty( $order_id ) || ! empty( $customer_id ) ) { + $can_filter_results = apply_filters( 'wcs_should_filter_subscriptions_results_by_product_id', true, $this->query_vars ); + } + + return $can_filter_results; + } + + /** + * Filters the subscription query results by product ID or variation ID. + * + * @param WC_Subscriptions[] $subscriptions + * @return WC_Subscriptions[] The filtered subscriptions. + */ + public function filter_subscriptions( $subscriptions ) { + $filtered_subscriptions = []; + $product_id = $this->query_vars['product_id'] ?? 0; + $variation_id = $this->query_vars['variation_id'] ?? 0; + + if ( empty( $product_id ) && empty( $variation_id ) ) { + return $subscriptions; + } + + // Filter the subscriptions by product ID or variation ID. + foreach ( $subscriptions as $subscription_id => $subscription ) { + if ( + ( $variation_id && $subscription->has_product( $variation_id ) ) || + ( $product_id && $subscription->has_product( $product_id ) ) + ) { + $filtered_subscriptions[ $subscription_id ] = $subscription; + } + } + + return $filtered_subscriptions; + } + + /** + * Applies pagination to the subscriptions array. + * + * @param WC_Subscriptions[] $subscriptions + * @return WC_Subscriptions[] The subscriptions array with pagination applied. + */ + public function paginate_results( $subscriptions ) { + $per_page = $this->query_vars['subscriptions_per_page']; + $page = $this->query_vars['paged']; + $offset = $this->query_vars['offset']; + + // If the limit is -1, return all subscriptions. + if ( -1 === $per_page ) { + return $subscriptions; + } + + if ( $offset ) { + $start_index = $offset; + } else { + // Calculate the starting index for the slice. + $start_index = ( $page - 1 ) * $per_page; + } + + // Slice the subscriptions array to get the required items. + return array_slice( $subscriptions, $start_index, $per_page, true ); + } +} diff --git a/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-cart-validator.php b/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-cart-validator.php index 9abe2e2..6091e28 100644 --- a/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-cart-validator.php +++ b/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-cart-validator.php @@ -18,7 +18,7 @@ public static function init() { add_filter( 'woocommerce_add_to_cart_validation', array( __CLASS__, 'maybe_empty_cart' ), 10, 5 ); add_filter( 'woocommerce_cart_loaded_from_session', array( __CLASS__, 'validate_cart_contents_for_mixed_checkout' ), 10 ); - add_filter( 'woocommerce_add_to_cart_validation', array( __CLASS__, 'can_add_subscription_product_to_cart' ), 10, 6 ); + add_filter( 'woocommerce_add_to_cart_validation', array( __CLASS__, 'can_add_product_to_cart' ), 10, 6 ); } @@ -119,15 +119,23 @@ public static function validate_cart_contents_for_mixed_checkout( $cart ) { } /** - * Don't allow new subscription products to be added to the cart if it contains a subscription renewal already. + * Don't allow products to be added to the cart if it contains a subscription renewal already. * - * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.6.0 + * @since 7.7.0 + * + * @param bool $can_add Whether the product can be added to the cart. + * @param int $product_id The product ID. + * @param int $quantity The quantity of the product being added. + * @param int $variation_id The variation ID. + * @param array $variations The variations of the product being added. + * @param array $item_data The item data. + * + * @return bool Whether the product can be added to the cart. */ - public static function can_add_subscription_product_to_cart( $can_add, $product_id, $quantity, $variation_id = '', $variations = array(), $item_data = array() ) { - - if ( $can_add && ! isset( $item_data['subscription_renewal'] ) && wcs_cart_contains_renewal() && WC_Subscriptions_Product::is_subscription( $product_id ) ) { + public static function can_add_product_to_cart( $can_add, $product_id, $quantity, $variation_id = '', $variations = array(), $item_data = array() ) { + if ( $can_add && ! isset( $item_data['subscription_renewal'] ) && wcs_cart_contains_renewal() ) { + wc_add_notice( __( 'That product can not be added to your cart as it already contains a subscription renewal.', 'woocommerce-subscriptions' ), 'error' ); - wc_add_notice( __( 'That subscription product can not be added to your cart as it already contains a subscription renewal.', 'woocommerce-subscriptions' ), 'error' ); $can_add = false; } @@ -158,4 +166,21 @@ public static function add_to_cart_ajax_redirect( $fragments ) { return $fragments; } + + /** + * Don't allow new subscription products to be added to the cart if it contains a subscription renewal already. + * + * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.6.0 + * @deprecated 3.0.0 + */ + public static function can_add_subscription_product_to_cart( $can_add, $product_id, $quantity, $variation_id = '', $variations = array(), $item_data = array() ) { + wcs_deprecated_function( __METHOD__, '6.9.0', 'WC_Subscriptions_Cart_Validator::can_add_product_to_cart' ); + if ( $can_add && ! isset( $item_data['subscription_renewal'] ) && wcs_cart_contains_renewal() && WC_Subscriptions_Product::is_subscription( $product_id ) ) { + wc_add_notice( __( 'That subscription product can not be added to your cart as it already contains a subscription renewal.', 'woocommerce-subscriptions' ), 'error' ); + + $can_add = false; + } + + return $can_add; + } } diff --git a/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php b/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php index dae49d8..3e6502c 100644 --- a/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php +++ b/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php @@ -16,7 +16,7 @@ class WC_Subscriptions_Core_Plugin { * The version of subscriptions-core library. * @var string */ - protected $library_version = '7.6.0'; // WRCS: DEFINED_VERSION. + protected $library_version = '7.7.1'; // WRCS: DEFINED_VERSION. /** * The subscription scheduler instance. @@ -25,6 +25,13 @@ class WC_Subscriptions_Core_Plugin { */ protected $scheduler = null; + /** + * Notification scheduler instance. + * + * @var WCS_Action_Scheduler_Customer_Notifications + */ + public $notifications_scheduler = null; + /** * The plugin's autoloader instance. * @@ -125,6 +132,7 @@ public function init() { WC_Subscriptions_Renewal_Order::init(); WC_Subscriptions_Checkout::init(); WC_Subscriptions_Email::init(); + WC_Subscriptions_Email_Notifications::init(); WC_Subscriptions_Addresses::init(); WC_Subscriptions_Change_Payment_Gateway::init(); $payment_gateways_handler::init(); @@ -156,10 +164,16 @@ public function init() { add_action( 'plugins_loaded', 'WCS_Related_Order_Store::instance' ); add_action( 'plugins_loaded', 'WCS_Customer_Store::instance' ); + // Initialise the batch processing controller. + add_action( 'init', 'WCS_Batch_Processing_Controller::instance' ); + // Initialise the scheduler. $scheduler_class = apply_filters( 'woocommerce_subscriptions_scheduler', 'WCS_Action_Scheduler' ); $this->scheduler = new $scheduler_class(); + // Customer notifications scheduler. + $this->notifications_scheduler = new WCS_Action_Scheduler_Customer_Notifications(); + // Initialise the cache. $this->cache = WCS_Cache_Manager::get_instance(); @@ -244,6 +258,8 @@ public function init_hooks() { add_action( 'init', array( $this, 'activate_plugin' ) ); add_filter( 'action_scheduler_queue_runner_batch_size', array( $this, 'reduce_multisite_action_scheduler_batch_size' ) ); + + add_action( 'init', array( $this, 'init_notification_batch_processor' ) ); } /** @@ -517,6 +533,11 @@ public function activate_plugin() { update_option( WC_Subscriptions_admin::$option_prefix . '_paypal_debugging_default_set', 'true' ); } + // Enable customer notifications by default for new stores. + if ( '0' === get_option( WC_Subscriptions_Admin::$option_prefix . '_previous_version', '0' ) && 'no' === get_option( WC_Subscriptions_Admin::$option_prefix . WC_Subscriptions_Email_Notifications::$switch_setting_string, 'no' ) ) { + update_option( WC_Subscriptions_Admin::$option_prefix . WC_Subscriptions_Email_Notifications::$switch_setting_string, 'yes' ); + } + update_option( WC_Subscriptions_Admin::$option_prefix . '_is_active', true ); set_transient( $this->get_activation_transient(), true, 60 * 60 ); @@ -625,4 +646,15 @@ public function reduce_multisite_action_scheduler_batch_size( $batch_size ) { return $batch_size; } + + /** + * Initialize batch processing for subscription notifications. + * + * @return void + */ + public function init_notification_batch_processor() { + // Background processing for notifications + $notifications_batch_processor = new WCS_Notifications_Batch_Processor(); + $notifications_debug_tool_processor = new WCS_Notifications_Debug_Tool_Processor(); + } } diff --git a/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email-notifications.php b/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email-notifications.php new file mode 100644 index 0000000..a2ada04 --- /dev/null +++ b/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email-notifications.php @@ -0,0 +1,354 @@ +get_id() ); + } + } + + /** + * Sets the update time when any of the settings that affect notifications change and triggers update of subscriptions. + * + * When time offset or global on/off switch change values, this method gets triggered and it: + * 1. Updates the wcs_notification_settings_update_time option so that the code knows which subscriptions to update + * 2. Triggers rescheduling/unscheduling of existing notifications. + * 3. Adds a notice with info about the actions that got triggered to the store manager. + * + * Side note: offset gets updated in WCS_Action_Scheduler_Customer_Notifications::set_time_offset_from_option. + * + * @return void + */ + public static function set_notification_settings_update_time() { + update_option( 'wcs_notification_settings_update_time', time() ); + + // Shortcut to unschedule all notifications more efficiently instead of processing them subscription by subscription. + if ( ! self::notifications_globally_enabled() ) { + as_unschedule_all_actions( null, [], 'wcs_customer_notifications' ); + } else { + $message = WCS_Notifications_Batch_Processor::enqueue(); + $admin_notice = new WCS_Admin_Notice( 'updated' ); + $admin_notice->set_simple_content( $message ); + $admin_notice->display(); + } + } + + /** + * Add Subscriptions notifications' email classes. + */ + public static function add_emails( $email_classes ) { + + $email_classes['WCS_Email_Customer_Notification_Auto_Trial_Expiration'] = new WCS_Email_Customer_Notification_Auto_Trial_Expiration(); + $email_classes['WCS_Email_Customer_Notification_Manual_Trial_Expiration'] = new WCS_Email_Customer_Notification_Manual_Trial_Expiration(); + $email_classes['WCS_Email_Customer_Notification_Subscription_Expiration'] = new WCS_Email_Customer_Notification_Subscription_Expiration(); + $email_classes['WCS_Email_Customer_Notification_Manual_Renewal'] = new WCS_Email_Customer_Notification_Manual_Renewal(); + $email_classes['WCS_Email_Customer_Notification_Auto_Renewal'] = new WCS_Email_Customer_Notification_Auto_Renewal(); + + return $email_classes; + } + + /** + * Hook the notification emails with our custom trigger. + */ + public static function hook_notification_emails() { + add_action( 'woocommerce_scheduled_subscription_customer_notification_renewal', [ __CLASS__, 'send_notification' ] ); + add_action( 'woocommerce_scheduled_subscription_customer_notification_trial_expiration', [ __CLASS__, 'send_notification' ] ); + add_action( 'woocommerce_scheduled_subscription_customer_notification_expiration', [ __CLASS__, 'send_notification' ] ); + } + + /** + * Send the notification emails. + * + * @param int $subscription_id Subscription ID. + */ + public static function send_notification( $subscription_id ) { + + // Init email classes. + $emails = WC()->mailer()->get_emails(); + + if ( ! ( $emails['WCS_Email_Customer_Notification_Auto_Renewal'] instanceof WCS_Email_Customer_Notification_Auto_Renewal + && $emails['WCS_Email_Customer_Notification_Manual_Renewal'] instanceof WCS_Email_Customer_Notification_Manual_Renewal + && $emails['WCS_Email_Customer_Notification_Subscription_Expiration'] instanceof WCS_Email_Customer_Notification_Subscription_Expiration + && $emails['WCS_Email_Customer_Notification_Manual_Trial_Expiration'] instanceof WCS_Email_Customer_Notification_Manual_Trial_Expiration + && $emails['WCS_Email_Customer_Notification_Auto_Trial_Expiration'] instanceof WCS_Email_Customer_Notification_Auto_Trial_Expiration + ) + ) { + return; + } + $notification = null; + switch ( current_action() ) { + case 'woocommerce_scheduled_subscription_customer_notification_renewal': + $subscription = wcs_get_subscription( $subscription_id ); + if ( $subscription->is_manual() ) { + $notification = $emails['WCS_Email_Customer_Notification_Manual_Renewal']; + } else { + $notification = $emails['WCS_Email_Customer_Notification_Auto_Renewal']; + } + break; + case 'woocommerce_scheduled_subscription_customer_notification_trial_expiration': + $subscription = wcs_get_subscription( $subscription_id ); + if ( $subscription->is_manual() ) { + $notification = $emails['WCS_Email_Customer_Notification_Manual_Trial_Expiration']; + } else { + $notification = $emails['WCS_Email_Customer_Notification_Auto_Trial_Expiration']; + } + break; + case 'woocommerce_scheduled_subscription_customer_notification_expiration': + $notification = $emails['WCS_Email_Customer_Notification_Subscription_Expiration']; + break; + } + + if ( $notification ) { + $notification->trigger( $subscription_id ); + } + } + + /** + * Is the notifications feature enabled? + * + * @return bool + */ + public static function notifications_globally_enabled() { + return ( 'yes' === get_option( WC_Subscriptions_Admin::$option_prefix . self::$switch_setting_string ) + && get_option( WC_Subscriptions_Admin::$option_prefix . self::$offset_setting_string ) ); + } + + /** + * Should the emails be sent out? + * + * @return bool + */ + public static function should_send_notification() { + $notification_enabled = true; + + if ( WCS_Staging::is_duplicate_site() ) { + $notification_enabled = false; + } + + $allowed_env_types = [ + 'production', + ]; + if ( ! in_array( wp_get_environment_type(), $allowed_env_types, true ) ) { + $notification_enabled = false; + } + + // If Customer notifications are disabled in the settings by a global switch, or there is no offset set, don't send notifications. + if ( ! self::notifications_globally_enabled() ) { + $notification_enabled = false; + } + + return $notification_enabled; + } + + /** + * Adds actions to the admin edit subscriptions page. + * + * @param array $actions An array of available actions + * @return array An array of updated actions + */ + public static function add_notification_actions( $actions ) { + global $theorder; + + if ( ! self::notifications_globally_enabled() ) { + return $actions; + } + + if ( ! wcs_is_subscription( $theorder ) ) { + return $actions; + } + + if ( ! $theorder->has_status( [ 'active', 'on-hold', 'pending-cancel' ] ) ) { + return $actions; + } + + $valid_notifications = WCS_Action_Scheduler_Customer_Notifications::get_valid_notifications( $theorder ); + + if ( in_array( 'trial_end', $valid_notifications, true ) ) { + $actions['wcs_customer_notification_free_trial_expiration'] = esc_html__( 'Send trial is ending notification', 'woocommerce-subscriptions' ); + } + + if ( in_array( 'end', $valid_notifications, true ) ) { + $actions['wcs_customer_notification_subscription_expiration'] = esc_html__( 'Send upcoming subscription expiration notification', 'woocommerce-subscriptions' ); + } + + if ( in_array( 'next_payment', $valid_notifications, true ) ) { + $actions['wcs_customer_notification_renewal'] = esc_html__( 'Send upcoming renewal notification', 'woocommerce-subscriptions' ); + } + + return $actions; + } + + /** + * Adds the subscription notification setting. + * + * @param array $settings Subscriptions settings. + * @return array Subscriptions settings. + */ + public static function add_settings( $settings ) { + + $notification_settings = [ + [ + 'name' => __( 'Customer Notifications', 'woocommerce-subscriptions' ), + 'type' => 'title', + 'id' => WC_Subscriptions_Admin::$option_prefix . '_customer_notifications', + /* translators: Link to WC Settings > Email. */ + 'desc' => sprintf( __( 'To enable/disable individual notifications and customize templates, visit the Email settings.', 'woocommerce-subscriptions' ), admin_url( 'admin.php?page=wc-settings&tab=email' ) ), + ], + [ + 'name' => __( 'Enable Reminders', 'woocommerce-subscriptions' ), + 'desc' => __( 'Send notification emails to customers for subscription renewals and expirations.', 'woocommerce-subscriptions' ), + 'tip' => '', + 'id' => WC_Subscriptions_Admin::$option_prefix . self::$switch_setting_string, + 'desc_tip' => false, + 'type' => 'checkbox', + 'default' => 'no', + 'autoload' => false, + ], + [ + 'name' => __( 'Reminder Timing', 'woocommerce-subscriptions' ), + 'desc' => __( 'How long before the event should the notification be sent.', 'woocommerce-subscriptions' ), + 'tip' => '', + 'id' => WC_Subscriptions_Admin::$option_prefix . self::$offset_setting_string, + 'desc_tip' => true, + 'type' => 'relative_date_selector', + 'placeholder' => __( 'N/A', 'woocommerce-subscriptions' ), + 'default' => [ + 'number' => '3', + 'unit' => 'days', + ], + 'autoload' => false, + ], + [ + 'type' => 'sectionend', + 'id' => WC_Subscriptions_Admin::$option_prefix . '_customer_notifications', + ], + ]; + + WC_Subscriptions_Admin::insert_setting_after( $settings, WC_Subscriptions_Admin::$option_prefix . '_miscellaneous', $notification_settings, 'multiple_settings', 'sectionend' ); + return $settings; + } + + /** + * Maybe add an admin notice to inform the store manager about the existance of the notifications feature. + */ + public static function maybe_add_admin_notice() { + + // If the notifications feature is enabled, don't show the notice. + if ( self::notifications_globally_enabled() ) { + return; + } + + // Prevent showing the notice on the Subscriptions settings page. + if ( isset( $_GET['page'], $_GET['tab'] ) && 'wc-settings' === $_GET['page'] && 'subscriptions' === $_GET['tab'] ) { + return; + } + + $option_name = 'wcs_hide_customer_notifications_notice'; + $nonce = '_wcsnonce'; + $action = 'wcs_hide_customer_notifications_notice_action'; + + // First, check if the notice is being dismissed. + $nonce_argument = sanitize_text_field( wp_unslash( $_GET[ $nonce ] ?? '' ) ); + if ( isset( $_GET[ $action ], $nonce_argument ) && wp_verify_nonce( $nonce_argument, $action ) ) { + update_option( $option_name, 'yes' ); + wp_safe_redirect( remove_query_arg( [ $action, $nonce ] ) ); + return; + } + + if ( 'yes' === get_option( $option_name ) ) { + return; + } + + $admin_notice = new WCS_Admin_Notice( 'notice', array(), wp_nonce_url( add_query_arg( $action, 'dismiss' ), $action, $nonce ) ); + $notice_title = __( 'WooCommerce Subscriptions: Introducing customer email notifications!', 'woocommerce-subscriptions' ); + $notice_content = __( 'You can now send email notifications for subscription renewals, expirations, and free trials. Go to the "Customer Notifications" settings section to configure when your customers receive these important updates.', 'woocommerce-subscriptions' ); + $html_content = sprintf( '

%1$s

%2$s

', $notice_title, $notice_content ); + $admin_notice->set_html_content( $html_content ); + $admin_notice->set_actions( + array( + array( + 'name' => __( 'Manage settings', 'woocommerce-subscriptions' ), + 'url' => admin_url( 'admin.php?page=wc-settings&tab=subscriptions' ), + 'class' => 'button button-primary', + ), + array( + 'name' => __( 'Learn more', 'woocommerce-subscriptions' ), + 'url' => 'https://woocommerce.com/document/subscriptions/subscriptions-notifications/', + 'class' => 'button', + ), + ) + ); + + $admin_notice->display(); + } +} diff --git a/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email.php b/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email.php index 5dd9ca1..2952d1e 100644 --- a/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email.php +++ b/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-email.php @@ -294,6 +294,31 @@ public static function order_details( $order, $sent_to_admin = false, $plain_tex ); } + /** + * Show the subscription details table. + * + * @param WC_Subscription[] $subscriptions List of subscriptions. Also accepts a single subscription. + * @param WC_Order|null $order The order related to the subscription - defaults to parent order. + * @param bool $sent_to_admin Whether the email is sent to admin - defaults to false. + * @param bool $plain_text Whether the email should use plain text templates - defaults to false. + * @param bool $skip_my_account_link Whether to skip displaying the My Account link - defaults to false. + */ + public static function subscription_details( $subscriptions, $order = null, $sent_to_admin = false, $plain_text = false, $skip_my_account_link = false ) { + $template = ( $plain_text ) ? 'emails/plain/subscription-info.php' : 'emails/subscription-info.php'; + + wc_get_template( + $template, + array( + 'order' => ! $order ? $subscription->get_parent() : $order, + 'subscriptions' => is_array( $subscriptions ) ? $subscriptions : [ $subscriptions ], + 'is_admin_email' => $sent_to_admin, + 'skip_my_account_link' => $skip_my_account_link, + ), + '', + WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory( 'templates/' ) + ); + } + /** * Detach WC transactional emails from a specific hook. * diff --git a/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-manager.php b/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-manager.php index f57aed5..1561930 100644 --- a/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-manager.php +++ b/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-manager.php @@ -2028,16 +2028,13 @@ public static function maybe_put_subscription_on_hold( $user_id, $subscription_k * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.2 */ public static function maybe_process_failed_renewal_for_repair( $subscription_id ) { - - if ( 'true' == get_post_meta( $subscription_id, '_wcs_repaired_2_0_2_needs_failed_payment', true ) ) { - - $subscription = wcs_get_subscription( $subscription_id ); - + $subscription = wcs_get_subscription( $subscription_id ); + if ( 'true' === $subscription->get_meta( '_wcs_repaired_2_0_2_needs_failed_payment', true ) ) { // Always put the subscription on hold in case something goes wrong while trying to process renewal $subscription->update_status( 'on-hold', _x( 'Subscription renewal payment due:', 'used in order note as reason for why subscription status changed', 'woocommerce-subscriptions' ) ); // Create a renewal order to record the failed payment which can then be used by the customer to reactivate the subscription - $renewal_order = wcs_create_renewal_order( $subscription ); + wcs_create_renewal_order( $subscription ); // Mark the payment as failed so the customer can login to fix up the failed payment $subscription->payment_failed(); diff --git a/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php b/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php index e3adbe3..75b8704 100644 --- a/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php +++ b/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-order.php @@ -741,7 +741,7 @@ public static function order_needs_payment( $needs_payment, $order, $valid_order * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.5 */ - public static function add_sub_info_email( $order, $is_admin_email, $plaintext = false ) { + public static function add_sub_info_email( $order, $is_admin_email, $plaintext = false, $skip_my_account_link = false ) { $subscriptions = wcs_get_subscriptions_for_order( $order, array( 'order_type' => 'any' ) ); @@ -753,9 +753,10 @@ public static function add_sub_info_email( $order, $is_admin_email, $plaintext = wc_get_template( $template, array( - 'order' => $order, - 'subscriptions' => $subscriptions, - 'is_admin_email' => $is_admin_email, + 'order' => $order, + 'subscriptions' => $subscriptions, + 'is_admin_email' => $is_admin_email, + 'skip_my_account_link' => $skip_my_account_link, ), '', $template_base diff --git a/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php b/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php index cee38c7..f4057c2 100644 --- a/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php +++ b/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-product.php @@ -126,13 +126,10 @@ public static function get_grouped_price_html( $price, $grouped_product ) { $contains_subscription = false; foreach ( $grouped_product->get_children() as $child_product_id ) { - + $child_product = wc_get_product( $child_product_id ); if ( self::is_subscription( $child_product_id ) ) { - $contains_subscription = true; - $child_product = wc_get_product( $child_product_id ); - $tax_display_mode = get_option( 'woocommerce_tax_display_shop' ); $child_price = 'incl' == $tax_display_mode ? wcs_get_price_including_tax( $child_product, array( 'price' => $child_product->get_price() ) ) : wcs_get_price_excluding_tax( $child_product, array( 'price' => $child_product->get_price() ) ); $sign_up_fee = 'incl' == $tax_display_mode ? wcs_get_price_including_tax( $child_product, array( 'price' => self::get_sign_up_fee( $child_product ) ) ) : wcs_get_price_excluding_tax( $child_product, array( 'price' => self::get_sign_up_fee( $child_product ) ) ); @@ -146,11 +143,8 @@ public static function get_grouped_price_html( $price, $grouped_product ) { } $child_prices[] = $child_price; - } else { - - $child_prices[] = get_post_meta( $child_product_id, '_price', true ); - + $child_prices[] = $child_product->get_price(); } } @@ -934,7 +928,8 @@ public static function bulk_edit_variations( $bulk_action, $data, $variable_prod $value = wc_clean( $data['value'] ); foreach ( $variation_ids as $variation_id ) { - $subscription_price = get_post_meta( $variation_id, '_subscription_price', true ); + $variation = wc_get_product( $variation_id ); + $subscription_price = $variation->get_meta( '_subscription_price', true ); if ( '%' === substr( $value, -1 ) ) { $percent = wc_format_decimal( substr( $value, 0, -1 ) ); diff --git a/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php b/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php index b9f4427..c8f3704 100644 --- a/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php +++ b/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-synchroniser.php @@ -263,7 +263,8 @@ public static function subscription_product_fields() { if ( self::is_syncing_enabled() ) { // Set month as the default billing period - if ( ! $subscription_period = get_post_meta( $post->ID, '_subscription_period', true ) ) { + $subscription_period = get_post_meta( $post->ID, '_subscription_period', true ); + if ( ! $subscription_period ) { $subscription_period = 'month'; } @@ -857,6 +858,7 @@ public static function get_products_first_payment_date( $product ) { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ public static function products_first_renewal_payment_time( $first_renewal_timestamp, $product_id, $from_date, $timezone ) { + $unmodified_first_renewal_timestamp = $first_renewal_timestamp; if ( self::is_product_synced( $product_id ) ) { @@ -871,7 +873,18 @@ public static function products_first_renewal_payment_time( $first_renewal_times } } - return $first_renewal_timestamp; + /** + * Filter the first renewal payment date string for a product. + * + * @since 7.7.0 + * + * @param int $first_renewal_timestamp The timestamp of the first renewal payment date. + * @param int $product_id The product ID. + * @param string $from_date The date to calculate the first payment from in GMT/UTC timezone. + * @param string $timezone The timezone to use for the first payment date. + * @param int $unmodified_first_renewal_timestamp The unmodified timestamp of the first renewal payment date. + */ + return apply_filters( 'woocommerce_subscriptions_synced_first_renewal_payment_timestamp', $first_renewal_timestamp, $product_id, $from_date, $timezone, $unmodified_first_renewal_timestamp ); } /** @@ -1495,10 +1508,12 @@ public static function order_contains_synced_subscription( $order_id ) { _deprecated_function( __METHOD__, '2.0', __CLASS__ . '::subscription_contains_synced_product()' ); if ( is_object( $order_id ) ) { - $order_id = wcs_get_objects_property( $order_id, 'id' ); + $order = $order_id; + } else { + $order = wc_get_order( $order_id ); } - return 'true' == get_post_meta( $order_id, '_order_contains_synced_subscription', true ); + return 'true' === $order->get_meta( '_order_contains_synced_subscription', true ); } /** diff --git a/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-tracker.php b/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-tracker.php index 81fba94..d504221 100644 --- a/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-tracker.php +++ b/vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-tracker.php @@ -79,6 +79,9 @@ private static function get_subscriptions_options() { 'allow_zero_initial_order_without_payment_method' => get_option( WC_Subscriptions_Admin::$option_prefix . '_zero_initial_payment_requires_payment' ), 'drip_downloadable_content_on_renewal' => get_option( WC_Subscriptions_Admin::$option_prefix . '_drip_downloadable_content_on_renewal' ), 'enable_retry' => get_option( WC_Subscriptions_Admin::$option_prefix . '_enable_retry' ), + + // Notifications + 'enable_notification_reminders' => get_option( WC_Subscriptions_Admin::$option_prefix . WC_Subscriptions_Email_Notifications::$switch_setting_string, 'no' ), ]; } diff --git a/vendor/woocommerce/subscriptions-core/includes/class-wcs-action-scheduler-customer-notifications.php b/vendor/woocommerce/subscriptions-core/includes/class-wcs-action-scheduler-customer-notifications.php new file mode 100644 index 0000000..4e1b5a6 --- /dev/null +++ b/vendor/woocommerce/subscriptions-core/includes/class-wcs-action-scheduler-customer-notifications.php @@ -0,0 +1,531 @@ + 3, + 'unit' => 'days', + ] + ); + $this->set_time_offset( self::convert_offset_to_seconds( $setting_option ) ); + + add_action( 'woocommerce_before_subscription_object_save', [ $this, 'update_notifications' ], 10, 2 ); + + add_action( 'update_option_' . WC_Subscriptions_Admin::$option_prefix . WC_Subscriptions_Email_Notifications::$offset_setting_string, [ $this, 'set_time_offset_from_option' ], 5, 3 ); + add_action( 'add_option_' . WC_Subscriptions_Admin::$option_prefix . WC_Subscriptions_Email_Notifications::$offset_setting_string, [ $this, 'set_time_offset_from_option' ], 5, 2 ); + } + + /** + * Check if the subscription period is too short to send a renewal notification. + * + * @param $subscription + * + * @return bool + */ + public static function is_subscription_period_too_short( $subscription ) { + $period = $subscription->get_billing_period(); + $interval = $subscription->get_billing_interval(); + + // By default, there are no shorter periods than days in WCS, so we ignore hours, minutes, etc. + if ( $interval <= 2 && 'day' === $period ) { + return true; + } + + return false; + } + + /** + * Return time offset for notifications for given subscription. + * + * Generally, there is one offset for all subscriptions, but there's a filter. + * + * @param WC_Subscription $subscription + * @param string $notification_type + * + * @return mixed|null + */ + public function get_time_offset( $subscription, $notification_type ) { + /** + * Offset between a subscription event and related notification. + * + * @since 7.7.0 + * + * @param int $time_offset In seconds + * @param WC_Subscription $subscription + * @param string $notification_type Can be 'trial_end', 'next_payment' or 'end'. + * + * @return int + */ + return apply_filters( 'woocommerce_subscription_customer_notification_time_offset', $this->time_offset, $subscription, $notification_type ); + } + + /** + * General time offset setter. + * + * @param int $time_offset In seconds + * + * @return void + */ + public function set_time_offset( $time_offset ) { + $this->time_offset = $time_offset; + } + + /** + * Set the offset based on new value set in the option. + * + * @param $_ Unused parameter. + * @param $new_option_value + * + * @return void + */ + public function set_time_offset_from_option( $_, $new_option_value ) { + $this->time_offset = self::convert_offset_to_seconds( $new_option_value ); + } + + /** + * Calculate time offset in seconds from the settings array. + * + * @param array $offset Format: [ 'number' => 3, 'unit' => 'days' ] + * + * @return int + */ + protected static function convert_offset_to_seconds( $offset ) { + $default_offset = 3 * DAY_IN_SECONDS; + + if ( ! isset( $offset['unit'] ) || ! isset( $offset['number'] ) ) { + return $default_offset; + } + + switch ( $offset['unit'] ) { + case 'days': + return ( $offset['number'] * DAY_IN_SECONDS ); + case 'weeks': + return ( $offset['number'] * WEEK_IN_SECONDS ); + case 'months': + return ( $offset['number'] * MONTH_IN_SECONDS ); + case 'years': + return ( $offset['number'] * YEAR_IN_SECONDS ); + default: + return $default_offset; + } + } + + /** + * Maybe schedule a notification action for given subscription and timestamp. + * + * Will *not* schedule notification if: + * - the notifications are globally disabled, + * - the subscription isn't active/pending-cancel, + * - the subscription's billing cycle is less than 3 days, + * - there is already the same action scheduled for the same subscription and time. + * + * If only the time differs, the previous scheduled action will be unscheduled and a new one will replace it. + * + * @param WC_Subscription $subscription Subscription to schedule the action for. + * @param string $action Action ID to schedule. + * @param int $timestamp Time to schedule the notification for. + * + * @return void + */ + protected function maybe_schedule_notification( $subscription, $action, $timestamp ) { + if ( ! WC_Subscriptions_Email_Notifications::notifications_globally_enabled() ) { + return; + } + + if ( ! $subscription->has_status( [ 'active', 'pending-cancel' ] ) ) { + return; + } + + if ( self::is_subscription_period_too_short( $subscription ) ) { + return; + } + + $action_args = self::get_action_args( $subscription ); + + $next_scheduled = as_next_scheduled_action( $action, $action_args, self::$notifications_as_group ); + + if ( $timestamp === $next_scheduled ) { + return; + } + + $this->unschedule_actions( $action, $action_args ); + + // Only reschedule if it's in the future + if ( $timestamp <= time() ) { + return; + } + + as_schedule_single_action( $timestamp, $action, $action_args, self::$notifications_as_group ); + } + + /** + * Subtract time offset from given datetime based on the settings and subscription properties and return resulting timestamp. + * + * @param string $datetime + * @param WC_Subscription $subscription + * @param string $notification_type Can be 'trial_end', 'next_payment' or 'end'. + * + * @return int + */ + protected function subtract_time_offset( $datetime, $subscription, $notification_type ) { + $dt = new DateTime( $datetime, new DateTimeZone( 'UTC' ) ); + + return $dt->getTimestamp() - $this->get_time_offset( $subscription, $notification_type ); + } + + /** + * Get the notification action name based on the date type. + * + * @param string $date_type + * + * @return string + */ + public static function get_action_from_date_type( $date_type ) { + $action = ''; + + switch ( $date_type ) { + case 'trial_end': + $action = 'woocommerce_scheduled_subscription_customer_notification_trial_expiration'; + break; + case 'next_payment': + $action = 'woocommerce_scheduled_subscription_customer_notification_renewal'; + break; + case 'end': + $action = 'woocommerce_scheduled_subscription_customer_notification_expiration'; + break; + } + + return $action; + } + + /** + * Update notifications when subscription gets updated. + * + * To make batch processing easier, we need to handle the following use case: + * 1. Subscription S1 gets updated. + * 2. Notification config gets updated, a batch to fix all subscriptions is started and processes all subscriptions + * with update time before the config got updated. + * 3. Subscription S1 gets updated before it gets processed by the batch process. + * + * Thus, we update notifications for all subscriptions that are being updated after notification config change time + * and which have their update time before that. + * + * As this gets called on Subscription save, the modification timestamp should be updated, too, and thus + * the currently updated subscription no longer needs to be processed by the batch process. + * + * @param WC_Subscription $subscription + * @param $subscription_data_store + * + * @return void + */ + public function update_notifications( $subscription, $subscription_data_store ) { + if ( ! $subscription->has_status( 'active' ) && ! $subscription->has_status( 'pending-cancel' ) ) { + return; + } + + // Here, we need the 'old' update timestamp for comparison, so can't use get_date_modified() method. + $subscription_update_time_raw = array_key_exists( 'date_modified', $subscription->get_data() ) ? $subscription->get_data()['date_modified'] : $subscription->get_date_created(); + if ( ! $subscription_update_time_raw ) { + $subscription_update_utc_timestamp = 0; + } else { + $subscription_update_time_raw->setTimezone( new DateTimeZone( 'UTC' ) ); + $subscription_update_utc_timestamp = $subscription_update_time_raw->getTimestamp(); + } + + $notification_settings_update_utc_timestamp = get_option( 'wcs_notification_settings_update_time', 0 ); + + if ( $subscription_update_utc_timestamp < $notification_settings_update_utc_timestamp ) { + $this->schedule_all_notifications( $subscription ); + } + } + + /** + * Schedule a notification with given type for given subscription. + * + * Date/time is determined automatically based on notification type, dates stored on the subscription, + * and offset WCS_Action_Scheduler_Customer_Notifications::$time_offset. + * + * @param WC_Subscription $subscription + * @param string $notification_type + * + * @return void + */ + protected function schedule_notification( $subscription, $notification_type ) { + $action_name = self::get_action_from_date_type( $notification_type ); + + $event_date = $subscription->get_date( $notification_type ); + $timestamp = $this->subtract_time_offset( $event_date, $subscription, $notification_type ); + + $this->maybe_schedule_notification( + $subscription, + $action_name, + $timestamp + ); + } + + /** + * Schedule all notifications for a subscription based on the dates defined on the subscription. + * + * Which notifications are needed for the subscription is determined by \WCS_Action_Scheduler_Customer_Notifications::get_valid_notifications. + * + * @param WC_Subscription $subscription + * + * @return void + */ + protected function schedule_all_notifications( $subscription ) { + $valid_notifications = self::get_valid_notifications( $subscription ); + $actual_notifications = $this->get_notifications( $subscription ); + + // Unschedule notifications that aren't valid for this subscription. + $notifications_to_unschedule = array_diff( $actual_notifications, $valid_notifications ); + foreach ( $notifications_to_unschedule as $notification_type ) { + $this->unschedule_actions( self::get_action_from_date_type( $notification_type ), self::get_action_args( $subscription ) ); + } + + // Schedule/check scheduling for valid notifications. + foreach ( $valid_notifications as $notification_type ) { + $this->schedule_notification( $subscription, $notification_type ); + } + } + + /** + * Set which date types are affecting the notifications. + * + * Currently, only trial_end, end and next_payment are being used. + * + * @return void + */ + public function set_date_types_to_schedule() { + $this->date_types_to_schedule = [ + 'trial_end', + 'next_payment', + 'end', + ]; + } + + /** + * Schedule notifications if the date has changed. + * + * @param object $subscription An instance of a WC_Subscription object + * @param string $date_type Can be 'trial_end', 'next_payment', 'payment_retry', 'end', 'end_of_prepaid_term' or a custom date type + * @param string $datetime A MySQL formatted date/time string in the GMT/UTC timezone. + */ + public function update_date( $subscription, $date_type, $datetime ) { + if ( in_array( $date_type, $this->get_date_types_to_schedule(), true ) ) { + $this->schedule_all_notifications( $subscription ); + } + } + + /** + * Schedule notifications if the date has been deleted. + * + * @param WC_Subscription $subscription An instance of a WC_Subscription object + * @param string $date_type Can be 'trial_end', 'next_payment', 'end', 'end_of_prepaid_term' or a custom date type + */ + public function delete_date( $subscription, $date_type ) { + $action = $this->get_action_from_date_type( $date_type ); + if ( $action ) { + $this->unschedule_actions( $action, self::get_action_args( $subscription ) ); + } + + $this->schedule_all_notifications( $subscription ); + } + + /** + * Unschedule all notifications for a subscription. + * + * @param object $subscription An instance of a WC_Subscription object + * @param array $exceptions Array of notification actions to not unschedule + * + * @return void + */ + public function unschedule_all_notifications( $subscription = null, $exceptions = [] ) { + foreach ( self::$notification_actions as $action ) { + if ( in_array( $action, $exceptions, true ) ) { + continue; + } + + $this->unschedule_actions( $action, self::get_action_args( $subscription ) ); + } + } + + /** + * When a subscription's status is updated, maybe schedule an event + * + * @param object $subscription An instance of a WC_Subscription object + * @param string $new_status New subscription status + * @param string $old_status Previous subscription status + */ + public function update_status( $subscription, $new_status, $old_status ) { + + switch ( $new_status ) { + case 'active': + // Schedule new notifications (will also unschedule unneeded ones). + $this->schedule_all_notifications( $subscription ); + break; + case 'pending-cancel': + // Unschedule all except expiration notification. + $this->unschedule_all_notifications( $subscription, [ 'woocommerce_scheduled_subscription_customer_notification_expiration' ] ); + break; + case 'on-hold': + case 'cancelled': + case 'switched': + case 'expired': + case 'trash': + $this->unschedule_all_notifications( $subscription ); + break; + } + } + + /** + * Get the args to set on the scheduled action. + * + * @param WC_Subscription|null $subscription An instance of WC_Subscription to get the hook for + * + * @return array Array of name => value pairs stored against the scheduled action. + */ + public static function get_action_args( $subscription ) { + if ( ! $subscription ) { + return []; + } + + $action_args = [ 'subscription_id' => $subscription->get_id() ]; + + return $action_args; + } + + /** + * Get the args to set on the scheduled action. + * + * @param string $action_hook Name of event used as the hook for the scheduled action. + * @param array $action_args Array of name => value pairs stored against the scheduled action. + */ + protected function unschedule_actions( $action_hook, $action_args = [] ) { + as_unschedule_all_actions( $action_hook, $action_args, self::$notifications_as_group ); + } + + /** + * Returns true if given date for subscription is now or in the future. + * + * @param WC_Subscription $subscription Subscription whose date is examined. + * @param string $date_type Date type to evaluate. + * + * @return bool + */ + protected static function is_date_in_the_future_or_now( $subscription, $date_type ) { + $dt = new DateTime( $subscription->get_date( $date_type ), new DateTimeZone( 'UTC' ) ); + $timestamp = $dt->getTimestamp(); + + return $timestamp >= time(); + } + + /** + * Return an array of notifications valid for given subscription based on the dates set on the subscription. + * + * This method doesn't take status into account. That's done in \WCS_Action_Scheduler_Customer_Notifications::update_status. + * + * Possible values in the array: 'end', 'trial_end', 'next_payment'. + * + * @param WC_Subscription $subscription + * + * @return array + * @throws Exception + */ + public static function get_valid_notifications( $subscription ) { + $notifications = []; + + if ( $subscription->get_date( 'end' ) && self::is_date_in_the_future_or_now( $subscription, 'end' ) ) { + $notifications[] = 'end'; + } + + if ( $subscription->get_date( 'trial_end' ) && self::is_date_in_the_future_or_now( $subscription, 'trial_end' ) ) { + $notifications[] = 'trial_end'; + } + + if ( $subscription->get_date( 'next_payment' ) ) { + + // Renewal notification is only valid after the trial ended. + $trial_end = $subscription->get_date( 'trial_end' ); + if ( $trial_end ) { + $trial_end_dt = new DateTime( $trial_end, new DateTimeZone( 'UTC' ) ); + $trial_end_timestamp = $trial_end_dt->getTimestamp(); + + if ( $trial_end_timestamp < time() && self::is_date_in_the_future_or_now( $subscription, 'next_payment' ) ) { + $notifications[] = 'next_payment'; + } + } elseif ( self::is_date_in_the_future_or_now( $subscription, 'next_payment' ) ) { + $notifications[] = 'next_payment'; + } + } + + return $notifications; + } + + /** + * Returns a list of currently scheduled notifications for a subscription. + * + * Notifications are identified by the date type of the subscription. + * I.e. possible values are: 'end', 'trial_end' and 'next_payment'. + * + * @param $subscription + * + * @return array + */ + public function get_notifications( $subscription ) { + $notifications = []; + + $date_types = $this->get_date_types_to_schedule(); + + foreach ( $date_types as $date_type ) { + $next_scheduled = as_next_scheduled_action( + self::get_action_from_date_type( $date_type ), + self::get_action_args( $subscription ), + self::$notifications_as_group + ); + if ( $next_scheduled ) { + $notifications[] = $date_type; + } + } + + return $notifications; + } +} diff --git a/vendor/woocommerce/subscriptions-core/includes/class-wcs-batch-processing-controller.php b/vendor/woocommerce/subscriptions-core/includes/class-wcs-batch-processing-controller.php new file mode 100644 index 0000000..c2c2b23 --- /dev/null +++ b/vendor/woocommerce/subscriptions-core/includes/class-wcs-batch-processing-controller.php @@ -0,0 +1,572 @@ +handle_watchdog_action(); + } + ); + + add_action( + self::PROCESS_SINGLE_BATCH_ACTION_NAME, + function ( $batch_process ) { + $this->process_next_batch_for_single_processor( $batch_process ); + }, + 10, + 2 + ); + + add_action( + 'shutdown', + function () { + $this->remove_or_retry_failed_processors(); + } + ); + + $this->logger = wc_get_logger(); + } + + /** + * Get the singleton instance of this class. + * + * @return WCS_Batch_Processing_Controller + */ + final public static function instance(): WCS_Batch_Processing_Controller { + if ( ! isset( self::$instance ) ) { + self::$instance = new self(); + } + return self::$instance; + } + + /** + * Enqueue a processor so that it will get batch processing requests from within scheduled actions. + * + * @param string $processor_class_name Fully qualified class name of the processor, must implement `WCS_Batch_Processor`. + */ + public function enqueue_processor( string $processor_class_name ): void { + $pending_updates = $this->get_enqueued_processors(); + if ( ! in_array( $processor_class_name, array_keys( $pending_updates ), true ) ) { + $pending_updates[] = $processor_class_name; + $this->set_enqueued_processors( $pending_updates ); + } + $this->schedule_watchdog_action( false, true ); + } + + /** + * Schedule the watchdog action. + * + * @param bool $with_delay Whether to delay the action execution. Should be true when rescheduling, false when enqueueing. + * @param bool $unique Whether to make the action unique. + */ + private function schedule_watchdog_action( bool $with_delay = false, bool $unique = false ): void { + $time = time(); + if ( $with_delay ) { + /** + * Modify the delay interval for the batch processor's watchdog events. + * + * @since 7.7.0 + * + * @param int $delay Time, in seconds, before the watchdog process will run. Defaults to 3600 (1 hour). + */ + $time += apply_filters( 'wcs_batch_processor_watchdog_delay_seconds', HOUR_IN_SECONDS ); + } + + if ( ! as_has_scheduled_action( self::WATCHDOG_ACTION_NAME ) ) { + as_schedule_single_action( + $time, + self::WATCHDOG_ACTION_NAME, + array(), + self::ACTION_GROUP, + $unique + ); + } + } + + /** + * Schedule a processing action for all the processors that are enqueued but not scheduled + * (because they have just been enqueued, or because the processing for a batch failed). + */ + private function handle_watchdog_action(): void { + $pending_processes = $this->get_enqueued_processors(); + if ( empty( $pending_processes ) ) { + return; + } + foreach ( $pending_processes as $process_name ) { + if ( ! $this->is_scheduled( $process_name ) ) { + $this->schedule_batch_processing( $process_name ); + } + } + $this->schedule_watchdog_action( true ); + } + + /** + * Process a batch for a single processor, and handle any required rescheduling or state cleanup. + * + * @param string $processor_class_name Fully qualified class name of the processor. + * + * @throws \Exception If error occurred during batch processing. + */ + private function process_next_batch_for_single_processor( string $processor_class_name ): void { + if ( ! $this->is_enqueued( $processor_class_name ) ) { + return; + } + + $batch_processor = $this->get_processor_instance( $processor_class_name ); + $error = $this->process_next_batch_for_single_processor_core( $batch_processor ); + $still_pending = count( $batch_processor->get_next_batch_to_process( 1 ) ) > 0; + if ( ( $error instanceof \Exception ) ) { + // The batch processing failed and no items were processed: + // reschedule the processing with a delay, unless this is a repeatead failure. + if ( $this->is_consistently_failing( $batch_processor ) ) { + $this->log_consistent_failure( $batch_processor, $this->get_process_details( $batch_processor ) ); + $this->remove_processor( $processor_class_name ); + } else { + $this->schedule_batch_processing( $processor_class_name, true ); + } + + throw $error; + } + if ( $still_pending ) { + $this->schedule_batch_processing( $processor_class_name ); + } else { + $this->dequeue_processor( $processor_class_name ); + } + } + + /** + * Process a batch for a single processor, updating state and logging any error. + * + * @param WCS_Batch_Processor $batch_processor Batch processor instance. + * + * @return null|\Exception Exception if error occurred, null otherwise. + */ + private function process_next_batch_for_single_processor_core( WCS_Batch_Processor $batch_processor ): ?\Exception { + $details = $this->get_process_details( $batch_processor ); + $time_start = microtime( true ); + $batch = $batch_processor->get_next_batch_to_process( $details['current_batch_size'] ); + if ( empty( $batch ) ) { + return null; + } + try { + $batch_processor->process_batch( $batch ); + $time_taken = microtime( true ) - $time_start; + $this->update_processor_state( $batch_processor, $time_taken ); + } catch ( \Exception $exception ) { + $time_taken = microtime( true ) - $time_start; + $this->log_error( $exception, $batch_processor, $batch ); + $this->update_processor_state( $batch_processor, $time_taken, $exception ); + return $exception; + } + return null; + } + + /** + * Get the current state for a given enqueued processor. + * + * @param WCS_Batch_Processor $batch_processor Batch processor instance. + * + * @return array Current state for the processor, or a "blank" state if none exists yet. + */ + private function get_process_details( WCS_Batch_Processor $batch_processor ): array { + $defaults = array( + 'total_time_spent' => 0, + 'current_batch_size' => $batch_processor->get_default_batch_size(), + 'last_error' => null, + 'recent_failures' => 0, + 'batch_first_failure' => null, + 'batch_last_failure' => null, + ); + + $process_details = get_option( $this->get_processor_state_option_name( $batch_processor ) ); + $process_details = wp_parse_args( is_array( $process_details ) ? $process_details : array(), $defaults ); + + return $process_details; + } + + /** + * Get the name of the option where we will be saving state for a given processor. + * + * @param WCS_Batch_Processor|string $batch_processor Batch processor instance or class name. + * + * @return string Option name. + */ + private function get_processor_state_option_name( $batch_processor ): string { + $class_name = is_a( $batch_processor, WCS_Batch_Processor::class ) ? get_class( $batch_processor ) : $batch_processor; + $class_md5 = md5( $class_name ); + // truncate the class name so we know that it will fit in the option name column along with md5 hash and prefix. + $class_name = substr( $class_name, 0, 140 ); + return 'wcs_batch_' . $class_name . '_' . $class_md5; + } + + /** + * Update the state for a processor after a batch has completed processing. + * + * @param WCS_Batch_Processor $batch_processor Batch processor instance. + * @param float $time_taken Time take by the batch to complete processing. + * @param \Exception|null $last_error Exception object in processing the batch, if there was one. + */ + private function update_processor_state( WCS_Batch_Processor $batch_processor, float $time_taken, \Exception $last_error = null ): void { + $current_status = $this->get_process_details( $batch_processor ); + $current_status['total_time_spent'] += $time_taken; + $current_status['last_error'] = null !== $last_error ? $last_error->getMessage() : null; + + if ( null !== $last_error ) { + $current_status['recent_failures'] = ( $current_status['recent_failures'] ?? 0 ) + 1; + $current_status['batch_last_failure'] = current_time( 'mysql' ); + + if ( is_null( $current_status['batch_first_failure'] ) ) { + $current_status['batch_first_failure'] = $current_status['batch_last_failure']; + } + } else { + $current_status['recent_failures'] = 0; + $current_status['batch_first_failure'] = null; + $current_status['batch_last_failure'] = null; + } + + update_option( $this->get_processor_state_option_name( $batch_processor ), $current_status, false ); + } + + /** + * Removes the option where we store state for a given processor. + * + * @param string $processor_class_name Fully qualified class name of the processor. + */ + private function clear_processor_state( string $processor_class_name ): void { + delete_option( $this->get_processor_state_option_name( $processor_class_name ) ); + } + + /** + * Schedule a processing action for a single processor. + * + * @param string $processor_class_name Fully qualified class name of the processor. + * @param bool $with_delay Whether to schedule the action for immediate execution or for later. + */ + private function schedule_batch_processing( string $processor_class_name, bool $with_delay = false ): void { + $time = $with_delay ? time() + MINUTE_IN_SECONDS : time(); + as_schedule_single_action( $time, self::PROCESS_SINGLE_BATCH_ACTION_NAME, array( $processor_class_name ) ); + } + + /** + * Check if a batch processing action is already scheduled for a given processor. + * Differs from `as_has_scheduled_action` in that this excludes actions in progress. + * + * @param string $processor_class_name Fully qualified class name of the batch processor. + * + * @return bool True if a batch processing action is already scheduled for the processor. + */ + public function is_scheduled( string $processor_class_name ): bool { + return as_has_scheduled_action( self::PROCESS_SINGLE_BATCH_ACTION_NAME, array( $processor_class_name ) ); + } + + /** + * Get an instance of a processor given its class name. + * + * @param string $processor_class_name Full class name of the batch processor. + * + * @return WCS_Batch_Processor Instance of batch processor for the given class. + * @throws \Exception If it's not possible to get an instance of the class. + */ + private function get_processor_instance( string $processor_class_name ): WCS_Batch_Processor { + if ( ! isset( $processor ) && class_exists( $processor_class_name ) ) { + // This is a fallback for when the batch processor is not registered in the container. + $processor = new $processor_class_name(); + } + if ( ! is_a( $processor, WCS_Batch_Processor::class ) ) { + throw new \Exception( "Unable to initialize batch processor instance for $processor_class_name" ); + } + return $processor; + } + + /** + * Helper method to get list of all the enqueued processors. + * + * @return array List (of string) of the class names of the enqueued processors. + */ + public function get_enqueued_processors(): array { + $enqueued_processors = get_option( self::ENQUEUED_PROCESSORS_OPTION_NAME, array() ); + if ( ! is_array( $enqueued_processors ) ) { + $this->logger->error( 'Could not fetch list of processors. Clearing up queue.', array( 'source' => self::LOGS_CONTEXT ) ); + delete_option( self::ENQUEUED_PROCESSORS_OPTION_NAME ); + $enqueued_processors = array(); + } + + return $enqueued_processors; + } + + /** + * Dequeue a processor once it has no more items pending processing. + * + * @param string $processor_class_name Full processor class name. + */ + private function dequeue_processor( string $processor_class_name ): void { + $pending_processes = $this->get_enqueued_processors(); + if ( in_array( $processor_class_name, $pending_processes, true ) ) { + $this->clear_processor_state( $processor_class_name ); + $pending_processes = array_diff( $pending_processes, array( $processor_class_name ) ); + $this->set_enqueued_processors( $pending_processes ); + } + } + + /** + * Helper method to set the enqueued processor class names. + * + * @param array $processors List of full processor class names. + */ + private function set_enqueued_processors( array $processors ): void { + update_option( self::ENQUEUED_PROCESSORS_OPTION_NAME, $processors, false ); + } + + /** + * Check if a particular processor is enqueued. + * + * @param string $processor_class_name Fully qualified class name of the processor. + * + * @return bool True if the processor is enqueued. + */ + public function is_enqueued( string $processor_class_name ): bool { + return in_array( $processor_class_name, $this->get_enqueued_processors(), true ); + } + + /** + * Dequeue and de-schedule a processor instance so that it won't be processed anymore. + * + * @param string $processor_class_name Fully qualified class name of the processor. + * @return bool True if the processor has been dequeued, false if the processor wasn't enqueued (so nothing has been done). + */ + public function remove_processor( string $processor_class_name ): bool { + $enqueued_processors = $this->get_enqueued_processors(); + if ( ! in_array( $processor_class_name, $enqueued_processors, true ) ) { + return false; + } + + $enqueued_processors = array_diff( $enqueued_processors, array( $processor_class_name ) ); + if ( empty( $enqueued_processors ) ) { + $this->force_clear_all_processes(); + } else { + update_option( self::ENQUEUED_PROCESSORS_OPTION_NAME, $enqueued_processors, false ); + as_unschedule_all_actions( self::PROCESS_SINGLE_BATCH_ACTION_NAME, array( $processor_class_name ) ); + $this->clear_processor_state( $processor_class_name ); + } + + return true; + } + + /** + * Dequeues and de-schedules all the processors. + */ + public function force_clear_all_processes(): void { + as_unschedule_all_actions( self::PROCESS_SINGLE_BATCH_ACTION_NAME ); + as_unschedule_all_actions( self::WATCHDOG_ACTION_NAME ); + + foreach ( $this->get_enqueued_processors() as $processor ) { + $this->clear_processor_state( $processor ); + } + + update_option( self::ENQUEUED_PROCESSORS_OPTION_NAME, array(), false ); + } + + /** + * Log an error that happened while processing a batch. + * + * @param \Exception $error Exception object to log. + * @param WCS_Batch_Processor $batch_processor Batch processor instance. + * @param array $batch Batch that was being processed. + */ + protected function log_error( \Exception $error, WCS_Batch_Processor $batch_processor, array $batch ): void { + $error_message = "Error processing batch for {$batch_processor->get_name()}: {$error->getMessage()}"; + $error_context = array( + 'exception' => $error, + 'source' => self::LOGS_CONTEXT, + ); + + // Log only first and last, as the entire batch may be too big. + if ( count( $batch ) > 0 ) { + $error_context = array_merge( + $error_context, + array( + 'batch_start' => $batch[0], + 'batch_end' => end( $batch ), + ) + ); + } + + /** + * Filters the error message for a batch processing. + * + * @param string $error_message The error message that will be logged. + * @param \Exception $error The exception that was thrown by the processor. + * @param WCS_Batch_Processor $batch_processor The processor that threw the exception. + * @param array $batch The batch that was being processed. + * @param array $error_context Context to be passed to the logging function. + * @return string The actual error message that will be logged. + * + * @since 7.7.0 + */ + $error_message = apply_filters( 'wcs_batch_processing_log_message', $error_message, $error, $batch_processor, $batch, $error_context ); + + $this->logger->error( $error_message, $error_context ); + } + + /** + * Determines whether a given processor is consistently failing based on how many recent consecutive failures it has had. + * + * @param WCS_Batch_Processor $batch_processor The processor that we want to check. + * @return boolean TRUE if processor is consistently failing. FALSE otherwise. + */ + private function is_consistently_failing( WCS_Batch_Processor $batch_processor ): bool { + $process_details = $this->get_process_details( $batch_processor ); + $max_attempts = absint( + /** + * Controls the failure threshold for batch processors. That is, the number of times we'll attempt to + * process a batch that has resulted in a failure. Once above this threshold, the processor won't be + * re-scheduled and will be removed from the queue. + * + * @since 7.7.0 + * + * @param int $failure_threshold Maximum number of times for the processor to try processing a given batch. + * @param WCS_Batch_Processor $batch_processor The processor instance. + * @param array $process_details Array with batch processor state. + */ + apply_filters( + 'wcs_batch_processing_max_attempts', + self::FAILING_PROCESS_MAX_ATTEMPTS_DEFAULT, + $batch_processor, + $process_details + ) + ); + + return absint( $process_details['recent_failures'] ?? 0 ) >= max( $max_attempts, 1 ); + } + + /** + * Creates log entry with details about a batch processor that is consistently failing. + * + * @param WCS_Batch_Processor $batch_processor The batch processor instance. + * @param array $process_details Failing process details. + */ + private function log_consistent_failure( WCS_Batch_Processor $batch_processor, array $process_details ): void { + $this->logger->error( + "Batch processor {$batch_processor->get_name()} appears to be failing consistently: {$process_details['recent_failures']} unsuccessful attempt(s). No further attempts will be made.", + array( + 'source' => self::LOGS_CONTEXT, + 'failures' => $process_details['recent_failures'], + 'first_failure' => $process_details['batch_first_failure'], + 'last_failure' => $process_details['batch_last_failure'], + ) + ); + } + + /** + * Hooked onto 'shutdown'. This cleanup routine checks enqueued processors and whether they are scheduled or not to + * either re-eschedule them or remove them from the queue. + * This prevents stale states where Action Scheduler won't schedule any more attempts but we still report the + * processor as enqueued. + * + */ + private function remove_or_retry_failed_processors(): void { + if ( ! did_action( 'wp_loaded' ) ) { + return; + } + + $last_error = error_get_last(); + if ( ! is_null( $last_error ) && in_array( $last_error['type'], array( E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR ), true ) ) { + return; + } + + // The most efficient way to check for an existing action is to use `as_has_scheduled_action`, but in unusual + // cases where another plugin has loaded a very old version of Action Scheduler, it may not be available to us. + $has_scheduled_action = function_exists( 'as_has_scheduled_action' ) ? 'as_has_scheduled_action' : 'as_next_scheduled_action'; + + if ( call_user_func( $has_scheduled_action, self::WATCHDOG_ACTION_NAME ) ) { + return; + } + + $enqueued_processors = $this->get_enqueued_processors(); + $unscheduled_processors = array_diff( $enqueued_processors, array_filter( $enqueued_processors, array( $this, 'is_scheduled' ) ) ); + + foreach ( $unscheduled_processors as $processor ) { + try { + $instance = $this->get_processor_instance( $processor ); + } catch ( \Exception $e ) { + continue; + } + + $exception = new \Exception( 'Processor is enqueued but not scheduled. Background job was probably killed or marked as failed. Reattempting execution.' ); + $this->update_processor_state( $instance, 0, $exception ); + $this->log_error( $exception, $instance, array() ); + + if ( $this->is_consistently_failing( $instance ) ) { + $this->log_consistent_failure( $instance, $this->get_process_details( $instance ) ); + $this->remove_processor( $processor ); + } else { + $this->schedule_batch_processing( $processor, true ); + } + } + } +} diff --git a/vendor/woocommerce/subscriptions-core/includes/class-wcs-cart-renewal.php b/vendor/woocommerce/subscriptions-core/includes/class-wcs-cart-renewal.php index a9692b2..c311eb2 100644 --- a/vendor/woocommerce/subscriptions-core/includes/class-wcs-cart-renewal.php +++ b/vendor/woocommerce/subscriptions-core/includes/class-wcs-cart-renewal.php @@ -1307,11 +1307,17 @@ public function remove_non_recurring_fees( $cart ) { return; } - $cart_fees = $cart->get_fees(); + $renewal_order = $this->get_order(); + + if ( ! $renewal_order ) { + return; + } // Fees are naturally recurring if they have been applied to the renewal order. Generate a key (name + amount) for each fee applied to the order. $renewal_order_fees = array(); - foreach ( $this->get_order()->get_fees() as $item_id => $fee_line_item ) { + $cart_fees = $cart->get_fees(); + + foreach ( $renewal_order->get_fees() as $item_id => $fee_line_item ) { $renewal_order_fees[ $item_id ] = $fee_line_item->get_name() . wc_format_decimal( $fee_line_item->get_total() ); } diff --git a/vendor/woocommerce/subscriptions-core/includes/class-wcs-core-autoloader.php b/vendor/woocommerce/subscriptions-core/includes/class-wcs-core-autoloader.php index 521ab5a..fbe37df 100644 --- a/vendor/woocommerce/subscriptions-core/includes/class-wcs-core-autoloader.php +++ b/vendor/woocommerce/subscriptions-core/includes/class-wcs-core-autoloader.php @@ -150,7 +150,8 @@ protected function is_class_abstract( $class ) { */ protected function is_class_interface( $class ) { static $interfaces = array( - 'wcs_cache_updater' => true, + 'wcs_cache_updater' => true, + 'wcs_batch_processor' => true, ); return isset( $interfaces[ $class ] ); @@ -227,7 +228,10 @@ protected function get_relative_class_path( $class ) { if ( false !== strpos( $class, 'handler' ) ) { $path .= '/deprecation-handlers'; } - } elseif ( false !== strpos( $class, 'email' ) && 'wc_subscriptions_email' !== $class ) { + } elseif ( false !== strpos( $class, 'email' ) + && 'wc_subscriptions_email' !== $class + && 'wc_subscriptions_email_notifications' !== $class + ) { $path .= '/emails'; } elseif ( false !== strpos( $class, 'gateway' ) && 'wc_subscriptions_change_payment_gateway' !== $class ) { $path .= '/gateways'; diff --git a/vendor/woocommerce/subscriptions-core/includes/class-wcs-notifications-batch-processor.php b/vendor/woocommerce/subscriptions-core/includes/class-wcs-notifications-batch-processor.php new file mode 100644 index 0000000..e244a55 --- /dev/null +++ b/vendor/woocommerce/subscriptions-core/includes/class-wcs-notifications-batch-processor.php @@ -0,0 +1,274 @@ +format( 'Y-m-d H:i:s' ); + } + + /** + * Get the total number of pending items that require processing. + * Once an item is successfully processed by 'process_batch' it shouldn't be included in this count. + * + * Note that once the processor is enqueued the batch processor controller will keep + * invoking `get_next_batch_to_process` and `process_batch` repeatedly until this method returns zero. + * + * Since this batch processor updates only subscriptions older than the settings update, + * it only selects subscriptions updated before the settings update time. + * + * @return int Number of items pending processing. + */ + public function get_total_pending_count(): int { + global $wpdb; + + if ( empty( $this->get_notification_settings_update_time() ) ) { + return 0; + } + + $allowed_statuses = $this->get_subscription_statuses(); + $placeholders = implode( ', ', array_fill( 0, count( $allowed_statuses ), '%s' ) ); + + if ( wcs_is_custom_order_tables_usage_enabled() ) { + return $wpdb->get_var( + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber + $wpdb->prepare( + "SELECT + COUNT(*) + FROM {$wpdb->prefix}wc_orders + WHERE type='shop_subscription' + AND date_updated_gmt < %s + AND status IN ($placeholders) + ", + $this->get_notification_settings_update_time(), + ...$allowed_statuses + ) + ); + } else { + return $wpdb->get_var( + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber + $wpdb->prepare( + "SELECT + COUNT(*) + FROM {$wpdb->prefix}posts + WHERE post_type='shop_subscription' + AND post_modified_gmt < %s + AND post_status IN ($placeholders) + ", + $this->get_notification_settings_update_time(), + ...$allowed_statuses + ) + ); + } + } + + /** + * Returns the next batch of items that need to be processed. + * + * A batch item can be anything needed to identify the actual processing to be done, + * but whenever possible items should be numbers (e.g. database record ids) + * or at least strings, to ease troubleshooting and logging in case of problems. + * + * The size of the batch returned can be less than $size if there aren't that + * many items pending processing (and it can be zero if there isn't anything to process), + * but the size should always be consistent with what 'get_total_pending_count' returns + * (i.e. the size of the returned batch shouldn't be larger than the pending items count). + * + * @param int $size Maximum size of the batch to be returned. + * + * @return array Batch of items to process, containing $size or less items. + */ + public function get_next_batch_to_process( int $size ): array { + global $wpdb; + + if ( empty( $this->get_notification_settings_update_time() ) ) { + return []; + } + + $allowed_statuses = $this->get_subscription_statuses(); + $placeholders = implode( ', ', array_fill( 0, count( $allowed_statuses ), '%s' ) ); + + $args = array_merge( + array( $this->get_notification_settings_update_time() ), + $allowed_statuses, + array( $size ), + ); + + if ( wcs_is_custom_order_tables_usage_enabled() ) { + return $wpdb->get_col( + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber + $wpdb->prepare( + "SELECT + id + FROM {$wpdb->prefix}wc_orders + WHERE type='shop_subscription' + AND date_updated_gmt < %s + AND status IN ($placeholders) + ORDER BY id ASC + LIMIT %d", + ...$args + ) + ); + } else { + return $wpdb->get_col( + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber + $wpdb->prepare( + "SELECT + ID + FROM {$wpdb->prefix}posts + WHERE post_type='shop_subscription' + AND post_modified_gmt < %s + AND post_status IN ($placeholders) + ORDER BY ID ASC + LIMIT %d", + ...$args + ) + ); + } + } + + /** + * Process data for the supplied batch: update all notifications for given batch of subscriptions. + * + * This method should be prepared to receive items that don't actually need processing + * (because they have been processed before) and ignore them, but if at least + * one of the batch items that actually need processing can't be processed, an exception should be thrown. + * + * Once an item has been processed it shouldn't be counted in 'get_total_pending_count' + * nor included in 'get_next_batch_to_process' anymore (unless something happens that causes it + * to actually require further processing). + * + * @throw \Exception Something went wrong while processing the batch. + * + * @param array $batch Batch to process, as returned by 'get_next_batch_to_process'. + */ + public function process_batch( array $batch ): void { + // Instantiating this again would hook another set of hooks for update_status and update_date. No bueno. + $subscriptions_notifications = WC_Subscriptions_Core_Plugin::instance()->notifications_scheduler; + + foreach ( $batch as $subscription_id ) { + $subscription = wcs_get_subscription( $subscription_id ); + + if ( ! $subscription ) { + continue; + } + + if ( WC_Subscriptions_Email_Notifications::notifications_globally_enabled() ) { + $subscriptions_notifications->update_status( $subscription, $subscription->get_status(), null ); + } else { + $subscriptions_notifications->unschedule_all_notifications( $subscription ); + } + + // Update the subscription's update time to mark it as updated. + $subscription->set_date_modified( time() ); + $subscription->save(); + } + } + + /** + * Default (preferred) batch size to pass to 'get_next_batch_to_process'. + * The controller will pass this size unless it's externally configured + * to use a different size. + * + * @return int Default batch size. + */ + public function get_default_batch_size(): int { + return 20; + } + + /** + * Start the background process for updating notifications. + * + * @return string Informative string to show after the tool is triggered in UI. + */ + public static function enqueue(): string { + $batch_processor = WCS_Batch_Processing_Controller::instance(); + if ( $batch_processor->is_enqueued( self::class ) ) { + return __( 'Background process for updating subscription notifications already started, nothing done.', 'woocommerce-subscriptions' ); + } + + $batch_processor->enqueue_processor( self::class ); + return __( 'Background process for updating subscription notifications started.', 'woocommerce-subscriptions' ); + } + + /** + * Stop the background process for updating notifications. + * + * @return string Informative string to show after the tool is triggered in UI. + */ + public static function dequeue(): string { + $batch_processor = WCS_Batch_Processing_Controller::instance(); + if ( ! $batch_processor->is_enqueued( self::class ) ) { + return __( 'Background process for updating subscription notifications not started, nothing done.', 'woocommerce-subscriptions' ); + } + + $batch_processor->remove_processor( self::class ); + return __( 'Background process for updating subscription notifications stopped.', 'woocommerce-subscriptions' ); + } +} diff --git a/vendor/woocommerce/subscriptions-core/includes/class-wcs-user-change-status-handler.php b/vendor/woocommerce/subscriptions-core/includes/class-wcs-user-change-status-handler.php index cd919cd..d8093af 100644 --- a/vendor/woocommerce/subscriptions-core/includes/class-wcs-user-change-status-handler.php +++ b/vendor/woocommerce/subscriptions-core/includes/class-wcs-user-change-status-handler.php @@ -46,7 +46,7 @@ public static function maybe_change_users_subscription() { */ public static function change_users_subscription( $subscription, $new_status ) { $subscription = ( ! is_object( $subscription ) ) ? wcs_get_subscription( $subscription ) : $subscription; - $changed = false; + $changed = false; do_action( 'woocommerce_before_customer_changed_subscription_to_' . $new_status, $subscription ); diff --git a/vendor/woocommerce/subscriptions-core/includes/data-stores/class-wcs-subscription-data-store-cpt.php b/vendor/woocommerce/subscriptions-core/includes/data-stores/class-wcs-subscription-data-store-cpt.php index cf87304..cd65a37 100644 --- a/vendor/woocommerce/subscriptions-core/includes/data-stores/class-wcs-subscription-data-store-cpt.php +++ b/vendor/woocommerce/subscriptions-core/includes/data-stores/class-wcs-subscription-data-store-cpt.php @@ -171,7 +171,7 @@ protected function read_order_data( &$subscription, $post_object ) { foreach ( $this->subscription_meta_keys_to_props as $meta_key => $prop_key ) { if ( 0 === strpos( $prop_key, 'schedule' ) || in_array( $meta_key, $this->subscription_internal_meta_keys ) ) { - + // Keeping this occurrence of `get_post_meta()` as get_post here does not work well. $meta_value = get_post_meta( $subscription->get_id(), $meta_key, true ); // Dates are set via update_dates() to make sure relationships between dates are validated @@ -201,7 +201,7 @@ protected function read_order_data( &$subscription, $post_object ) { * @see https://github.com/Prospress/woocommerce-subscriptions/issues/3036 */ if ( '3.5.0' === WC()->version ) { - $props_to_set['customer_id'] = get_post_meta( $subscription->get_id(), '_customer_user', true ); + $props_to_set['customer_id'] = $subscription->get_meta( '_customer_user', true ); } $subscription->update_dates( $dates_to_set ); diff --git a/vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-auto-renewal.php b/vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-auto-renewal.php new file mode 100644 index 0000000..4cbe88c --- /dev/null +++ b/vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-auto-renewal.php @@ -0,0 +1,60 @@ +plugin_id = 'woocommerce-subscriptions_'; + + $this->id = 'customer_notification_auto_renewal'; + $this->title = __( 'Customer Notification: Automatic renewal notice', 'woocommerce-subscriptions' ); + $this->description = __( 'Customer Notification: Automatic renewal notice emails are sent when customer\'s subscription is about to be renewed automatically.', 'woocommerce-subscriptions' ); + + $this->heading = __( 'Automatic renewal notice', 'woocommerce-subscriptions' ); + + $this->subject = sprintf( + // translators: $1: {site_title}, $2: {customers_first_name}, $3: {time_until_renewal}, variables that will be substituted when email is sent out + _x( '[%1$s] %2$s, your subscription automatically renews in %3$s!', 'default email subject for subscription\'s automatic renewal notice', 'woocommerce-subscriptions' ), + '{site_title}', + '{customers_first_name}', + '{time_until_renewal}', + ); + + $this->template_html = 'emails/customer-notification-auto-renewal.php'; + $this->template_plain = 'emails/plain/customer-notification-auto-renewal.php'; + $this->template_base = WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory( 'templates/' ); + + $this->customer_email = true; + + // Constructor in parent uses the values above in the initialization. + parent::__construct(); + } + + public function get_relevant_date_type() { + return 'next_payment'; + } + + /** + * Default content to show below main email content. + * + * @return string + */ + public function get_default_additional_content() { + return __( 'Thank you for being a loyal customer, {customers_first_name} — we appreciate your business.', 'woocommerce-subscriptions' ); + } +} diff --git a/vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-auto-trial-expiration.php b/vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-auto-trial-expiration.php new file mode 100644 index 0000000..225c2e0 --- /dev/null +++ b/vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-auto-trial-expiration.php @@ -0,0 +1,45 @@ +plugin_id = 'woocommerce-subscriptions_'; + + $this->id = 'customer_notification_auto_trial_expiry'; + $this->title = __( 'Customer Notification: Free trial expiration: automatic payment notice', 'woocommerce-subscriptions' ); + $this->description = __( 'Free trial expiry notification emails are sent when customer\'s free trial for an automatically renewd subscription is about to expire.', 'woocommerce-subscriptions' ); + + $this->heading = __( 'Free trial expiration: automatic payment notice', 'woocommerce-subscriptions' ); + // translators: $1: {site_title}, $2: {customers_first_name}, variables that will be substituted when email is sent out + $this->subject = sprintf( _x( '[%1$s] %2$s, your paid subscription starts soon!', 'default email subject for free trial expiry notification emails sent to the customer', 'woocommerce-subscriptions' ), '{site_title}', '{customers_first_name}' ); + + $this->template_html = 'emails/customer-notification-auto-trial-ending.php'; + $this->template_plain = 'emails/plain/customer-notification-auto-trial-ending.php'; + $this->template_base = WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory( 'templates/' ); + + $this->customer_email = true; + + // Constructor in parent uses the values above in the initialization. + parent::__construct(); + } + + public function get_relevant_date_type() { + return 'trial_end'; + } +} diff --git a/vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-manual-renewal.php b/vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-manual-renewal.php new file mode 100644 index 0000000..d322e07 --- /dev/null +++ b/vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-manual-renewal.php @@ -0,0 +1,55 @@ +plugin_id = 'woocommerce-subscriptions_'; + + $this->id = 'customer_notification_manual_renewal'; + $this->title = __( 'Customer Notification: Manual renewal notice', 'woocommerce-subscriptions' ); + $this->description = __( 'Customer Notification: Manual renewal notice are sent when customer\'s subscription needs to be manually renewed.', 'woocommerce-subscriptions' ); + + $this->heading = __( 'Manual renewal notice', 'woocommerce-subscriptions' ); + // translators: $1: {site_title}, $2: {customers_first_name}, variables that will be substituted when email is sent out + $this->subject = sprintf( _x( '[%1$s] %2$s, your subscription is ready to be renewed!', 'default email subject for notification for a manually renewed subscription sent to the customer', 'woocommerce-subscriptions' ), '{site_title}', '{customers_first_name}' ); + + $this->template_html = 'emails/customer-notification-manual-renewal.php'; + $this->template_plain = 'emails/plain/customer-notification-manual-renewal.php'; + $this->template_base = WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory( 'templates/' ); + + $this->customer_email = true; + + // Constructor in parent uses the values above in the initialization. + parent::__construct(); + } + + public function get_relevant_date_type() { + return 'next_payment'; + } + + /** + * Default content to show below main email content. + * + * @return string + */ + public function get_default_additional_content() { + return __( 'Thanks again for choosing {site_title}.', 'woocommerce-subscriptions' ); + } + +} diff --git a/vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-manual-trial-expiration.php b/vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-manual-trial-expiration.php new file mode 100644 index 0000000..276d5b9 --- /dev/null +++ b/vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-manual-trial-expiration.php @@ -0,0 +1,46 @@ +plugin_id = 'woocommerce-subscriptions_'; + + $this->id = 'customer_notification_manual_trial_expiry'; + $this->title = __( 'Customer Notification: Free trial expiration: manual payment required', 'woocommerce-subscriptions' ); + $this->description = __( 'Free trial expiry notification emails are sent when customer\'s free trial for a manually renewed subscription is about to expire.', 'woocommerce-subscriptions' ); + + $this->heading = __( 'Free trial expiration: manual payment required', 'woocommerce-subscriptions' ); + // translators: $1: {site_title}, $2: {customers_first_name}, variables that will be substituted when email is sent out. + $this->subject = sprintf( _x( '[%1$s] %2$s, your free trial is almost up!', 'default email subject for an email notification for a manually renewed subscription with free trial expiry emails sent to the customer', 'woocommerce-subscriptions' ), '{site_title}', '{customers_first_name}' ); + + $this->template_html = 'emails/customer-notification-manual-trial-ending.php'; + $this->template_plain = 'emails/plain/customer-notification-manual-trial-ending.php'; + $this->template_base = WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory( 'templates/' ); + + $this->customer_email = true; + + // Constructor in parent uses the values above in the initialization. + parent::__construct(); + } + + public function get_relevant_date_type() { + return 'trial_end'; + } +} diff --git a/vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-subscription-expiration.php b/vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-subscription-expiration.php new file mode 100644 index 0000000..24b8af8 --- /dev/null +++ b/vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification-subscription-expiration.php @@ -0,0 +1,54 @@ +plugin_id = 'woocommerce-subscriptions_'; + + $this->id = 'customer_notification_subscription_expiry'; + $this->title = __( 'Customer Notification: Subscription expiration notice', 'woocommerce-subscriptions' ); + $this->description = __( 'Subscription expiration notification emails are sent when customer\'s subscription is about to expire.', 'woocommerce-subscriptions' ); + + $this->heading = __( 'Subscription expiration notice', 'woocommerce-subscriptions' ); + // translators: $1: {site_title}, $2: {customers_first_name}, variables that will be substituted when email is sent out + $this->subject = sprintf( _x( '[%1$s] %2$s, your subscription is about to expire!', 'default email subject for subscription expiry notification email sent to the customer', 'woocommerce-subscriptions' ), '{site_title}', '{customers_first_name}' ); + + $this->template_html = 'emails/customer-notification-expiring-subscription.php'; + $this->template_plain = 'emails/plain/customer-notification-expiring-subscription.php'; + $this->template_base = WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory( 'templates/' ); + + $this->customer_email = true; + + // Constructor in parent uses the values above in the initialization. + parent::__construct(); + } + + public function get_relevant_date_type() { + return 'end'; + } + + /** + * Default content to show below main email content. + * + * @return string + */ + public function get_default_additional_content() { + return __( 'Thank you for choosing {site_title}, {customers_first_name}.', 'woocommerce-subscriptions' ); + } +} diff --git a/vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php b/vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php new file mode 100644 index 0000000..3e5283d --- /dev/null +++ b/vendor/woocommerce/subscriptions-core/includes/emails/class-wcs-email-customer-notification.php @@ -0,0 +1,296 @@ +placeholders = array_merge( + [ + '{customers_first_name}' => '', + '{time_until_renewal}' => '', + ], + $this->placeholders + ); + + parent::__construct(); + } + + /** + * Initialise Settings Form Fields - these are generic email options most will use. + */ + public function init_form_fields() { + /* translators: %s: list of placeholders */ + $placeholder_text = sprintf( __( 'Available placeholders: %s', 'woocommerce-subscriptions' ), '' . esc_html( implode( ', ', array_keys( $this->placeholders ) ) ) . '' ); + $this->form_fields = array( + 'enabled' => array( + 'title' => __( 'Enable/Disable', 'woocommerce-subscriptions' ), + 'type' => 'checkbox', + 'label' => __( 'Enable this email notification. Disabled automatically on staging sites.', 'woocommerce-subscriptions' ), + 'default' => 'yes', + ), + 'subject' => array( + 'title' => __( 'Subject', 'woocommerce-subscriptions' ), + 'type' => 'text', + 'desc_tip' => true, + 'description' => $placeholder_text, + 'placeholder' => $this->get_default_subject(), + 'default' => '', + ), + 'heading' => array( + 'title' => __( 'Email heading', 'woocommerce-subscriptions' ), + 'type' => 'text', + 'desc_tip' => true, + 'description' => $placeholder_text, + 'placeholder' => $this->get_default_heading(), + 'default' => '', + ), + 'additional_content' => array( + 'title' => __( 'Additional content', 'woocommerce-subscriptions' ), + 'description' => __( 'Text to appear below the main email content.', 'woocommerce-subscriptions' ) . ' ' . $placeholder_text, + 'css' => 'width:400px; height: 75px;', + 'placeholder' => __( 'N/A', 'woocommerce-subscriptions' ), + 'type' => 'textarea', + 'default' => $this->get_default_additional_content(), + 'desc_tip' => true, + ), + 'email_type' => array( + 'title' => __( 'Email type', 'woocommerce-subscriptions' ), + 'type' => 'select', + 'description' => __( 'Choose which format of email to send.', 'woocommerce-subscriptions' ), + 'default' => 'html', + 'class' => 'email_type wc-enhanced-select', + 'options' => $this->get_email_type_options(), + 'desc_tip' => true, + ), + ); + } + + /** + * Trigger function. + * + * @return void + */ + public function trigger( $subscription_id ) { + $subscription = wcs_get_subscription( $subscription_id ); + $this->object = $subscription; + $this->recipient = $subscription->get_billing_email(); + + if ( ! $this->should_send_reminder_email( $subscription ) ) { + return; + } + + $this->setup_locale(); + + try { + $this->placeholders['{customers_first_name}'] = $subscription->get_billing_first_name(); + $this->placeholders['{time_until_renewal}'] = $this->get_time_until_date( $subscription, 'next_payment' ); + + $result = $this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments() ); + + if ( $result ) { + /* translators: 1: Notification type, 2: customer's email. */ + $order_note_msg = sprintf( __( '%1$s was successfully sent to %2$s.', 'woocommerce-subscriptions' ), $this->title, $this->recipient ); + } else { + /* translators: 1: Notification type, 2: customer's email. */ + $order_note_msg = sprintf( __( 'Attempt to send %1$s to %2$s failed.', 'woocommerce-subscriptions' ), $this->title, $this->recipient ); + } + + $subscription->add_order_note( $order_note_msg ); + } finally { + $this->restore_locale(); + } + } + + /** + * Get content for the HTML-version of the email. + * + * @return string + */ + public function get_content_html() { + $subscription = $this->object; + + if ( wcs_can_user_renew_early( $subscription ) + && $subscription->payment_method_supports( 'subscription_date_changes' ) + && WCS_Early_Renewal_Manager::is_early_renewal_enabled() + && WCS_Manual_Renewal_Manager::is_manual_renewal_enabled() + ) { + $url_for_renewal = wcs_get_early_renewal_url( $subscription ); + $can_renew_early = true; + } else { + $url_for_renewal = $subscription->get_view_order_url(); + $can_renew_early = false; + } + + return wc_get_template_html( + $this->template_html, + [ + 'subscription' => $subscription, + 'order' => $subscription->get_parent(), + 'email_heading' => $this->get_heading(), + 'subscription_time_til_event' => $this->get_time_until_date( $subscription, $this->get_relevant_date_type() ), + 'subscription_event_date' => $this->get_formatted_date( $subscription, $this->get_relevant_date_type() ), + 'url_for_renewal' => $url_for_renewal, + 'can_renew_early' => $can_renew_early, + 'additional_content' => is_callable( + [ + $this, + 'get_additional_content', + ] + ) ? $this->get_additional_content() : '', + // WC 3.7 introduced an additional content field for all emails. + 'sent_to_admin' => false, + 'plain_text' => false, + 'email' => $this, + ], + '', + $this->template_base + ); + } + + /** + * Get content for the plain (text, non-HTML) version of the email. + * + * @return string + */ + public function get_content_plain() { + $subscription = $this->object; + + if ( wcs_can_user_renew_early( $subscription ) + && $subscription->payment_method_supports( 'subscription_date_changes' ) + && WCS_Early_Renewal_Manager::is_early_renewal_enabled() + && WCS_Manual_Renewal_Manager::is_manual_renewal_enabled() + ) { + $url_for_renewal = wcs_get_early_renewal_url( $subscription ); + $can_renew_early = true; + } else { + $url_for_renewal = $subscription->get_view_order_url(); + $can_renew_early = false; + } + + return wc_get_template_html( + $this->template_plain, + [ + 'subscription' => $subscription, + 'order' => $subscription->get_parent(), + 'email_heading' => $this->get_heading(), + 'subscription_time_til_event' => $this->get_time_until_date( $subscription, $this->get_relevant_date_type() ), + 'subscription_event_date' => $this->get_formatted_date( $subscription, $this->get_relevant_date_type() ), + 'url_for_renewal' => $url_for_renewal, + 'can_renew_early' => $can_renew_early, + 'additional_content' => is_callable( + [ + $this, + 'get_additional_content', + ] + ) ? $this->get_additional_content() : '', + // WC 3.7 introduced an additional content field for all emails. + 'sent_to_admin' => false, + 'plain_text' => true, + 'email' => $this, + ], + '', + $this->template_base + ); + } + + /** + * Returns number of days until date_type for subscription. + * + * This method is needed when sending out the emails as the email queue might be delayed, in which case the email + * should state the correct number of days until the date_type. + * + * @param WC_Subscription $subscription Subscription to check. + * @param string $date_type Date type to count days to. + * + * @return false|int|string Number of days from now until the date type event's time. Empty string if subscription doesn't have the date_type defined. False if DateTime can't process the data. + */ + public function get_time_until_date( $subscription, $date_type ) { + $next_event = $subscription->get_date( $date_type ); + + if ( ! $next_event ) { + return ''; + } + + $next_event_dt = new DateTime( $next_event, new DateTimeZone( 'UTC' ) ); + $now = new DateTime( 'now', new DateTimeZone( 'UTC' ) ); + + // Both dates to midnight so we only compare days, not hours. + $next_event_dt->setTime( 0, 0 ); + $now->setTime( 0, 0 ); + + // Add some buffer, otherwise it will claim that only 2 full days are left when in reality it's 2 days, 23 hours and 59 minutes. + $now->modify( '-1 hour' ); + return human_time_diff( $now->getTimestamp(), $next_event_dt->getTimestamp() ); + } + + /** + * Return subscription's date of date type in localized format. + * + * @param WC_Subscription $subscription + * @param string $date_type + * + * @return string + */ + public function get_formatted_date( $subscription, $date_type ) { + return date_i18n( wc_date_format(), $subscription->get_time( $date_type, 'site' ) ); + } + + /** + * Default content to show below main email content. + * + * @return string + */ + public function get_default_additional_content() { + return __( 'Thank you for choosing {site_title}!', 'woocommerce-subscriptions' ); + } + + /** + * Determine whether the customer reminder email should be sent and add an order note if it shouldn't. + * + * Reminder emails are not sent if: + * - The Customer Notification feature is disabled. + * - The store is a staging or development site. + * - The recipient email address is missing. + * - The subscription's billing cycle is too short. + * + * @param WC_Subscription $subscription + * + * @return bool + */ + public function should_send_reminder_email( $subscription ) { + $should_skip = []; + + if ( ! $this->is_enabled() ) { + $should_skip[] = __( 'Reminder emails disabled.', 'woocommerce-subscriptions' ); + } else { + if ( ! WC_Subscriptions_Email_Notifications::should_send_notification() ) { + $should_skip[] = __( 'Not a production site', 'woocommerce-subscriptions' ); + } + + if ( ! $this->get_recipient() ) { + $should_skip[] = __( 'Recipient not found', 'woocommerce-subscriptions' ); + } + + if ( WCS_Action_Scheduler_Customer_Notifications::is_subscription_period_too_short( $subscription ) ) { + $should_skip[] = __( 'Subscription billing cycle too short', 'woocommerce-subscriptions' ); + } + } + + if ( ! empty( $should_skip ) ) { + // translators: %1$s: email title, %2$s: list of reasons why email was skipped. + $subscription->add_order_note( sprintf( __( 'Skipped sending "%1$s": %2$s', 'woocommerce-subscriptions' ), $this->title, '
- ' . implode( '
- ', $should_skip ) ) ); + return false; + } + + return true; + } +} diff --git a/vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/admin/class-wcs-paypal-change-payment-method-admin.php b/vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/admin/class-wcs-paypal-change-payment-method-admin.php index e2c9888..d25da9a 100644 --- a/vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/admin/class-wcs-paypal-change-payment-method-admin.php +++ b/vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/admin/class-wcs-paypal-change-payment-method-admin.php @@ -43,7 +43,7 @@ public static function init() { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ public static function add_payment_meta_details( $payment_meta, $subscription ) { - $subscription_id = get_post_meta( $subscription->get_id(), '_paypal_subscription_id', true ); + $subscription_id = $subscription->get_meta( '_paypal_subscription_id', true ); if ( wcs_is_paypal_profile_a( $subscription_id, 'billing_agreement' ) || empty( $subscription_id ) ) { $label = 'PayPal Billing Agreement ID'; diff --git a/vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php b/vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php index 031b8e8..982f669 100644 --- a/vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php +++ b/vendor/woocommerce/subscriptions-core/includes/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php @@ -139,7 +139,7 @@ protected function process_ipn_request( $transaction_details ) { if ( isset( $transaction_details['txn_id'] ) ) { // Make sure the IPN request has not already been handled - $handled_transactions = get_post_meta( $subscription->get_id(), '_paypal_ipn_tracking_ids', true ); + $handled_transactions = $subscription->get_meta( '_paypal_ipn_tracking_ids', true ); if ( empty( $handled_transactions ) ) { $handled_transactions = array(); @@ -190,13 +190,13 @@ protected function process_ipn_request( $transaction_details ) { $transaction_order = wc_get_order( substr( $transaction_details['invoice'], strrpos( $transaction_details['invoice'], '-' ) + 1 ) ); // check if the failed signup has been previously recorded - if ( wcs_get_objects_property( $transaction_order, 'id' ) != get_post_meta( $subscription->get_id(), '_paypal_failed_sign_up_recorded', true ) ) { + if ( wcs_get_objects_property( $transaction_order, 'id' ) !== $subscription->get_meta( '_paypal_failed_sign_up_recorded', true ) ) { $is_renewal_sign_up_after_failure = true; } } // If the invoice ID doesn't match the default invoice ID and contains the string '-wcscpm-', the IPN is for a subscription payment method change - if ( 'subscr_signup' == $transaction_details['txn_type'] && false !== strpos( $transaction_details['invoice'], '-wcscpm-' ) ) { + if ( 'subscr_signup' === $transaction_details['txn_type'] && false !== strpos( $transaction_details['invoice'], '-wcscpm-' ) ) { $is_payment_change = true; } else { $is_payment_change = false; @@ -268,8 +268,8 @@ protected function process_ipn_request( $transaction_details ) { WC_Subscriptions_Change_Payment_Gateway::update_payment_method( $subscription, 'paypal' ); // We need to cancel the subscription now that the method has been changed successfully - if ( 'paypal' == get_post_meta( $subscription->get_id(), '_old_payment_method', true ) ) { - self::cancel_subscription( $subscription, get_post_meta( $subscription->get_id(), '_old_paypal_subscriber_id', true ) ); + if ( 'paypal' === $subscription->get_meta( '_old_payment_method', true ) ) { + self::cancel_subscription( $subscription, $subscription->get_meta( '_old_paypal_subscriber_id', true ) ); } $this->add_order_note( _x( 'IPN subscription payment method changed to PayPal.', 'when it is a payment change, and there is a subscr_signup message, this will be a confirmation message that PayPal accepted it being the new payment method', 'woocommerce-subscriptions' ), $subscription, $transaction_details ); @@ -353,7 +353,7 @@ protected function process_ipn_request( $transaction_details ) { update_post_meta( $subscription->get_id(), '_paypal_first_ipn_ignored_for_pdt', 'true' ); // Ignore the first IPN message if the PDT should have handled it (if it didn't handle it, it will have been dealt with as first payment), but set a flag to make sure we only ignore it once - } elseif ( $subscription->get_payment_count() == 1 && '' !== WCS_PayPal::get_option( 'identity_token' ) && 'true' != get_post_meta( $subscription->get_id(), '_paypal_first_ipn_ignored_for_pdt', true ) && false === $is_renewal_sign_up_after_failure ) { + } elseif ( $subscription->get_payment_count() === 1 && '' !== WCS_PayPal::get_option( 'identity_token' ) && 'true' !== $subscription->get_meta( '_paypal_first_ipn_ignored_for_pdt', true ) && false === $is_renewal_sign_up_after_failure ) { WC_Gateway_Paypal::log( 'IPN subscription payment ignored for subscription ' . $subscription->get_id() . ' due to PDT previously handling the payment.' ); @@ -367,9 +367,8 @@ protected function process_ipn_request( $transaction_details ) { update_post_meta( $subscription->get_id(), '_paypal_failed_sign_up_recorded', wcs_get_objects_property( $transaction_order, 'id' ) ); // We need to cancel the old subscription now that the method has been changed successfully - if ( 'paypal' == get_post_meta( $subscription->get_id(), '_old_payment_method', true ) ) { - - $profile_id = get_post_meta( $subscription->get_id(), '_old_paypal_subscriber_id', true ); + if ( 'paypal' === $subscription->get_meta( '_old_payment_method', true ) ) { + $profile_id = $subscription->get_meta( '_old_paypal_subscriber_id' ); // Make sure we don't cancel the current profile if ( $profile_id !== $transaction_details['subscr_id'] ) { diff --git a/vendor/woocommerce/subscriptions-core/includes/interfaces/interface-wcs-batch-processor.php b/vendor/woocommerce/subscriptions-core/includes/interfaces/interface-wcs-batch-processor.php new file mode 100644 index 0000000..bf979d4 --- /dev/null +++ b/vendor/woocommerce/subscriptions-core/includes/interfaces/interface-wcs-batch-processor.php @@ -0,0 +1,81 @@ +id, '_' . $min_or_max . '_price_variation_id', true ); + $variation_id = $this->get_meta( '_' . $min_or_max . '_price_variation_id', true ); if ( $display ) { if ( $variation = wc_get_product( $variation_id ) ) { @@ -109,7 +109,7 @@ public function get_variation_price( $min_or_max = 'min', $display = false ) { $price = ''; } } else { - $price = get_post_meta( $variation_id, '_price', true ); + $price = $this->get_meta( '_price', true ); } return apply_filters( 'woocommerce_get_variation_price', $price, $this, $min_or_max, $display ); @@ -299,7 +299,7 @@ public function get_price_html( $price = '' ) { $price .= wcs_get_price_html_from_text( $this ); } - $variation_id = get_post_meta( $this->id, '_min_price_variation_id', true ); + $variation_id = $this->get_meta( '_min_price_variation_id', true ); $variation = wc_get_product( $variation_id ); $tax_display_mode = get_option( 'woocommerce_tax_display_shop' ); @@ -358,7 +358,7 @@ public function get_price_html( $price = '' ) { * @return object WC_Product_Subscription or WC_Product_Subscription_Variation */ function get_meta( $meta_key = '', $single = true, $context = 'view' ) { - return get_post_meta( $this->get_id(), $meta_key, $single ); + return $this->get_meta( $meta_key, $single ); } /** diff --git a/vendor/woocommerce/subscriptions-core/includes/legacy/class-wc-subscription-legacy.php b/vendor/woocommerce/subscriptions-core/includes/legacy/class-wc-subscription-legacy.php index a7baf7c..17643aa 100644 --- a/vendor/woocommerce/subscriptions-core/includes/legacy/class-wc-subscription-legacy.php +++ b/vendor/woocommerce/subscriptions-core/includes/legacy/class-wc-subscription-legacy.php @@ -474,7 +474,7 @@ protected function get_prop( $prop, $context = 'view' ) { // The requires manual renewal prop uses boolean values but is stored as a string so needs special handling, it also needs to be handled before the checks on $this->$prop to avoid triggering __isset() & __get() magic methods for $this->requires_manual_renewal if ( 'requires_manual_renewal' === $prop ) { - $value = get_post_meta( $this->get_id(), '_' . $prop, true ); + $value = $this->get_meta( '_' . $prop, true ); if ( 'false' === $value || '' === $value ) { $value = false; @@ -482,7 +482,7 @@ protected function get_prop( $prop, $context = 'view' ) { $value = true; } } elseif ( ! isset( $this->$prop ) || empty( $this->$prop ) ) { - $value = get_post_meta( $this->get_id(), '_' . $prop, true ); + $value = $this->get_meta( '_' . $prop, true ); } else { $value = $this->$prop; } diff --git a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php index bb5c3b1..297a4d5 100644 --- a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php +++ b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wc-subscriptions-upgrader.php @@ -16,128 +16,60 @@ */ class WC_Subscriptions_Upgrader { + /** + * @var string The database version of Subscriptions. + */ private static $active_version; - private static $upgrade_limit_hooks; - - private static $upgrade_limit_subscriptions; - - private static $about_page_url; - - private static $old_subscription_count = null; - - public static $is_wc_version_2 = false; - - public static $updated_to_wc_2_0; + /** + * @var string The minimum supported version that this class can upgrade from. + */ + private static $minimum_supported_version = '3.0'; /** * @var array An array of WCS_Background_Updater objects used to run upgrade scripts in the background. */ protected static $background_updaters = array(); + /** + * Deprecated variables. + * + * @deprecated subscriptions-core 7.7.0 + */ + public static $is_wc_version_2 = false; + public static $updated_to_wc_2_0; + private static $upgrade_limit_subscriptions; + private static $about_page_url; + private static $old_subscription_count = null; + private static $upgrade_limit_hooks; + /** * Hooks upgrade function to init. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 */ public static function init() { - self::$active_version = get_option( WC_Subscriptions_Admin::$option_prefix . '_active_version', '0' ); - - self::$is_wc_version_2 = version_compare( get_option( 'woocommerce_db_version' ), '2.0', '>=' ); - self::$about_page_url = admin_url( 'admin.php?page=wc-admin' ); - - $version_out_of_date = version_compare( self::$active_version, WC_Subscriptions_Core_Plugin::instance()->get_library_version(), '<' ); - - // Set the cron lock on every request with an out of date version, regardless of authentication level, as we can only lock cron for up to 10 minutes at a time, but we need to keep it locked until the upgrade is complete, regardless of who is browsing the site - if ( $version_out_of_date ) { - self::set_cron_lock(); - } - - if ( isset( $_POST['action'] ) && 'wcs_upgrade' == $_POST['action'] ) { // We're checking for CSRF in ajax_upgrade - - add_action( 'wp_ajax_wcs_upgrade', __CLASS__ . '::ajax_upgrade', 10 ); - - } elseif ( @current_user_can( 'activate_plugins' ) ) { - - if ( isset( $_GET['wcs_upgrade_step'] ) || $version_out_of_date ) { - - $is_upgrading = get_option( 'wc_subscriptions_is_upgrading', false ); - - // Check if we've exceeded the 2 minute upgrade window we use for blocking upgrades (we could seemingly use transients here to get the check for free if transients were guaranteed to exist: http://journal.rmccue.io/296/youre-using-transients-wrong/) - if ( false !== $is_upgrading && $is_upgrading < gmdate( 'U' ) ) { - $is_upgrading = false; - delete_option( 'wc_subscriptions_is_upgrading' ); + $version_out_of_date = version_compare( self::$active_version, WC_Subscriptions_Core_Plugin::instance()->get_library_version(), '<' ); + + // Show warning that upgrades are no longer supported. + if ( '0' !== self::$active_version && version_compare( self::$active_version, self::$minimum_supported_version, '<=' ) ) { + add_action( + 'admin_notices', + function () { + self::show_unsupported_upgrade_path_notice(); } - - if ( false !== $is_upgrading ) { - - add_action( 'init', __CLASS__ . '::upgrade_in_progress_notice', 11 ); - - } else { - - // Run upgrades as soon as admin hits site - add_action( 'wp_loaded', __CLASS__ . '::upgrade', 11 ); - - } - } elseif ( is_admin() && isset( $_GET['page'] ) && 'wcs-about' == $_GET['page'] ) { - - add_action( 'admin_menu', __CLASS__ . '::updated_welcome_page' ); - - } + ); + return; } - // While the upgrade is in progress, we need to block PayPal IPN messages to avoid renewals failing to process - add_action( 'woocommerce_api_wc_gateway_paypal', __CLASS__ . '::maybe_block_paypal_ipn', 0 ); - - // Sometimes redirect to the Welcome/About page after an upgrade - add_action( 'woocommerce_subscriptions_upgraded', __CLASS__ . '::maybe_redirect_after_upgrade_complete', 100, 2 ); - - add_action( 'wcs_repair_end_of_prepaid_term_actions', __CLASS__ . '::repair_end_of_prepaid_term_actions' ); - - add_action( 'wcs_repair_subscriptions_containing_synced_variations', __CLASS__ . '::repair_subscription_contains_sync_meta' ); - - // When WC is updated from a version prior to 3.0 to a version after 3.0, add subscription address indexes. Must be hooked on before WC runs its updates, which occur on priority 5. - add_action( 'init', array( __CLASS__, 'maybe_add_subscription_address_indexes' ), 2 ); - - add_action( 'admin_notices', array( __CLASS__, 'maybe_display_external_object_cache_warning' ) ); - - add_action( 'init', array( __CLASS__, 'initialise_background_updaters' ), 0 ); - } - - /** - * Set limits on the number of items to upgrade at any one time based on the size of the site. - * - * The size of subscription at the time the upgrade is started is used to determine the batch size. - * - * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 - */ - protected static function set_upgrade_limits() { - - $total_initial_subscription_count = self::get_total_subscription_count( true ); - - if ( $total_initial_subscription_count > 5000 ) { - $base_upgrade_limit = 20; - } elseif ( $total_initial_subscription_count > 1500 ) { - $base_upgrade_limit = 30; - } else { - $base_upgrade_limit = 50; + if ( @current_user_can( 'activate_plugins' ) && $version_out_of_date ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors + // Run upgrades as soon as admin hits site + add_action( 'wp_loaded', [ __CLASS__, 'upgrade' ], 11 ); } - self::$upgrade_limit_hooks = apply_filters( 'woocommerce_subscriptions_hooks_to_upgrade', $base_upgrade_limit * 5 ); - self::$upgrade_limit_subscriptions = apply_filters( 'woocommerce_subscriptions_to_upgrade', $base_upgrade_limit ); - } - - /** - * Try to block WP-Cron until upgrading finishes. spawn_cron() will only let us steal the lock for 10 minutes into the future, so - * we can actually only block it for 9 minutes confidently. But as long as the upgrade process continues, the lock will remain. - * - * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 - */ - protected static function set_cron_lock() { - delete_transient( 'doing_cron' ); - set_transient( 'doing_cron', sprintf( '%.22F', 9 * MINUTE_IN_SECONDS + microtime( true ) ), 0 ); + add_action( 'init', [ __CLASS__, 'initialise_background_updaters' ], 0 ); } /** @@ -148,8 +80,6 @@ protected static function set_cron_lock() { public static function upgrade() { global $wpdb; - self::set_upgrade_limits(); - update_option( WC_Subscriptions_Admin::$option_prefix . '_previous_version', self::$active_version ); /** @@ -157,9 +87,8 @@ public static function upgrade() { */ do_action( 'woocommerce_subscriptions_before_upgrade', WC_Subscriptions_Core_Plugin::instance()->get_library_version(), self::$active_version ); - // Update the hold stock notification to be one week (if it's still at the default 60 minutes) to prevent cancelling subscriptions using manual renewals and payment methods that can take more than 1 hour (i.e. PayPal eCheck) - if ( '0' == self::$active_version || version_compare( self::$active_version, '1.4', '<' ) ) { - + if ( '0' === self::$active_version ) { + // Update the hold stock notification to be one week (if it's still at the default 60 minutes) to prevent cancelling subscriptions using manual renewals and payment methods that can take more than 1 hour (i.e. PayPal eCheck) $hold_stock_duration = get_option( 'woocommerce_hold_stock_minutes' ); if ( 60 == $hold_stock_duration ) { @@ -169,81 +98,13 @@ public static function upgrade() { // Allow products & subscriptions to be purchased in the same transaction update_option( 'woocommerce_subscriptions_multiple_purchase', 'yes' ); - } - - // Keep track of site url to prevent duplicate payments from staging sites, first added in 1.3.8 & updated with 1.4.2 to work with WP Engine staging sites - if ( '0' == self::$active_version || version_compare( self::$active_version, '1.4.2', '<' ) ) { + // Keep track of site url to prevent duplicate payments from staging sites, first added in 1.3.8 & updated with 1.4.2 to work with WP Engine staging sites WCS_Staging::set_duplicate_site_url_lock(); - } - - // Migrate products, WP-Cron hooks and subscriptions to the latest architecture, via Ajax - if ( '0' != self::$active_version && version_compare( self::$active_version, '2.0', '<' ) ) { - // Delete old cron locks - $deleted_rows = $wpdb->query( "DELETE FROM {$wpdb->options} WHERE `option_name` LIKE 'wcs\_blocker\_%'" ); - - WCS_Upgrade_Logger::add( sprintf( 'Deleted %d rows of "wcs_blocker_"', $deleted_rows ) ); - - self::ajax_upgrade_handler(); - } - - // Repair incorrect dates set when upgrading with 2.0.0 - if ( version_compare( self::$active_version, '2.0.0', '>=' ) && version_compare( self::$active_version, '2.0.2', '<' ) && self::migrated_subscription_count() > 0 ) { - self::ajax_upgrade_handler(); - } - - if ( '0' != self::$active_version && version_compare( self::$active_version, '2.1.0', '<' ) ) { - - // Delete cached subscription length ranges to force an update with 2.1 - WC_Subscriptions_Core_Plugin::instance()->cache->delete_cached( 'wcs-sub-ranges-' . get_locale() ); - WCS_Upgrade_Logger::add( 'v2.1: Deleted cached subscription ranges.' ); - WCS_Upgrade_2_1::set_cancelled_dates(); - - // Schedule report cache updates in the hopes that the data is ready and waiting for the store owner the first time they visit the reports pages - do_action( 'woocommerce_subscriptions_reports_schedule_cache_updates' ); - } - - // Repair missing end_of_prepaid_term scheduled actions - if ( version_compare( self::$active_version, '2.2.0', '>=' ) && version_compare( self::$active_version, '2.2.7', '<' ) ) { - WCS_Upgrade_2_2_7::schedule_end_of_prepaid_term_repair(); - } - - // Repair missing _contains_synced_subscription post meta - if ( version_compare( get_option( 'woocommerce_db_version' ), '3.0', '>=' ) && version_compare( self::$active_version, '2.2.0', '>=' ) && version_compare( self::$active_version, '2.2.9', '<' ) ) { - WCS_Upgrade_2_2_9::schedule_repair(); - } - - // Repair subscriptions suspended via PayPal. - if ( version_compare( self::$active_version, '2.1.4', '>=' ) && version_compare( self::$active_version, '2.3.0', '<' ) ) { - self::$background_updaters['2.3']['suspended_paypal_repair']->schedule_repair(); - } - - // If the store is running WC 3.0, repair subscriptions with missing address indexes. - if ( '0' !== self::$active_version && version_compare( self::$active_version, '2.3.0', '<' ) && version_compare( WC()->version, '3.0', '>=' ) ) { - self::$background_updaters['2.3']['address_indexes_repair']->schedule_repair(); - } - - if ( version_compare( self::$active_version, '2.3.0', '>=' ) && version_compare( self::$active_version, '2.3.3', '<' ) && wp_using_ext_object_cache() ) { - $has_transient_cache = $wpdb->get_var( "SELECT option_id FROM {$wpdb->prefix}options WHERE option_name LIKE '_transient_wcs-related-orders-to%' OR option_name LIKE '_transient_wcs_user_subscriptions_%' LIMIT 1;" ); - - if ( ! empty( $has_transient_cache ) ) { - update_option( 'wcs_display_2_3_3_warning', 'yes' ); - } - } - - if ( version_compare( self::$active_version, '2.4.0', '<' ) ) { - self::$background_updaters['2.4']['start_date_metadata']->schedule_repair(); - } - // Upon upgrading or installing 2.5.0 for the first time, enable or disable PayPal Standard for Subscriptions. - if ( version_compare( self::$active_version, '2.5.0', '<' ) ) { + // Upon installing for the first time, enable or disable PayPal Standard for Subscriptions. WCS_PayPal::set_enabled_for_subscriptions_default(); } - // Upon upgrading to 2.6.0 from a version after 2.2.0, schedule missing _has_trial line item meta repair. - if ( version_compare( self::$active_version, '2.6.0', '<' ) && version_compare( self::$active_version, '2.2.0', '>=' ) ) { - self::$background_updaters['2.6']['has_trial_item_meta']->schedule_repair(); - } - // Delete old subscription period string ranges transients. if ( version_compare( self::$active_version, '3.0.10', '<' ) ) { $deleted_rows = $wpdb->query( "DELETE FROM {$wpdb->options} WHERE `option_name` LIKE '_transient_timeout_wcs-sub-ranges-%' OR `option_name` LIKE '_transient_wcs-sub-ranges-%'" ); @@ -272,27 +133,185 @@ public static function upgrade() { } /** - * When an upgrade is complete, set the active version, delete the transient locking upgrade and fire a hook. + * When an upgrade is complete, set the active version and fire a hook. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 */ public static function upgrade_complete() { - update_option( WC_Subscriptions_Admin::$option_prefix . '_active_version', WC_Subscriptions_Core_Plugin::instance()->get_library_version() ); + do_action( 'woocommerce_subscriptions_upgraded', WC_Subscriptions_Core_Plugin::instance()->get_library_version(), self::$active_version ); + } - delete_transient( 'doing_cron' ); + /** + * Load and initialise the background updaters. + * + * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.4.0 + */ + public static function initialise_background_updaters() { + $logger = new WC_logger(); + self::$background_updaters['3.1']['subtracted_base_tax_repair'] = new WCS_Repair_Subtracted_Base_Tax_Line_Item_Meta( $logger ); - delete_option( 'wc_subscriptions_is_upgrading' ); + // Init the updaters + foreach ( self::$background_updaters as $version => $updaters ) { + foreach ( $updaters as $updater ) { + $updater->init(); + } + } + } - do_action( 'woocommerce_subscriptions_upgraded', WC_Subscriptions_Core_Plugin::instance()->get_library_version(), self::$active_version ); + /** + * Repair a single item's subtracted base tax meta. + * + * @since 3.1.0 + * @param int $item_id The ID of the item which needs repairing. + */ + public static function repair_subtracted_base_taxes( $item_id ) { + if ( ! isset( self::$background_updaters['3.1']['subtracted_base_tax_repair'] ) ) { + return; + } + + self::$background_updaters['3.1']['subtracted_base_tax_repair']->repair_item( $item_id ); + } + + /** + * Show an admin notice if the store is upgrading from a Subscriptions version that's no longer supported. + * + * @since 7.7.0 + */ + private static function show_unsupported_upgrade_path_notice() { + echo '

' . + esc_html( + __( + 'A database upgrade is required to use Subscriptions. Upgrades from the previously installed version is no longer supported. You will need to install an older version of WooCommerce Subscriptions or WooCommerce Payments to proceed with the upgrade before you can use a newer version.', + 'woocommerce-subscriptions' + ) + ) . + '

'; + } + + /* Deprecated Functions */ + + /** + * Handles the WC 3.5.0 upgrade routine that moves customer IDs from post metadata to the 'post_author' column. + * + * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.4.0 + * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.5.0 + */ + public static function maybe_update_subscription_post_author() { + wcs_deprecated_function( __METHOD__, '2.5.0' ); + + if ( version_compare( WC()->version, '3.5.0', '<' ) ) { + return; + } + + // If WC hasn't run the update routine yet we can hook into theirs to update subscriptions, otherwise we'll need to schedule our own update. + if ( version_compare( get_option( 'woocommerce_db_version' ), '3.5.0', '<' ) ) { + self::$background_updaters['2.4']['subscription_post_author']->hook_into_wc_350_update(); + } elseif ( version_compare( self::$active_version, '2.4.0', '<' ) ) { + self::$background_updaters['2.4']['subscription_post_author']->schedule_repair(); + } + } + + /** + * Used to check if a user ID is greater than the last user upgraded to version 1.4. + * + * Needs to be a separate function so that it can use a static variable (and therefore avoid calling get_option() thousands + * of times when iterating over thousands of users). + * + * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.4 + */ + public static function is_user_upgraded_to_1_4( $user_id ) { + _deprecated_function( __METHOD__, '2.0', 'WCS_Upgrade_1_4::is_user_upgraded( $user_id )' ); + return WCS_Upgrade_1_4::is_user_upgraded( $user_id ); } /** - * Redirect to the Subscriptions major version Welcome/About page for major version updates + * Display an admin notice if the database version is greater than the active version of the plugin by at least one minor release (eg 1.1 and 1.0). + * + * @since 2.3.0 + * @deprecated 1.2.0 + */ + public static function maybe_add_downgrade_notice() { + wcs_deprecated_function( __METHOD__, '1.2.0' ); + + // If there's no downgrade, exit early. self::$active_version is a bit of a misnomer here but in an upgrade context it refers to the database version of the plugin. + if ( ! version_compare( wcs_get_minor_version_string( self::$active_version ), wcs_get_minor_version_string( WC_Subscriptions_Core_Plugin::instance()->get_library_version() ), '>' ) ) { + return; + } + + $admin_notice = new WCS_Admin_Notice( 'error' ); + $admin_notice->set_simple_content( + sprintf( + // translators: 1-2: opening/closing tags, 3: active version of Subscriptions, 4: current version of Subscriptions, 5-6: opening/closing tags linked to ticket form, 7-8: opening/closing tags linked to documentation. + esc_html__( '%1$sWarning!%2$s It appears that you have downgraded %1$sWooCommerce Subscriptions%2$s from %3$s to %4$s. Downgrading the plugin in this way may cause issues. Please update to %3$s or higher, or %5$sopen a new support ticket%6$s for further assistance. %7$sLearn more »%8$s', 'woocommerce-subscriptions' ), + '', + '', + '' . self::$active_version . '', + '' . WC_Subscriptions_Core_Plugin::instance()->get_library_version() . '', + '', + '', + '', + '' + ) + ); + + $admin_notice->display(); + } + + /** + * Deprecated functions. + */ + + /** + * Set limits on the number of items to upgrade at any one time based on the size of the site. + * + * The size of subscription at the time the upgrade is started is used to determine the batch size. + * + * @deprecated subscriptions-core 7.7.0 - Upgrade limits were used when the upgrade process used AJAX. + * + * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 + */ + protected static function set_upgrade_limits() { + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); + + $total_initial_subscription_count = self::get_total_subscription_count( true ); + + if ( $total_initial_subscription_count > 5000 ) { + $base_upgrade_limit = 20; + } elseif ( $total_initial_subscription_count > 1500 ) { + $base_upgrade_limit = 30; + } else { + $base_upgrade_limit = 50; + } + + self::$upgrade_limit_hooks = apply_filters( 'woocommerce_subscriptions_hooks_to_upgrade', $base_upgrade_limit * 5 ); + self::$upgrade_limit_subscriptions = apply_filters( 'woocommerce_subscriptions_to_upgrade', $base_upgrade_limit ); + } + + /** + * Try to block WP-Cron until upgrading finishes. spawn_cron() will only let us steal the lock for 10 minutes into the future, so + * we can actually only block it for 9 minutes confidently. But as long as the upgrade process continues, the lock will remain. + * + * @deprecated subscriptions-core 7.7.0 Cron lock was required for more intensive upgrades prior to v3.0 + * + * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 + */ + protected static function set_cron_lock() { + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); + delete_transient( 'doing_cron' ); + set_transient( 'doing_cron', sprintf( '%.22F', 9 * MINUTE_IN_SECONDS + microtime( true ) ), 0 ); + } + + /** + * Redirect to the Subscriptions major version Welcome/About page for major version updates. + * + * @deprecated subscriptions-core 7.7.0 * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.1 */ public static function maybe_redirect_after_upgrade_complete( $current_version, $previously_active_version ) { + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); + if ( version_compare( $previously_active_version, '2.1.0', '<' ) && version_compare( $current_version, '2.1.0', '>=' ) && version_compare( $current_version, '2.2.0', '<' ) ) { wp_safe_redirect( self::$about_page_url ); exit(); @@ -303,9 +322,12 @@ public static function maybe_redirect_after_upgrade_complete( $current_version, * Add support for quantities for subscriptions. * Update all current subscription wp_cron tasks to the new action-scheduler system. * + * @deprecated subscriptions-core 7.7.0 + * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ private static function ajax_upgrade_handler() { + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); $_GET['wcs_upgrade_step'] = ( ! isset( $_GET['wcs_upgrade_step'] ) ) ? 0 : $_GET['wcs_upgrade_step']; @@ -332,10 +354,13 @@ private static function ajax_upgrade_handler() { * Also set all existing subscriptions to "sold individually" to maintain previous behavior * for existing subscription products before the subscription quantities feature was enabled.. * + * @deprecated subscriptions-core 7.7.0 - This function is only used when upgrading from versions less than v3.0. + * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.5 */ public static function ajax_upgrade() { global $wpdb; + wcs_deprecated_function( __METHOD__, 'subscription-core 7.7.0' ); check_admin_referer( 'wcs_upgrade_process', 'nonce' ); @@ -477,9 +502,11 @@ public static function ajax_upgrade() { /** * Handle upgrades for really old versions. * + * @deprecated subscriptions-core 7.7.0 * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ private static function upgrade_really_old_versions() { + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); if ( '0' != self::$active_version && version_compare( self::$active_version, '1.2', '<' ) ) { WCS_Upgrade_1_2::init(); @@ -509,10 +536,14 @@ private static function upgrade_really_old_versions() { * Version 1.2 introduced child renewal orders to keep a record of each completed subscription * payment. Before 1.2, these orders did not exist, so this function creates them. * + * @deprecated subscriptions-core 7.7.0 + * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 */ private static function generate_renewal_orders() { global $wpdb; + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); + $woocommerce = WC(); $subscriptions_grouped_by_user = WC_Subscriptions_Manager::get_all_users_subscriptions(); @@ -578,27 +609,18 @@ private static function generate_renewal_orders() { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 */ public static function display_database_upgrade_helper() { + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); wp_register_style( 'wcs-upgrade', WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory_url( 'assets/css/wcs-upgrade.css' ) ); wp_register_script( 'wcs-upgrade', WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory_url( 'assets/js/wcs-upgrade.js' ), 'jquery' ); - if ( version_compare( self::$active_version, '2.0.0', '<' ) ) { - // We're running the 2.0 upgrade routine - $subscription_count = self::get_total_subscription_count(); - } elseif ( version_compare( self::$active_version, '2.0.0', '>=' ) && version_compare( self::$active_version, '2.0.2', '<' ) ) { - // We're running the 2.0.2 repair routine - $subscription_counts = wp_count_posts( 'shop_subscription' ); - $subscription_count = array_sum( (array) $subscription_counts ) - $subscription_counts->trash - $subscription_counts->{'auto-draft'}; - } else { - // How did we get here? - $subscription_count = 0; - } + $subscription_count = 0; $script_data = array( - 'really_old_version' => ( version_compare( self::$active_version, '1.4', '<' ) ) ? 'true' : 'false', - 'upgrade_to_1_5' => ( version_compare( self::$active_version, '1.5', '<' ) ) ? 'true' : 'false', - 'upgrade_to_2_0' => ( version_compare( self::$active_version, '2.0.0', '<' ) ) ? 'true' : 'false', - 'repair_2_0' => ( version_compare( self::$active_version, '2.0.0', '>=' ) && version_compare( self::$active_version, '2.0.2', '<' ) ) ? 'true' : 'false', + 'really_old_version' => 'false', + 'upgrade_to_1_5' => false, + 'upgrade_to_2_0' => false, + 'repair_2_0' => false, 'hooks_per_request' => self::$upgrade_limit_hooks, 'ajax_url' => admin_url( 'admin-ajax.php' ), 'upgrade_nonce' => wp_create_nonce( 'wcs_upgrade_process' ), @@ -637,9 +659,13 @@ public static function display_database_upgrade_helper() { * upgrade process, and how many subscriptions per request can typically be updated given the amount of memory * allocated to PHP. * + * @deprecated subscriptions-core 7.7.0 - We know longer use a notice or pages to display upgrade progress. + * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.4 */ public static function upgrade_in_progress_notice() { + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); + include_once( dirname( __FILE__ ) . '/templates/wcs-upgrade-in-progress.php' ); WCS_Upgrade_Logger::add( 'Loaded database upgrade in progress notice...' ); } @@ -650,6 +676,8 @@ public static function upgrade_in_progress_notice() { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.4 */ public static function updated_welcome_page() { + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); + $about_page = add_dashboard_page( __( 'Welcome to WooCommerce Subscriptions 2.1', 'woocommerce-subscriptions' ), __( 'About WooCommerce Subscriptions', 'woocommerce-subscriptions' ), 'manage_options', 'wcs-about', __CLASS__ . '::about_screen' ); add_action( 'admin_print_styles-' . $about_page, __CLASS__ . '::admin_css' ); add_action( 'admin_head', __CLASS__ . '::admin_head' ); @@ -658,20 +686,21 @@ public static function updated_welcome_page() { /** * admin_css function. * - * @access public * @return void */ public static function admin_css() { + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); + wp_enqueue_style( 'woocommerce-subscriptions-about', WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory_url( 'assets/css/about.css' ), array(), self::$active_version ); } /** * Add styles just for this page, and remove dashboard page links. * - * @access public * @return void */ public static function admin_head() { + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); remove_submenu_page( 'index.php', 'wcs-about' ); } @@ -679,6 +708,7 @@ public static function admin_head() { * Output the about screen. */ public static function about_screen() { + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); $active_version = self::$active_version; $settings_page = admin_url( 'admin.php?page=wc-settings&tab=subscriptions' ); @@ -694,6 +724,7 @@ public static function about_screen() { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ private static function get_total_subscription_count( $initial = false ) { + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); if ( $initial ) { @@ -721,6 +752,7 @@ private static function get_total_subscription_count( $initial = false ) { */ private static function get_total_subscription_count_query() { global $wpdb; + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); $query = self::get_subscription_query(); @@ -729,6 +761,7 @@ private static function get_total_subscription_count_query() { return $wpdb->num_rows; } + /** * Single source of truth for the query * @param integer $limit the number of subscriptions to get @@ -736,6 +769,7 @@ private static function get_total_subscription_count_query() { */ public static function get_subscription_query( $batch_size = null ) { global $wpdb; + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); if ( null === $batch_size ) { $select = 'SELECT DISTINCT items.order_item_id'; @@ -774,6 +808,7 @@ public static function get_subscription_query( $batch_size = null ) { */ protected static function migrated_subscription_count() { global $wpdb; + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); $migrated_subscription_count = $wpdb->get_var( "SELECT COUNT(DISTINCT `post_id`) FROM $wpdb->postmeta @@ -783,6 +818,7 @@ protected static function migrated_subscription_count() { return $migrated_subscription_count; } + /** * While the upgrade is in progress, we need to block IPN messages to avoid renewals failing to process correctly. * @@ -791,9 +827,13 @@ protected static function migrated_subscription_count() { * * The method returns a 409 Conflict HTTP response code to indicate that the IPN is conflicting with the upgrader. * + * @deprecated subscriptions-core 7.7.0 - We no lock down the store during subscription upgrades so we don't need to block IPNs. + * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ public static function maybe_block_paypal_ipn() { + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); + if ( false !== get_option( 'wc_subscriptions_is_upgrading', false ) ) { WCS_Upgrade_Logger::add( '*** PayPal IPN Request blocked: ' . print_r( wp_unslash( $_POST ), true ) ); // No CSRF needed as it's from outside wp_die( 'PayPal IPN Request Failure', 'PayPal IPN', array( 'response' => 409 ) ); @@ -802,54 +842,42 @@ public static function maybe_block_paypal_ipn() { /** * Run the end of prepaid term repair script. - * + * @deprecated subscriptions-core 7.7.0 * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.7 */ public static function repair_end_of_prepaid_term_actions() { + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); WCS_Upgrade_2_2_7::repair_pending_cancelled_subscriptions(); } /** * Repair subscriptions with missing contains_synced_subscription post meta. - * + * @deprecated subscriptions-core 7.7.0 * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.9 */ public static function repair_subscription_contains_sync_meta() { + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); WCS_Upgrade_2_2_9::repair_subscriptions_containing_synced_variations(); } /** * When updating WC to a version after 3.0 from a version prior to 3.0, schedule the repair script to add address indexes. * + * @deprecated subscriptions-core 7.7.0 - Upgrading from before WC 3.0 is not supported. + * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0 */ public static function maybe_add_subscription_address_indexes() { + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); + $woocommerce_active_version = WC()->version; $woocommerce_database_version = get_option( 'woocommerce_version' ); if ( $woocommerce_active_version !== $woocommerce_database_version && version_compare( $woocommerce_active_version, '3.0', '>=' ) && version_compare( $woocommerce_database_version, '3.0', '<' ) ) { - self::$background_updaters['2.3']['address_indexes_repair']->schedule_repair(); - } - } - - /** - * Load and initialise the background updaters. - * - * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.4.0 - */ - public static function initialise_background_updaters() { - $logger = new WC_logger(); - self::$background_updaters['2.3']['suspended_paypal_repair'] = new WCS_Repair_Suspended_PayPal_Subscriptions( $logger ); - self::$background_updaters['2.3']['address_indexes_repair'] = new WCS_Repair_Subscription_Address_Indexes( $logger ); - self::$background_updaters['2.4']['start_date_metadata'] = new WCS_Repair_Start_Date_Metadata( $logger ); - self::$background_updaters['2.6']['has_trial_item_meta'] = new WCS_Repair_Line_Item_Has_Trial_Meta( $logger ); - self::$background_updaters['3.1']['subtracted_base_tax_repair'] = new WCS_Repair_Subtracted_Base_Tax_Line_Item_Meta( $logger ); - - // Init the updaters - foreach ( self::$background_updaters as $version => $updaters ) { - foreach ( $updaters as $updater ) { - $updater->init(); - } + $logger = new WC_logger(); + $background_updater = new WCS_Repair_Subscription_Address_Indexes( $logger ); + $background_updater->init(); + $background_updater->schedule_repair(); } } @@ -861,8 +889,11 @@ public static function initialise_background_updaters() { * * @see https://github.com/Prospress/woocommerce-subscriptions/issues/2822 for more details. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.3 + * @deprecated subscriptions-core 7.7.0 */ public static function maybe_display_external_object_cache_warning() { + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); + $option_name = 'wcs_display_2_3_3_warning'; $nonce = '_wcsnonce'; $action = 'wcs_external_cache_warning'; @@ -895,83 +926,4 @@ public static function maybe_display_external_object_cache_warning() { $admin_notice->display(); } - - /** - * Repair a single item's subtracted base tax meta. - * - * @since 3.1.0 - * @param int $item_id The ID of the item which needs repairing. - */ - public static function repair_subtracted_base_taxes( $item_id ) { - self::$background_updaters['3.1']['subtracted_base_tax_repair']->repair_item( $item_id ); - } - - /* Deprecated Functions */ - - /** - * Handles the WC 3.5.0 upgrade routine that moves customer IDs from post metadata to the 'post_author' column. - * - * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.4.0 - * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.5.0 - */ - public static function maybe_update_subscription_post_author() { - wcs_deprecated_function( __METHOD__, '2.5.0' ); - - if ( version_compare( WC()->version, '3.5.0', '<' ) ) { - return; - } - - // If WC hasn't run the update routine yet we can hook into theirs to update subscriptions, otherwise we'll need to schedule our own update. - if ( version_compare( get_option( 'woocommerce_db_version' ), '3.5.0', '<' ) ) { - self::$background_updaters['2.4']['subscription_post_author']->hook_into_wc_350_update(); - } else if ( version_compare( self::$active_version, '2.4.0', '<' ) ) { - self::$background_updaters['2.4']['subscription_post_author']->schedule_repair(); - } - } - - /** - * Used to check if a user ID is greater than the last user upgraded to version 1.4. - * - * Needs to be a separate function so that it can use a static variable (and therefore avoid calling get_option() thousands - * of times when iterating over thousands of users). - * - * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.4 - */ - public static function is_user_upgraded_to_1_4( $user_id ) { - _deprecated_function( __METHOD__, '2.0', 'WCS_Upgrade_1_4::is_user_upgraded( $user_id )' ); - return WCS_Upgrade_1_4::is_user_upgraded( $user_id ); - } - - /** - * Display an admin notice if the database version is greater than the active version of the plugin by at least one minor release (eg 1.1 and 1.0). - * - * @since 2.3.0 - * @deprecated 1.2.0 - */ - public static function maybe_add_downgrade_notice() { - wcs_deprecated_function( __METHOD__, '1.2.0' ); - - // If there's no downgrade, exit early. self::$active_version is a bit of a misnomer here but in an upgrade context it refers to the database version of the plugin. - if ( ! version_compare( wcs_get_minor_version_string( self::$active_version ), wcs_get_minor_version_string( WC_Subscriptions_Core_Plugin::instance()->get_library_version() ), '>' ) ) { - return; - } - - $admin_notice = new WCS_Admin_Notice( 'error' ); - $admin_notice->set_simple_content( - sprintf( - // translators: 1-2: opening/closing tags, 3: active version of Subscriptions, 4: current version of Subscriptions, 5-6: opening/closing tags linked to ticket form, 7-8: opening/closing tags linked to documentation. - esc_html__( '%1$sWarning!%2$s It appears that you have downgraded %1$sWooCommerce Subscriptions%2$s from %3$s to %4$s. Downgrading the plugin in this way may cause issues. Please update to %3$s or higher, or %5$sopen a new support ticket%6$s for further assistance. %7$sLearn more »%8$s', 'woocommerce-subscriptions' ), - '', - '', - '' . self::$active_version . '', - '' . WC_Subscriptions_Core_Plugin::instance()->get_library_version() . '', - '', - '', - '', - '' - ) - ); - - $admin_notice->display(); - } } diff --git a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-2-0-2.php b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-2-0-2.php index c939bdb..623ae06 100644 --- a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-2-0-2.php +++ b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-2-0-2.php @@ -12,6 +12,9 @@ exit; // Exit if accessed directly } +/** + * @deprecated + */ class WCS_Repair_2_0_2 { /** diff --git a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-2-0.php b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-2-0.php index 194dc63..f7538f2 100644 --- a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-2-0.php +++ b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-2-0.php @@ -12,6 +12,9 @@ exit; // Exit if accessed directly } +/** + * @deprecated + */ class WCS_Repair_2_0 { /** @@ -392,7 +395,8 @@ public static function repair_length( $subscription, $item_id, $item_meta ) { public static function repair_start_date( $subscription, $item_id, $item_meta ) { global $wpdb; - $start_date = get_post_meta( $subscription['order_id'], '_paid_date', true ); + $order = wc_get_order( $subscription['order_id'] ); + $start_date = $order->get_meta( '_paid_date', true ); WCS_Upgrade_Logger::add( sprintf( 'Repairing start_date for order %d: Trying to use the _paid date for start date.', $subscription['order_id'] ) ); diff --git a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-line-item-has-trial-meta.php b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-line-item-has-trial-meta.php index 1991b59..2f2e99f 100644 --- a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-line-item-has-trial-meta.php +++ b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-line-item-has-trial-meta.php @@ -22,6 +22,9 @@ exit; } +/** + * @deprecated + */ class WCS_Repair_Line_Item_Has_Trial_Meta extends WCS_Background_Repairer { /** diff --git a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-start-date-metadata.php b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-start-date-metadata.php index 7436f2b..635a49b 100644 --- a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-start-date-metadata.php +++ b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-start-date-metadata.php @@ -13,6 +13,9 @@ exit; } +/** + * @deprecated + */ class WCS_Repair_Start_Date_Metadata extends WCS_Background_Upgrader { /** diff --git a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-subscription-address-indexes.php b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-subscription-address-indexes.php index 0b48c17..6ee782b 100644 --- a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-subscription-address-indexes.php +++ b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-subscription-address-indexes.php @@ -16,6 +16,9 @@ exit; } +/** + * @deprecated + */ class WCS_Repair_Subscription_Address_Indexes extends WCS_Background_Upgrader { /** diff --git a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-suspended-paypal-subscriptions.php b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-suspended-paypal-subscriptions.php index 9b641d2..0d213e6 100644 --- a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-suspended-paypal-subscriptions.php +++ b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-repair-suspended-paypal-subscriptions.php @@ -19,6 +19,9 @@ exit; } +/** + * @deprecated + */ class WCS_Repair_Suspended_PayPal_Subscriptions extends WCS_Background_Upgrader { /** diff --git a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-1-2.php b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-1-2.php index 33350c0..74e882c 100644 --- a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-1-2.php +++ b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-1-2.php @@ -20,10 +20,14 @@ exit; // Exit if accessed directly } +/** + * @deprecated subscriptions-core 7.7.0 + */ class WCS_Upgrade_1_2 { public static function init() { global $wpdb; + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); // Get IDs only and use a direct DB query for efficiency $orders_to_upgrade = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE post_type = 'shop_order' AND post_parent = 0" ); @@ -70,10 +74,10 @@ public static function init() { $order_discount = $order->get_total_discount(); update_post_meta( $order_id, '_order_recurring_discount_total', $order_discount ); - $order_shipping_tax = get_post_meta( $order_id, '_order_shipping_tax', true ); + $order_shipping_tax = $order->get_meta( '_order_shipping_tax', true ); update_post_meta( $order_id, '_order_recurring_shipping_tax_total', $order_shipping_tax ); - $order_tax = get_post_meta( $order_id, '_order_tax', true ); // $order->get_total_tax() includes shipping tax + $order_tax = $order->get_meta( '_order_tax', true ); // $order->get_total_tax() includes shipping tax update_post_meta( $order_id, '_order_recurring_tax_total', $order_tax ); $order_total = $order->get_total(); diff --git a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-1-3.php b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-1-3.php index 9cea922..ae41eb5 100644 --- a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-1-3.php +++ b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-1-3.php @@ -14,10 +14,13 @@ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } - +/** + * @deprecated subscription-core 7.7.0 + */ class WCS_Upgrade_1_3 { public static function init() { + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); global $wpdb; // Change transient timeout entries to be a vanilla option diff --git a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-1-4.php b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-1-4.php index 9a67fa4..dbdfcce 100644 --- a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-1-4.php +++ b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-1-4.php @@ -14,12 +14,15 @@ exit; // Exit if accessed directly } +/** + * @deprecated subscriptions-core 7.7.0 + */ class WCS_Upgrade_1_4 { private static $last_upgraded_user_id = false; public static function init() { - + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); global $wpdb; $subscriptions_meta_key = $wpdb->get_blog_prefix() . 'woocommerce_subscriptions'; @@ -166,11 +169,11 @@ public static function init() { * * Needs to be a separate function so that it can use a static variable (and therefore avoid calling get_option() thousands * of times when iterating over thousands of users). - * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.4 + * @deprecated subscriptions-core 7.7.0 */ public static function is_user_upgraded( $user_id ) { - + wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); if ( false === self::$last_upgraded_user_id ) { self::$last_upgraded_user_id = get_option( 'wcs_1_4_last_upgraded_user_id', 0 ); } diff --git a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-1-5.php b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-1-5.php index 7964057..06a42f0 100644 --- a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-1-5.php +++ b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-1-5.php @@ -20,6 +20,7 @@ class WCS_Upgrade_1_5 { * Subscriptions 1.5 made it possible for a product to be sold individually or in multiple quantities, whereas * previously it was possible only to buy a subscription product in a single quantity. * + * @deprecated * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ public static function upgrade_products() { @@ -54,6 +55,7 @@ public static function upgrade_products() { /** * Update subscription WP-Cron tasks to Action Scheduler. * + * @deprecated * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ public static function upgrade_hooks( $number_hooks_to_upgrade ) { diff --git a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-2-0.php b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-2-0.php index e95bc79..0d32b69 100644 --- a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-2-0.php +++ b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-2-0.php @@ -12,6 +12,9 @@ exit; // Exit if accessed directly } +/** + * @deprecated + */ class WCS_Upgrade_2_0 { /* Cache of order item meta keys that were used to store subscription data in v1.5 */ @@ -214,12 +217,13 @@ private static function get_subscriptions( $batch_size ) { } if ( ! array_key_exists( $raw_subscription->order_item_id, $subscriptions ) ) { + $order = wc_get_order( $raw_subscription->order_id ); $subscriptions[ $raw_subscription->order_item_id ] = array( 'order_id' => $raw_subscription->order_id, 'name' => $raw_subscription->order_item_name, ); - $subscriptions[ $raw_subscription->order_item_id ]['user_id'] = (int) get_post_meta( $raw_subscription->order_id, '_customer_user', true ); + $subscriptions[ $raw_subscription->order_item_id ]['user_id'] = (int) $order->get_meta( '_customer_user', true ); } $meta_key = str_replace( '_subscription', '', $raw_subscription->meta_key ); @@ -610,8 +614,7 @@ private static function migrate_post_meta( $subscription_id, $order ) { $order_meta = get_post_meta( wcs_get_objects_property( $order, 'id' ) ); foreach ( $post_meta_with_new_key as $subscription_meta_key => $order_meta_key ) { - - $order_meta_value = get_post_meta( wcs_get_objects_property( $order, 'id' ), $order_meta_key, true ); + $order_meta_value = $order->get_meta( $order_meta_key, true ); if ( isset( $order_meta[ $order_meta_key ] ) && '' !== $order_meta[ $order_meta_key ] ) { update_post_meta( $subscription_id, $subscription_meta_key, $order_meta_value ); @@ -838,7 +841,7 @@ private static function migrate_resubscribe_orders( $new_subscription, $resubscr $new_subscription_id = wcs_get_objects_property( $new_subscription, 'id' ); // Set the post meta on the new subscription and old order - foreach ( get_post_meta( $resubscribe_order_id, '_original_order', false ) as $original_order_id ) { + foreach ( $resubscribe_order->get_meta( '_original_order', false ) as $original_order_id ) { // Because self::get_subscriptions() orders by order ID, it's safe to use wcs_get_subscriptions_for_order() here because the subscription in the new format will have been created for the original order (because its ID will be < the resubscribe order's ID) foreach ( wcs_get_subscriptions_for_order( $original_order_id ) as $old_subscription ) { @@ -876,7 +879,7 @@ private static function migrate_switch_meta( $new_subscription, $switch_order, $ global $wpdb; // If the order doesn't contain a switch, we don't need to do anything - if ( '' == get_post_meta( wcs_get_objects_property( $switch_order, 'id' ), '_switched_subscription_key', true ) ) { + if ( '' === $switch_order->get_meta( '_switched_subscription_key', true ) ) { return; } diff --git a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-2-1.php b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-2-1.php index ba35857..a58a12a 100644 --- a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-2-1.php +++ b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-2-1.php @@ -12,6 +12,9 @@ exit; // Exit if accessed directly } +/** + * @deprecated + */ class WCS_Upgrade_2_1 { /** diff --git a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-2-2-7.php b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-2-2-7.php index 39dcd3b..dc54fdd 100644 --- a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-2-2-7.php +++ b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-2-2-7.php @@ -12,6 +12,9 @@ exit; // Exit if accessed directly } +/** + * @deprecated + */ class WCS_Upgrade_2_2_7 { private static $cron_hook = 'wcs_repair_end_of_prepaid_term_actions'; diff --git a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-2-2-9.php b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-2-2-9.php index c989bf1..c67b5e1 100644 --- a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-2-2-9.php +++ b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-2-2-9.php @@ -13,6 +13,9 @@ exit; // Exit if accessed directly } +/** + * @deprecated + */ class WCS_Upgrade_2_2_9 { private static $cron_hook = 'wcs_repair_subscriptions_containing_synced_variations'; diff --git a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-subscription-post-author.php b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-subscription-post-author.php index 101da39..a0405de 100644 --- a/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-subscription-post-author.php +++ b/vendor/woocommerce/subscriptions-core/includes/upgrades/class-wcs-upgrade-subscription-post-author.php @@ -13,6 +13,9 @@ exit; // Exit if accessed directly } +/** + * @deprecated + */ class WCS_Upgrade_Subscription_Post_Author extends WCS_Background_Upgrader { /** diff --git a/vendor/woocommerce/subscriptions-core/includes/wcs-deprecated-functions.php b/vendor/woocommerce/subscriptions-core/includes/wcs-deprecated-functions.php index 74a3532..a7310cc 100644 --- a/vendor/woocommerce/subscriptions-core/includes/wcs-deprecated-functions.php +++ b/vendor/woocommerce/subscriptions-core/includes/wcs-deprecated-functions.php @@ -129,12 +129,10 @@ function wcs_get_subscription_id_from_key( $subscription_key ) { } $order_and_product_id = explode( '_', $subscription_key ); - - $subscription_ids = array(); + $subscription_ids = array(); // If we have an order ID and product ID, query based on that if ( ! empty( $order_and_product_id[0] ) && ! empty( $order_and_product_id[1] ) ) { - $subscription_ids = $wpdb->get_col( $wpdb->prepare( " SELECT DISTINCT order_items.order_id FROM {$wpdb->prefix}woocommerce_order_items as order_items LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS itemmeta ON order_items.order_item_id = itemmeta.order_item_id @@ -146,15 +144,16 @@ function wcs_get_subscription_id_from_key( $subscription_key ) { $order_and_product_id[0], $order_and_product_id[1] ) ); } elseif ( ! empty( $order_and_product_id[0] ) ) { - - $subscription_ids = get_posts( array( - 'posts_per_page' => 1, - 'post_parent' => $order_and_product_id[0], - 'post_status' => 'any', - 'post_type' => 'shop_subscription', - 'fields' => 'ids', - ) ); - + // Not replacing this `get_posts` call with `wc_get_orders`. This is from when subscription object used to be an array of data stored in order meta ({$order_id}_{$product_id}, referred to as a "subscriptions key") + $subscription_ids = get_posts( + array( + 'posts_per_page' => 1, + 'post_parent' => $order_and_product_id[0], + 'post_status' => 'any', + 'post_type' => 'shop_subscription', + 'fields' => 'ids', + ) + ); } return ( ! empty( $subscription_ids ) ) ? $subscription_ids[0] : null; diff --git a/vendor/woocommerce/subscriptions-core/includes/wcs-limit-functions.php b/vendor/woocommerce/subscriptions-core/includes/wcs-limit-functions.php index d06f657..e57b942 100644 --- a/vendor/woocommerce/subscriptions-core/includes/wcs-limit-functions.php +++ b/vendor/woocommerce/subscriptions-core/includes/wcs-limit-functions.php @@ -60,13 +60,13 @@ function wcs_is_product_limited_for_user( $product, $user_id = 0 ) { // If the product is limited for any status, there exists a chance that the customer has cancelled subscriptions which cannot be resubscribed to as they have no completed payments. if ( 'any' === $product_limitation && $is_limited_for_user ) { $is_limited_for_user = false; - $user_subscriptions = wcs_get_subscriptions( array( - 'subscriptions_per_page' => -1, - 'customer_id' => $user_id, - 'product_id' => $product->get_id(), - ) ); - foreach ( $user_subscriptions as $subscription ) { + foreach ( wcs_get_users_subscriptions( $user_id ) as $subscription ) { + // Skip if the subscription is not for the product we are checking. + if ( ! $subscription->has_product( $product->get_id() ) ) { + continue; + } + if ( ! $subscription->has_status( 'cancelled' ) || 0 !== $subscription->get_payment_count() ) { $is_limited_for_user = true; break; diff --git a/vendor/woocommerce/subscriptions-core/includes/wcs-renewal-functions.php b/vendor/woocommerce/subscriptions-core/includes/wcs-renewal-functions.php index 1babc62..b1f1bd5 100644 --- a/vendor/woocommerce/subscriptions-core/includes/wcs-renewal-functions.php +++ b/vendor/woocommerce/subscriptions-core/includes/wcs-renewal-functions.php @@ -108,11 +108,14 @@ function wcs_cart_contains_failed_renewal_order_payment() { $cart_item = wcs_cart_contains_renewal(); if ( false !== $cart_item && isset( $cart_item['subscription_renewal']['renewal_order_id'] ) ) { - $renewal_order = wc_get_order( $cart_item['subscription_renewal']['renewal_order_id'] ); - $is_failed_renewal_order = apply_filters( 'woocommerce_subscriptions_is_failed_renewal_order', $renewal_order->has_status( 'failed' ), $cart_item['subscription_renewal']['renewal_order_id'], $renewal_order->get_status() ); + $renewal_order = wc_get_order( $cart_item['subscription_renewal']['renewal_order_id'] ); - if ( $is_failed_renewal_order ) { - $contains_renewal = $cart_item; + if ( $renewal_order ) { + $is_failed_renewal_order = apply_filters( 'woocommerce_subscriptions_is_failed_renewal_order', $renewal_order->has_status( 'failed' ), $cart_item['subscription_renewal']['renewal_order_id'], $renewal_order->get_status() ); + + if ( $is_failed_renewal_order ) { + $contains_renewal = $cart_item; + } } } diff --git a/vendor/woocommerce/subscriptions-core/templates/admin/deprecated/html-variation-price.php b/vendor/woocommerce/subscriptions-core/templates/admin/deprecated/html-variation-price.php index 4a7ae4d..58f7f42 100644 --- a/vendor/woocommerce/subscriptions-core/templates/admin/deprecated/html-variation-price.php +++ b/vendor/woocommerce/subscriptions-core/templates/admin/deprecated/html-variation-price.php @@ -29,7 +29,7 @@ // translators: placeholder is a currency symbol / code 'label' => sprintf( __( 'Subscription Price (%s)', 'woocommerce-subscriptions' ), get_woocommerce_currency_symbol() ), 'placeholder' => _x( 'e.g. 9.90', 'example price', 'woocommerce-subscriptions' ), - 'value' => get_post_meta( $variation->get_id(), '_subscription_price', true ), + 'value' => $variation->get_meta( '_subscription_price', true ), 'type' => 'number', 'custom_attributes' => array( 'step' => 'any', @@ -45,7 +45,7 @@ 'wrapper_class' => '_subscription_period_interval_field', 'label' => __( 'Subscription Periods', 'woocommerce-subscriptions' ), 'options' => wcs_get_subscription_period_interval_strings(), - 'value' => get_post_meta( $variation->get_id(), '_subscription_period_interval', true ), + 'value' => $variation->get_meta( '_subscription_period_interval', true ), ) ); @@ -68,7 +68,7 @@ 'wrapper_class' => '_subscription_length_field', 'label' => __( 'Subscription Length', 'woocommerce-subscriptions' ), 'options' => wcs_get_subscription_ranges( $subscription_period ), - 'value' => get_post_meta( $variation->get_id(), '_subscription_length', true ), + 'value' => $variation->get_meta( '_subscription_length', true ), ) ); ?> @@ -84,7 +84,7 @@ 'wrapper_class' => '_subscription_sign_up_fee_field', 'label' => sprintf( __( 'Sign-up Fee (%s)', 'woocommerce-subscriptions' ), get_woocommerce_currency_symbol() ), 'placeholder' => _x( 'e.g. 9.90', 'example price', 'woocommerce-subscriptions' ), - 'value' => get_post_meta( $variation->get_id(), '_subscription_sign_up_fee', true ), + 'value' => $variation->get_meta( '_subscription_sign_up_fee', true ), 'type' => 'number', 'custom_attributes' => array( 'step' => 'any', @@ -103,7 +103,7 @@ 'wrapper_class' => '_subscription_trial_length_field', 'label' => __( 'Free Trial', 'woocommerce-subscriptions' ), 'placeholder' => _x( 'e.g. 3', 'example number of days / weeks / months', 'woocommerce-subscriptions' ), - 'value' => get_post_meta( $variation->get_id(), '_subscription_trial_length', true ), + 'value' => $variation->get_meta( '_subscription_trial_length', true ), ) ); diff --git a/vendor/woocommerce/subscriptions-core/templates/admin/html-admin-notice.php b/vendor/woocommerce/subscriptions-core/templates/admin/html-admin-notice.php index 4a07820..a1f0fbb 100644 --- a/vendor/woocommerce/subscriptions-core/templates/admin/html-admin-notice.php +++ b/vendor/woocommerce/subscriptions-core/templates/admin/html-admin-notice.php @@ -24,6 +24,6 @@ is_dismissible() ) : ?> - + diff --git a/vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-auto-renewal.php b/vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-auto-renewal.php new file mode 100644 index 0000000..82db308 --- /dev/null +++ b/vendor/woocommerce/subscriptions-core/templates/emails/customer-notification-auto-renewal.php @@ -0,0 +1,89 @@ + + +

+ get_billing_first_name() + ) + ); + ?> +

+ + +

+ automatically renew in %1$s — that’s %2$s.', + 'woocommerce-subscriptions' + ), + $subscription_time_til_event, + $subscription_event_date + ), + [ 'strong' => [] ] + ); + ?> +

+ +

+ +

+ + + +

+ + get_view_order_url() ) . '">' . esc_html__( 'account dashboard', 'woocommerce-subscriptions' ) . '', + ) + ); + ?> + +

+ + + +

+ get_billing_first_name() + ) + ); + ?> +

+ + +

+ %2$s.', + 'woocommerce-subscriptions' + ), + $subscription_time_til_event, + $subscription_event_date + ), + [ 'strong' => [] ] + ); + ?> +

+ +

+ get_view_order_url() ) . '">' . esc_html__( 'account dashboard', 'woocommerce-subscriptions' ) . '' + ), + [ 'a' => [ 'href' => true ] ] + ); + ?> +

+ +

+ +

+ + + +

+ get_billing_first_name() + ) + ); + ?> +

+ + +

+ %2$s.', + 'woocommerce-subscriptions' + ), + $subscription_time_til_event, + $subscription_event_date + ), + [ 'strong' => [] ] + ); + ?> +

+ +

+ +

+ + + + +

+ get_billing_first_name() + ) + ); + ?> +

+ + +

+ %2$s.', + 'woocommerce-subscriptions' + ), + $subscription_time_til_event, + $subscription_event_date + ), + [ 'strong' => [] ] + ); + ?> +

+ +

+ + + + renew it manually in a few short steps via the Subscriptions tab in your account dashboard.', 'woocommerce-subscriptions' ), + [ + 'strong' => [], + 'em' => [], + ] + ); + } + + ?> +

+ + + + + + +
+ ' . esc_html( $link_text ) . '', + [ 'a' => [ 'href' => true ] ] + ); + ?> + +
+ +
+

+ +

+ + + + +

+ get_billing_first_name() + ) + ); + ?> +

+ + +

+ %2$s.', + 'woocommerce-subscriptions' + ), + $subscription_time_til_event, + $subscription_event_date + ), + [ 'strong' => [] ] + ); + ?> +

+ + + +get_billing_first_name() + ) +); + +echo "\n\n"; + +echo esc_html( + sprintf( + // translators: %1$s: human readable time difference (eg 3 days, 1 day), %2$s: date in local format. + __( + 'Your subscription will automatically renew in %1$s — that’s %2$s.', + 'woocommerce-subscriptions' + ), + $subscription_time_til_event, + $subscription_event_date + ) +); + +echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; + +esc_html_e( 'Here are the details:', 'woocommerce-subscriptions' ); +echo "\n"; +// Show subscription details. +\WC_Subscriptions_Email::subscription_details( $subscription, $order, $sent_to_admin, $plain_text, true ); + +esc_html_e( 'You can manage this subscription from your account dashboard: ', 'woocommerce-subscriptions' ); +echo esc_url( wc_get_page_permalink( 'myaccount' ) ); + +echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; + +/** + * Show user-defined additional content - this is set in each email's settings. + */ +if ( $additional_content ) { + echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); + echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; +} + +echo esc_html( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-auto-trial-ending.php b/vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-auto-trial-ending.php new file mode 100644 index 0000000..e1ef241 --- /dev/null +++ b/vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-auto-trial-ending.php @@ -0,0 +1,61 @@ +get_billing_first_name() + ) +); + +echo "\n\n"; + +echo esc_html( + sprintf( + // translators: %1$s: human readable time difference (eg 3 days, 1 day), %2$s: date in local format. + __( + 'Your paid subscription begins when your free trial expires in %1$s — that’s %2$s.', + 'woocommerce-subscriptions' + ), + $subscription_time_til_event, + $subscription_event_date + ) +); + +echo "\n\n"; + +// translators: %1$s: link to account dashboard. +esc_html_e( 'Payment will be deducted using the payment method on file. You can manage this subscription from your account dashboard: ', 'woocommerce-subscriptions' ); +echo esc_url( wc_get_page_permalink( 'myaccount' ) ); + +echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; + +esc_html_e( 'Here are the details:', 'woocommerce-subscriptions' ); + +// Show subscription details. +\WC_Subscriptions_Email::subscription_details( $subscription, $order, $sent_to_admin, $plain_text, true ); + +echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; + +/** + * Show user-defined additional content - this is set in each email's settings. + */ +if ( $additional_content ) { + echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); + echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; +} + +echo esc_html( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-expiring-subscription.php b/vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-expiring-subscription.php new file mode 100644 index 0000000..0ade00c --- /dev/null +++ b/vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-expiring-subscription.php @@ -0,0 +1,53 @@ +get_billing_first_name() + ) +); + +echo "\n\n"; + +echo esc_html( + sprintf( + // translators: %1$s: human readable time difference (eg 3 days, 1 day), %2$s: date in local format. + __( + 'Your subscription expires in %1$s — that’s %2$s.', + 'woocommerce-subscriptions' + ), + $subscription_time_til_event, + $subscription_event_date + ) +); + +echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; + +esc_html_e( 'Here are the details:', 'woocommerce-subscriptions' ); + +// Show subscription details. +\WC_Subscriptions_Email::subscription_details( $subscription, $order, $sent_to_admin, $plain_text, true ); + +/** + * Show user-defined additional content - this is set in each email's settings. + */ +if ( $additional_content ) { + echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); + echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; +} + +echo esc_html( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-manual-renewal.php b/vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-manual-renewal.php new file mode 100644 index 0000000..2ed4908 --- /dev/null +++ b/vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-manual-renewal.php @@ -0,0 +1,76 @@ +get_billing_first_name() + ) +); + +echo "\n\n"; + +echo esc_html( + sprintf( + // translators: %1$s: human readable time difference (eg 3 days, 1 day), %2$s: date in local format. + __( + 'Your subscription is up for renewal in %1$s — that’s %2$s.', + 'woocommerce-subscriptions' + ), + $subscription_time_til_event, + $subscription_event_date + ) +); + +echo "\n\n"; + +esc_html_e( 'This subscription will not renew automatically.', 'woocommerce-subscriptions' ); +echo "\n"; +if ( $can_renew_early ) { + esc_html_e( + 'You can renew it manually in a few short steps via the Subscriptions tab in your account dashboard.', + 'woocommerce-subscriptions' + ); +} + +echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; + +if ( $can_renew_early ) { + esc_html_e( 'Renew my subscription: ', 'woocommerce-subscriptions' ); + echo esc_url( $url_for_renewal ); +} else { + esc_html_e( 'Manage my subscription: ', 'woocommerce-subscriptions' ); + echo esc_url( $url_for_renewal ); +} + +echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; + +esc_html_e( 'Here are the details:', 'woocommerce-subscriptions' ); + +// Show subscription details. +\WC_Subscriptions_Email::subscription_details( $subscription, $order, $sent_to_admin, $plain_text ); + +echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; + +/** + * Show user-defined additional content - this is set in each email's settings. + */ +if ( $additional_content ) { + echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); + echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; +} + +echo esc_html( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-manual-trial-ending.php b/vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-manual-trial-ending.php new file mode 100644 index 0000000..95c321a --- /dev/null +++ b/vendor/woocommerce/subscriptions-core/templates/emails/plain/customer-notification-manual-trial-ending.php @@ -0,0 +1,53 @@ +get_billing_first_name() + ) +); + +echo "\n\n"; + +echo esc_html( + sprintf( + // translators: %1$s: human readable time difference (eg 3 days, 1 day), %2$s: date in local format. + __( + 'Your free trial expires in %1$s — that’s %2$s.', + 'woocommerce-subscriptions' + ), + $subscription_time_til_event, + $subscription_event_date + ) +); + +echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; + +// Show subscription details. +\WC_Subscriptions_Email::subscription_details( $subscription, $order, $sent_to_admin, $plain_text ); + +echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; + +/** + * Show user-defined additional content - this is set in each email's settings. + */ +if ( $additional_content ) { + echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); + echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; +} + +echo esc_html( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/vendor/woocommerce/subscriptions-core/templates/emails/plain/subscription-info.php b/vendor/woocommerce/subscriptions-core/templates/emails/plain/subscription-info.php index a309935..001e117 100644 --- a/vendor/woocommerce/subscriptions-core/templates/emails/plain/subscription-info.php +++ b/vendor/woocommerce/subscriptions-core/templates/emails/plain/subscription-info.php @@ -40,7 +40,7 @@ echo "\n\n"; } -if ( $has_automatic_renewal && ! $is_admin_email && $subscription->get_time( 'next_payment' ) > 0 ) { +if ( $has_automatic_renewal && ! $is_admin_email && $subscription->get_time( 'next_payment' ) > 0 && ! $skip_my_account_link ) { if ( count( $subscriptions ) === 1 ) { $subscription = reset( $subscriptions ); $my_account_url = $subscription->get_view_order_url(); @@ -49,10 +49,15 @@ } // Translators: Placeholder is the My Account URL. - echo wp_kses_post( sprintf( _n( - 'This subscription is set to renew automatically using your payment method on file. You can manage or cancel this subscription from your my account page. %s', - 'These subscriptions are set to renew automatically using your payment method on file. You can manage or cancel your subscriptions from your my account page. %s', - count( $subscriptions ), - 'woocommerce-subscriptions' - ), $my_account_url ) ); + echo wp_kses_post( + sprintf( + _n( + 'This subscription is set to renew automatically using your payment method on file. You can manage or cancel this subscription from your my account page. %s', + 'These subscriptions are set to renew automatically using your payment method on file. You can manage or cancel your subscriptions from your my account page. %s', + count( $subscriptions ), + 'woocommerce-subscriptions' + ), + $my_account_url + ) + ); } diff --git a/vendor/woocommerce/subscriptions-core/templates/emails/subscription-info.php b/vendor/woocommerce/subscriptions-core/templates/emails/subscription-info.php index ac20171..ea619b0 100644 --- a/vendor/woocommerce/subscriptions-core/templates/emails/subscription-info.php +++ b/vendor/woocommerce/subscriptions-core/templates/emails/subscription-info.php @@ -46,7 +46,8 @@ -get_time( 'next_payment' ) > 0 ) { +get_time( 'next_payment' ) > 0 && ! $skip_my_account_link ) { if ( count( $subscriptions ) === 1 ) { $subscription = reset( $subscriptions ); $my_account_url = $subscription->get_view_order_url(); @@ -55,12 +56,22 @@ } // Translators: Placeholders are opening and closing My Account link tags. - printf( '%s', wp_kses_post( sprintf( _n( - 'This subscription is set to renew automatically using your payment method on file. You can manage or cancel this subscription from your %smy account page%s.', - 'These subscriptions are set to renew automatically using your payment method on file. You can manage or cancel your subscriptions from your %smy account page%s.', - count( $subscriptions ), - 'woocommerce-subscriptions' - ), '', '' ) ) ); -}?> + printf( + '%s', + wp_kses_post( + sprintf( + _n( + 'This subscription is set to renew automatically using your payment method on file. You can manage or cancel this subscription from your %1$smy account page%2$s.', + 'These subscriptions are set to renew automatically using your payment method on file. You can manage or cancel your subscriptions from your %1$smy account page%2$s.', + count( $subscriptions ), + 'woocommerce-subscriptions' + ), + '', + '' + ) + ) + ); +} +?> diff --git a/vendor/woocommerce/subscriptions-core/templates/myaccount/subscription-totals-table.php b/vendor/woocommerce/subscriptions-core/templates/myaccount/subscription-totals-table.php index d33a6a6..58a2f64 100644 --- a/vendor/woocommerce/subscriptions-core/templates/myaccount/subscription-totals-table.php +++ b/vendor/woocommerce/subscriptions-core/templates/myaccount/subscription-totals-table.php @@ -76,7 +76,7 @@ get_id(), '_purchase_note', true ); + $purchase_note = $_product->get_purchase_note(); if ( $subscription->has_status( array( 'completed', 'processing' ) ) && $purchase_note ) { ?> diff --git a/vendor/woocommerce/subscriptions-core/wcs-functions.php b/vendor/woocommerce/subscriptions-core/wcs-functions.php index a7ce6fc..e7babbb 100644 --- a/vendor/woocommerce/subscriptions-core/wcs-functions.php +++ b/vendor/woocommerce/subscriptions-core/wcs-functions.php @@ -458,7 +458,7 @@ function wcs_get_subscriptions( $args ) { 'status' => $args['subscription_status'], 'limit' => $args['subscriptions_per_page'], 'page' => $args['paged'], - 'offset' => $args['offset'], + 'offset' => $args['offset'] > 0 ? $args['offset'] : null, 'order' => $args['order'], 'return' => 'ids', // just in case we need to filter or order by meta values later @@ -509,10 +509,23 @@ function wcs_get_subscriptions( $args ) { } } - // We need to restrict subscriptions to those which contain a certain product/variation - if ( ( 0 !== $args['product_id'] && is_numeric( $args['product_id'] ) ) || ( 0 !== $args['variation_id'] && is_numeric( $args['variation_id'] ) ) ) { - $subscriptions_for_product = wcs_get_subscriptions_for_product( array( $args['product_id'], $args['variation_id'] ) ); - $query_args = WCS_Admin_Post_Types::set_post__in_query_var( $query_args, $subscriptions_for_product ); + // It's more efficient to filter the results by product ID or variation ID rather than querying for via a "post__in" clause. + // This can only work where we know that the results will be sufficiently limited by the other query args. ie when we're querying by customer_id or order_id. + // We store the filters in a separate array so that we can apply them after the query has been run. + $query_controller = new WC_Subscription_Query_Controller( $args ); + + // We need to restrict subscriptions to those which contain a certain product/variation. + if ( $query_controller->has_product_query() ) { + if ( $query_controller->should_filter_query_results() ) { + // We will filter the results and apply any paging, limit and offset after the query has been run. + unset( $args['product_id'], $args['variation_id'], $query_args['limit'], $query_args['paged'], $query_args['offset'] ); + + // We need to get all subscriptions otherwise the limit could be filled with subscriptions that don't contain the product. + $query_args['limit'] = -1; + } else { + $subscriptions_for_product = wcs_get_subscriptions_for_product( array( $args['product_id'], $args['variation_id'] ) ); + $query_args = WCS_Admin_Post_Types::set_post__in_query_var( $query_args, $subscriptions_for_product ); + } } if ( ! empty( $query_args['meta_query'] ) ) { @@ -532,6 +545,12 @@ function wcs_get_subscriptions( $args ) { $subscriptions[ $subscription_id ] = wcs_get_subscription( $subscription_id ); } + // If we didn't query the database for subscriptions to a product, filter the results now. + if ( $query_controller->has_product_query() && $query_controller->should_filter_query_results() ) { + $subscriptions = $query_controller->filter_subscriptions( $subscriptions ); + $subscriptions = $query_controller->paginate_results( $subscriptions ); + } + return apply_filters( 'woocommerce_got_subscriptions', $subscriptions, $args ); } diff --git a/vendor/woocommerce/subscriptions-core/woocommerce-subscriptions-core.php b/vendor/woocommerce/subscriptions-core/woocommerce-subscriptions-core.php index aeffa84..2a05303 100644 --- a/vendor/woocommerce/subscriptions-core/woocommerce-subscriptions-core.php +++ b/vendor/woocommerce/subscriptions-core/woocommerce-subscriptions-core.php @@ -6,5 +6,5 @@ * Author: Automattic * Author URI: https://woocommerce.com/ * Requires WP: 5.6 - * Version: 7.6.0 + * Version: 7.7.1 */ diff --git a/woocommerce-subscriptions.php b/woocommerce-subscriptions.php index e9541b8..851517a 100644 --- a/woocommerce-subscriptions.php +++ b/woocommerce-subscriptions.php @@ -5,11 +5,11 @@ * Description: Sell products and services with recurring payments in your WooCommerce Store. * Author: WooCommerce * Author URI: https://woocommerce.com/ - * Version: 6.8.0 + * Version: 6.9.0 * Requires Plugins: woocommerce * * WC requires at least: 8.7.1 - * WC tested up to: 9.3.0 + * WC tested up to: 9.4 * Woo: 27147:6115e6d7e297b623a169fdcf5728b224 * * Copyright 2019 WooCommerce @@ -78,7 +78,7 @@ class WC_Subscriptions { public static $plugin_file = __FILE__; /** @var string */ - public static $version = '6.8.0'; // WRCS: DEFINED_VERSION. + public static $version = '6.9.0'; // WRCS: DEFINED_VERSION. /** @var string */ public static $wc_minimum_supported_version = '7.7';