diff --git a/assets/css/admin.css b/assets/css/admin.css index 73be899..3c0ad39 100644 --- a/assets/css/admin.css +++ b/assets/css/admin.css @@ -566,6 +566,19 @@ a.close-subscriptions-search { display: inline-block; font-weight: bold; } + +#woocommerce-subscription-schedule .date-fields.has-error { + color: #d63638; +} + +#woocommerce-subscription-schedule .date-fields .message { + margin: 0; +} + +#woocommerce-subscription-schedule .date-fields.has-error input { + border-color: #d63638; +} + #woocommerce-subscription-schedule .wcs-date-input input[type='text']:first-of-type { diff --git a/assets/js/admin/meta-boxes-subscription.js b/assets/js/admin/meta-boxes-subscription.js index fc61749..6b1b5d6 100644 --- a/assets/js/admin/meta-boxes-subscription.js +++ b/assets/js/admin/meta-boxes-subscription.js @@ -242,22 +242,66 @@ jQuery( function ( $ ) { // Update the UTC timestamp sent to the server date_pieces = $date_input.val().split( '-' ); + var newTimeStampValue = moment( { + years: date_pieces[ 0 ], + months: date_pieces[ 1 ] - 1, + date: date_pieces[ 2 ], + hours: $hour_input.val(), + minutes: $minute_input.val(), + seconds: one_hour_from_now.format( 'ss' ), + } ) + .utc() + .unix(); + + + // Moment will return NaN if the date is invalid, that's why we need to check for NaN only. + if ( isNaN( newTimeStampValue ) ) { + wcsShowDateFieldError( date_type ); + } else { + wcsHideDateFieldError( date_type ); + } + + // Intentionally do not prevent timestamp updates if the date is invalid. + // This way it's easier to catch invalid fields during submit event if attempted without editing invalid values. $( '#' + date_type + '_timestamp_utc' ).val( - moment( { - years: date_pieces[ 0 ], - months: date_pieces[ 1 ] - 1, - date: date_pieces[ 2 ], - hours: $hour_input.val(), - minutes: $minute_input.val(), - seconds: one_hour_from_now.format( 'ss' ), - } ) - .utc() - .unix() + newTimeStampValue ); $( 'body' ).trigger( 'wcs-updated-date', date_type ); } ); + function wcsShowDateFieldError( date_type ) { + var $fieldContainer = $( '#subscription-' + date_type + '-date' ); + $fieldContainer.addClass( 'has-error' ); + var $messageContainer = $fieldContainer.find( '.message' ); + var $messageContent = $messageContainer.find( '.message-content' ); + + // Clear and set content before showing to ensure screen readers announce the new message + $messageContent.text(''); + $messageContainer.show(); + + // Use setTimeout to ensure DOM update occurs before adding new text + setTimeout(function() { + // If the focus switched to the next field voice over skips announcing the error message. + // This is a workaround to ensure the error message is announced. + $fieldContainer + .find( `input#${date_type}` ) + .trigger( 'focus' ) + .trigger( 'blur' ); + $messageContent.text( wcs_admin_meta_boxes.i18n_invalid_date_notice ); + }, 100); + } + + function wcsHideDateFieldError( date_type ) { + var $fieldContainer = $( '#subscription-' + date_type + '-date' ); + $fieldContainer.removeClass( 'has-error' ); + var $messageContainer = $fieldContainer.find( '.message' ); + var $messageContent = $messageContainer.find( '.message-content' ); + + $messageContainer.hide(); + $messageContent.text(''); + } + function zeroise( val ) { return val > 9 ? val : '0' + val; } @@ -327,6 +371,26 @@ jQuery( function ( $ ) { return false; } + $( 'body.post-type-shop_subscription #post, body.woocommerce_page_wc-orders--shop_subscription #order' ).on( 'submit', function ( evt ) { + var invalid_dates = []; + $( '.woocommerce-subscriptions.date-picker' ).each( function () { + var $date_input = $( this ); + var date_type = $date_input.attr( 'id' ); + var timestamp = $( '#' + date_type + '_timestamp_utc' ).val(); + // At this point, timestamp is a string, not a number. + // We check for NaN only because everything else should be a valid timestamp set during the change event. + if ( timestamp === 'NaN' ) { + invalid_dates.push( date_type ); + } + } ); + + if ( invalid_dates.length > 0 ) { + // Focus the first invalid date to make it noticeable. + $( '#subscription-' + invalid_dates[0] + '-date' ).find( '.wcs-date-input input' ).first().focus(); + return false; + } + } ) + $( 'body.post-type-shop_subscription #post, body.woocommerce_page_wc-orders--shop_subscription #order' ).on( 'submit', function () { if ( 'wcs_process_renewal' == diff --git a/changelog.txt b/changelog.txt index 953f31c..2fa087b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,18 @@ *** WooCommerce Subscriptions Changelog *** +2025-07-09 - version 7.7.0 +* Fix: Restores normal behavior for the report caching updates scheduled action, which was failing due to a bad filepath. +* Fix: Fix error when placing an order with a valid card after using a declined one. +* Fix: Fix order renewal errors when using some plugins. +* Fix: Prevent fatal errors when loading or deleting subscriptions with corrupted date values stored in a database. +* Fix: Prevent fatal errors when trying to save invalid date values and display better error messages instead. +* Fix: Fix broken blocks and javascript translations. +* Fix: Keep newly created subscriptions in pending status when initial payment fails instead of putting them on hold. +* Dev: Improve test suite bootstrap process by postponing interactions with Action Scheduler until it has fully initialized. +* Dev: Plugin upgrade routines will now be triggered by changes in the main plugin version (and not the core library version). +* Dev: Update wcs_is_paypal_profile_a() so it handles non-string parameters more gracefully. This is principally to reduce noise levels when running the test suite. +* Dev: Proactively reviewed and hardened code to improve our security posture going forward. + 2025-06-11 - version 7.6.0 * Update: Allow updating billing info on existing subscriptions when customer changes account details. * Fix: Use floats instead of integers for tax calculation in subscription switching when prices include tax. diff --git a/includes/admin/reports/class-wcs-report-cache-manager.php b/includes/admin/reports/class-wcs-report-cache-manager.php index 3f118fe..90812fd 100644 --- a/includes/admin/reports/class-wcs-report-cache-manager.php +++ b/includes/admin/reports/class-wcs-report-cache-manager.php @@ -217,7 +217,16 @@ class WCS_Report_Cache_Manager { // Load report class dependencies require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' ); - $wc_core_dir = getenv( 'CI' ) ? ( getenv( 'WC_CORE_DIR' ) ? getenv( 'WC_CORE_DIR' ) : '/tmp/woocommerce' ) : WC()->plugin_path() . '/woocommerce'; + $within_ci_environment = getenv( 'CI' ); + $wc_core_dir_from_env = getenv( 'WC_CORE_DIR' ); + + if ( $within_ci_environment && ! empty( $wc_core_dir_from_env ) ) { + $wc_core_dir = $wc_core_dir_from_env; + } elseif ( $within_ci_environment ) { + $wc_core_dir = '/tmp/woocommerce'; + } else { + $wc_core_dir = WC()->plugin_path(); + } require_once( $wc_core_dir . '/includes/admin/reports/class-wc-admin-report.php' ); diff --git a/includes/core/abstracts/abstract-wcs-background-repairer.php b/includes/core/abstracts/abstract-wcs-background-repairer.php index 9d38d04..ee07b97 100644 --- a/includes/core/abstracts/abstract-wcs-background-repairer.php +++ b/includes/core/abstracts/abstract-wcs-background-repairer.php @@ -98,7 +98,7 @@ abstract class WCS_Background_Repairer extends WCS_Background_Upgrader { */ protected function update_item( $item ) { // Schedule the individual repair actions to run in 1 hr to give us the best chance at scheduling all the actions before they start running and clogging up the queue. - as_schedule_single_action( gmdate( 'U' ) + HOUR_IN_SECONDS, $this->repair_hook, array( 'repair_object' => $item ) ); + as_schedule_single_action( (int) gmdate( 'U' ) + HOUR_IN_SECONDS, $this->repair_hook, array( 'repair_object' => $item ) ); unset( $this->items_to_repair[ $item ] ); } diff --git a/includes/core/abstracts/abstract-wcs-background-updater.php b/includes/core/abstracts/abstract-wcs-background-updater.php index bb9130b..75a0c41 100644 --- a/includes/core/abstracts/abstract-wcs-background-updater.php +++ b/includes/core/abstracts/abstract-wcs-background-updater.php @@ -97,7 +97,8 @@ abstract class WCS_Background_Updater { $this->schedule_background_update(); // If the update is being run via WP CLI, we don't need to worry about the request time, just the processing time for this method - $start_time = $this->is_wp_cli_request() ? gmdate( 'U' ) : WCS_INIT_TIMESTAMP; + // @phpstan-ignore constant.notFound + $start_time = $this->is_wp_cli_request() ? (int) gmdate( 'U' ) : WCS_INIT_TIMESTAMP; do { @@ -107,7 +108,7 @@ abstract class WCS_Background_Updater { $this->update_item( $item ); - $time_elapsed = ( gmdate( 'U' ) - $start_time ); + $time_elapsed = (int) gmdate( 'U' ) - $start_time; if ( $time_elapsed >= $this->time_limit ) { break 2; @@ -127,7 +128,7 @@ abstract class WCS_Background_Updater { protected function schedule_background_update() { // A timestamp is returned if there's a pending action already scheduled. Otherwise true if its running or false if one doesn't exist. if ( ! is_numeric( as_next_scheduled_action( $this->scheduled_hook ) ) ) { - as_schedule_single_action( gmdate( 'U' ) + $this->time_limit, $this->scheduled_hook ); + as_schedule_single_action( (int) gmdate( 'U' ) + $this->time_limit, $this->scheduled_hook ); } } diff --git a/includes/core/abstracts/abstract-wcs-related-order-store.php b/includes/core/abstracts/abstract-wcs-related-order-store.php index 5824dac..60cf39d 100644 --- a/includes/core/abstracts/abstract-wcs-related-order-store.php +++ b/includes/core/abstracts/abstract-wcs-related-order-store.php @@ -58,7 +58,6 @@ abstract class WCS_Related_Order_Store { /** * Allow third-parties to register their own custom order relationship types which should be handled by this store. * - * @param array An array of order relationship types. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.5.0 */ foreach ( (array) apply_filters( 'wcs_additional_related_order_relation_types', array() ) as $relation_type ) { @@ -153,7 +152,7 @@ abstract class WCS_Related_Order_Store { * Get related order IDs grouped by relation type. * * @param WC_Order $subscription The subscription to find related orders. - * @param array $relation_type An array of relation types to fetch. Must be an array containing 'renewal', 'switch' or 'resubscribe' unless custom relationships are implemented. + * @param array $relation_types An array of relation types to fetch. Must be an array containing 'renewal', 'switch' or 'resubscribe' unless custom relationships are implemented. * * @return array An associative array where keys are relation types and values are arrays of related order IDs. */ diff --git a/includes/core/abstracts/abstract-wcs-table-maker.php b/includes/core/abstracts/abstract-wcs-table-maker.php index 5845302..89df5fa 100644 --- a/includes/core/abstracts/abstract-wcs-table-maker.php +++ b/includes/core/abstracts/abstract-wcs-table-maker.php @@ -35,7 +35,8 @@ abstract class WCS_Table_Maker { foreach ( $this->tables as $table ) { $wpdb->tables[] = $table; $name = $this->get_full_table_name( $table ); - $wpdb->$table = $name; + // phpcs:disable QITStandard.DB.DynamicWpdbMethodCall.DynamicMethod + $wpdb->$table = $name; } // create the tables diff --git a/includes/core/admin/class-wc-subscriptions-admin.php b/includes/core/admin/class-wc-subscriptions-admin.php index 4b02fd3..c49d5f1 100644 --- a/includes/core/admin/class-wc-subscriptions-admin.php +++ b/includes/core/admin/class-wc-subscriptions-admin.php @@ -160,12 +160,10 @@ class WC_Subscriptions_Admin { * triggered. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.1.1 - * - * @return null */ public static function clear_subscriptions_transients() { global $wpdb; - if ( empty( $_GET['action'] ) || empty( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'debug_action' ) ) { + if ( empty( $_GET['action'] ) || empty( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( wc_clean( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'debug_action' ) ) { return; } @@ -202,7 +200,7 @@ class WC_Subscriptions_Admin { /** * Add the 'subscriptions' product type to the WooCommerce product type select box. * - * @param array Array of Product types & their labels, excluding the Subscription product type. + * @param array $product_types Array of Product types & their labels, excluding the Subscription product type. * @return array Array of Product types & their labels, including the Subscription product type. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0 */ @@ -331,7 +329,10 @@ class WC_Subscriptions_Admin { - +

- +

get_product_type_name() ) ) ) ) { + if ( empty( $_POST['_wcsnonce'] ) || ! wp_verify_nonce( wc_clean( wp_unslash( $_POST['_wcsnonce'] ) ), 'wcs_subscription_meta' ) || false === self::is_subscription_product_save_request( $post_id, apply_filters( 'woocommerce_subscription_product_types', array( WC_Subscriptions_Core_Plugin::instance()->get_product_type_name() ) ) ) ) { return; } @@ -544,7 +547,7 @@ class WC_Subscriptions_Admin { $_POST['_subscription_trial_length'] = $max_trial_length; } - update_post_meta( $post_id, '_subscription_trial_length', $_POST['_subscription_trial_length'] ); + update_post_meta( $post_id, '_subscription_trial_length', wc_clean( wp_unslash( $_POST['_subscription_trial_length'] ) ) ); $_REQUEST['_subscription_sign_up_fee'] = wc_format_decimal( $_REQUEST['_subscription_sign_up_fee'] ); $_REQUEST['_subscription_one_time_shipping'] = isset( $_REQUEST['_subscription_one_time_shipping'] ) ? 'yes' : 'no'; @@ -572,13 +575,12 @@ class WC_Subscriptions_Admin { /** * Save meta data for variable subscription product type when the "Edit Product" form is submitted. * - * @param array Array of Product types & their labels, excluding the Subscription product type. - * @return array Array of Product types & their labels, including the Subscription product type. + * @param int $post_id The ID of the post being saved. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ public static function save_variable_subscription_meta( $post_id ) { - if ( empty( $_POST['_wcsnonce'] ) || ! wp_verify_nonce( $_POST['_wcsnonce'], 'wcs_subscription_meta' ) || false === self::is_subscription_product_save_request( $post_id, apply_filters( 'woocommerce_subscription_variable_product_types', array( 'variable-subscription' ) ) ) ) { + if ( empty( $_POST['_wcsnonce'] ) || ! wp_verify_nonce( wc_clean( wp_unslash( $_POST['_wcsnonce'] ) ), 'wcs_subscription_meta' ) || false === self::is_subscription_product_save_request( $post_id, apply_filters( 'woocommerce_subscription_variable_product_types', array( 'variable-subscription' ) ) ) ) { return; } @@ -596,7 +598,6 @@ class WC_Subscriptions_Admin { * Calculate and set a simple subscription's prices when edited via the bulk edit * * @param object $product An instance of a WC_Product_* object. - * @return null * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.3.9 */ public static function bulk_edit_save_subscription_meta( $product ) { @@ -621,7 +622,7 @@ class WC_Subscriptions_Admin { break; case 2: if ( strstr( $regular_price, '%' ) ) { - $percent = str_replace( '%', '', $regular_price ) / 100; + $percent = (float) str_replace( '%', '', $regular_price ) / 100; $new_price = $old_regular_price + ( $old_regular_price * $percent ); } else { $new_price = $old_regular_price + $regular_price; @@ -629,7 +630,7 @@ class WC_Subscriptions_Admin { break; case 3: if ( strstr( $regular_price, '%' ) ) { - $percent = str_replace( '%', '', $regular_price ) / 100; + $percent = (float) str_replace( '%', '', $regular_price ) / 100; $new_price = $old_regular_price - ( $old_regular_price * $percent ); } else { $new_price = $old_regular_price - $regular_price; @@ -655,7 +656,7 @@ class WC_Subscriptions_Admin { break; case 2: if ( strstr( $sale_price, '%' ) ) { - $percent = str_replace( '%', '', $sale_price ) / 100; + $percent = (float) str_replace( '%', '', $sale_price ) / 100; $new_price = $old_sale_price + ( $old_sale_price * $percent ); } else { $new_price = $old_sale_price + $sale_price; @@ -663,7 +664,7 @@ class WC_Subscriptions_Admin { break; case 3: if ( strstr( $sale_price, '%' ) ) { - $percent = str_replace( '%', '', $sale_price ) / 100; + $percent = (float) str_replace( '%', '', $sale_price ) / 100; $new_price = $old_sale_price - ( $old_sale_price * $percent ); } else { $new_price = $old_sale_price - $sale_price; @@ -671,7 +672,7 @@ class WC_Subscriptions_Admin { break; case 4: if ( strstr( $sale_price, '%' ) ) { - $percent = str_replace( '%', '', $sale_price ) / 100; + $percent = (float) str_replace( '%', '', $sale_price ) / 100; $new_price = $product->get_regular_price() - ( $product->get_regular_price() * $percent ); } else { $new_price = $product->get_regular_price() - $sale_price; @@ -706,17 +707,16 @@ class WC_Subscriptions_Admin { * subscription product type (or the bulk edit product is saved). * * @param int $post_id ID of the parent WC_Product_Variable_Subscription - * @return null * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.3 */ public static function process_product_meta_variable_subscription( $post_id ) { - if ( ! WC_Subscriptions_Product::is_subscription( $post_id ) || empty( $_POST['_wcsnonce'] ) || ! wp_verify_nonce( $_POST['_wcsnonce'], 'wcs_subscription_meta' ) ) { + if ( ! WC_Subscriptions_Product::is_subscription( $post_id ) || empty( $_POST['_wcsnonce'] ) || ! wp_verify_nonce( wc_clean( wp_unslash( $_POST['_wcsnonce'] ) ), 'wcs_subscription_meta' ) ) { return; } // Make sure WooCommerce calculates correct prices - $_POST['variable_regular_price'] = isset( $_POST['variable_subscription_price'] ) ? $_POST['variable_subscription_price'] : 0; + $_POST['variable_regular_price'] = isset( $_POST['variable_subscription_price'] ) ? wc_clean( wp_unslash( $_POST['variable_subscription_price'] ) ) : 0; // Sync the min variation price if ( wcs_is_woocommerce_pre( '3.0' ) ) { @@ -731,13 +731,12 @@ class WC_Subscriptions_Admin { * Save meta info for subscription variations * * @param int $variation_id - * @param int $i - * return void + * @param int $index * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ public static function save_product_variation( $variation_id, $index ) { - if ( ! WC_Subscriptions_Product::is_subscription( $variation_id ) || empty( $_POST['_wcsnonce_save_variations'] ) || ! wp_verify_nonce( $_POST['_wcsnonce_save_variations'], 'wcs_subscription_variations' ) ) { + if ( ! WC_Subscriptions_Product::is_subscription( $variation_id ) || empty( $_POST['_wcsnonce_save_variations'] ) || ! wp_verify_nonce( wc_clean( wp_unslash( $_POST['_wcsnonce_save_variations'] ) ), 'wcs_subscription_variations' ) ) { return; } @@ -793,15 +792,14 @@ class WC_Subscriptions_Admin { * * @param string $old_status Previous status of the subscription in update_status * @param string $new_status New status of the subscription in update_status - * @param WC_Subscription $subscription The subscription being saved + * @param WC_Subscription $subscription The subscription being savedf * - * @return null * @throws Exception in case there was no user found / there's no customer attached to it */ public static function check_customer_is_set( $old_status, $new_status, $subscription ) { global $post; - if ( is_admin() && 'active' == $new_status && isset( $_POST['woocommerce_meta_nonce'] ) && wp_verify_nonce( $_POST['woocommerce_meta_nonce'], 'woocommerce_save_data' ) && isset( $_POST['customer_user'] ) && ! empty( $post ) && 'shop_subscription' === $post->post_type ) { + if ( is_admin() && 'active' == $new_status && isset( $_POST['woocommerce_meta_nonce'] ) && wp_verify_nonce( wc_clean( wp_unslash( $_POST['woocommerce_meta_nonce'] ) ), 'woocommerce_save_data' ) && isset( $_POST['customer_user'] ) && ! empty( $post ) && 'shop_subscription' === $post->post_type ) { $user = new WP_User( absint( $_POST['customer_user'] ) ); @@ -816,7 +814,6 @@ class WC_Subscriptions_Admin { * Set default values for subscription dropdown fields when bulk adding variations to fix issue #1342 * * @param int $variation_id ID the post_id of the variation being added - * @return null */ public static function set_variation_meta_defaults_on_bulk_add( $variation_id ) { @@ -831,8 +828,6 @@ class WC_Subscriptions_Admin { /** * Adds all necessary admin styles. * - * @param array Array of Product types & their labels, excluding the Subscription product type. - * @return array Array of Product types & their labels, including the Subscription product type. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0 */ public static function enqueue_styles_scripts() { @@ -1008,7 +1003,7 @@ class WC_Subscriptions_Admin { search_box( __( 'Search Subscriptions', 'woocommerce-subscriptions' ), 'subscription' ); ?> - +
@@ -1070,6 +1065,7 @@ class WC_Subscriptions_Admin { public static function get_subscriptions_list_table() { if ( ! isset( self::$subscriptions_list_table ) ) { + // @phpstan-ignore class.notFound self::$subscriptions_list_table = new WC_Subscriptions_List_Table(); } @@ -1085,7 +1081,7 @@ class WC_Subscriptions_Admin { */ public static function update_subscription_settings() { - if ( empty( $_POST['_wcsnonce'] ) || ! wp_verify_nonce( $_POST['_wcsnonce'], 'wcs_subscription_settings' ) ) { + if ( empty( $_POST['_wcsnonce'] ) || ! wp_verify_nonce( wc_clean( wp_unslash( $_POST['_wcsnonce'] ) ), 'wcs_subscription_settings' ) ) { return; } @@ -1484,7 +1480,7 @@ class WC_Subscriptions_Admin { // Map the order or subscription type to their respective keys and type key. $object_type = 'shop_order' === $typenow ? 'order' : 'subscription'; - $cache_report_key = isset( $_GET[ "_{$object_type}s_list_key" ] ) ? $_GET[ "_{$object_type}s_list_key" ] : ''; + $cache_report_key = isset( $_GET[ "_{$object_type}s_list_key" ] ) ? wc_clean( wp_unslash( $_GET[ "_{$object_type}s_list_key" ] ) ) : ''; // If the report key or report arg is empty exit early. if ( empty( $cache_report_key ) || empty( $_GET['_report'] ) ) { @@ -1492,7 +1488,7 @@ class WC_Subscriptions_Admin { return $where; } - $cache = get_transient( $_GET['_report'] ); + $cache = get_transient( wc_clean( wp_unslash( $_GET['_report'] ) ) ); // Display an admin notice if we cannot find the report data requested. if ( ! isset( $cache[ $cache_report_key ] ) ) { @@ -1522,6 +1518,7 @@ class WC_Subscriptions_Admin { // $format = '%d, %d, %d, %d, %d, [...]' $format = implode( ', ', array_fill( 0, count( $ids ), '%d' ) ); + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare $where .= $wpdb->prepare( " AND {$wpdb->posts}.ID IN ($format)", $ids ); return $where; @@ -1541,7 +1538,7 @@ class WC_Subscriptions_Admin { return $where; } - $user_id = $_GET['_paid_subscription_orders_for_customer_user']; + $user_id = wc_clean( wp_unslash( $_GET['_paid_subscription_orders_for_customer_user'] ) ); // Unset the GET arg so that it doesn't interfere with the query for user's subscriptions. unset( $_GET['_paid_subscription_orders_for_customer_user'] ); @@ -1559,7 +1556,7 @@ class WC_Subscriptions_Admin { $where .= " AND {$wpdb->posts}.ID = 0"; } else { // Orders with paid status - $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_status IN ( 'wc-processing', 'wc-completed' )" ); + $where .= " AND {$wpdb->posts}.post_status IN ( 'wc-processing', 'wc-completed' )"; $where .= sprintf( " AND {$wpdb->posts}.ID IN (%s)", implode( ',', array_unique( $users_subscription_orders ) ) ); } @@ -1711,7 +1708,7 @@ class WC_Subscriptions_Admin { /** * Adds Subscriptions specific details to the WooCommerce System Status report. * - * @param array $attributes Shortcode attributes. + * @param array $debug_data * @return array */ public static function add_system_status_items( $debug_data ) { @@ -1946,14 +1943,15 @@ class WC_Subscriptions_Admin { /** * Check if subscription product meta data should be saved for the current request. * - * @param array Array of product types. + * @param int $post_id The ID of the post being saved. + * @param array $product_types Array of product types. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.9 */ private static function is_subscription_product_save_request( $post_id, $product_types ) { if ( self::$saved_product_meta ) { $is_subscription_product_save_request = false; - } elseif ( empty( $_POST['_wcsnonce'] ) || ! wp_verify_nonce( $_POST['_wcsnonce'], 'wcs_subscription_meta' ) ) { + } elseif ( empty( $_POST['_wcsnonce'] ) || ! wp_verify_nonce( wc_clean( wp_unslash( $_POST['_wcsnonce'] ) ), 'wcs_subscription_meta' ) ) { $is_subscription_product_save_request = false; } elseif ( ! isset( $_POST['product-type'] ) || ! in_array( $_POST['product-type'], $product_types ) ) { $is_subscription_product_save_request = false; @@ -2059,7 +2057,7 @@ class WC_Subscriptions_Admin { */ public static function validate_product_type_change( $product_id ) { - if ( empty( $_POST['woocommerce_meta_nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['woocommerce_meta_nonce'] ), 'woocommerce_save_data' ) || empty( $_POST['product-type'] ) ) { + if ( empty( $_POST['woocommerce_meta_nonce'] ) || ! wp_verify_nonce( wc_clean( wp_unslash( $_POST['woocommerce_meta_nonce'] ) ), 'woocommerce_save_data' ) || empty( $_POST['product-type'] ) ) { return; } diff --git a/includes/core/admin/class-wcs-admin-meta-boxes.php b/includes/core/admin/class-wcs-admin-meta-boxes.php index 692f81d..8347b4f 100644 --- a/includes/core/admin/class-wcs-admin-meta-boxes.php +++ b/includes/core/admin/class-wcs-admin-meta-boxes.php @@ -49,7 +49,7 @@ class WCS_Admin_Meta_Boxes { add_action( 'woocommerce_order_action_wcs_retry_renewal_payment', array( __CLASS__, 'process_retry_renewal_payment_action_request' ), 10, 1 ); // Disable stock management while adding line items to a subscription via AJAX. - add_action( 'option_woocommerce_manage_stock', array( __CLASS__, 'override_stock_management' ) ); + add_filter( 'option_woocommerce_manage_stock', array( __CLASS__, 'override_stock_management' ) ); // Parent order line item price lock option. add_action( 'woocommerce_order_item_add_action_buttons', array( __CLASS__, 'output_price_lock_html' ) ); @@ -178,6 +178,7 @@ class WCS_Admin_Meta_Boxes { 'i18n_trial_end_start_notice' => __( 'Please enter a date after the start date.', 'woocommerce-subscriptions' ), 'i18n_trial_end_next_notice' => __( 'Please enter a date before the next payment.', 'woocommerce-subscriptions' ), 'i18n_end_date_notice' => __( 'Please enter a date after the next payment.', 'woocommerce-subscriptions' ), + 'i18n_invalid_date_notice' => __( 'Invalid date', 'woocommerce-subscriptions' ), 'process_renewal_action_warning' => __( "Are you sure you want to process a renewal?\n\nThis will charge the customer and email them the renewal order (if emails are enabled).", 'woocommerce-subscriptions' ), 'payment_method' => $subscription->get_payment_method(), 'search_customers_nonce' => wp_create_nonce( 'search-customers' ), @@ -244,7 +245,7 @@ class WCS_Admin_Meta_Boxes { /** * Handles the action request to process a renewal order. * - * @param array $subscription + * @param WC_Subscription $subscription * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ public static function process_renewal_action_request( $subscription ) { @@ -321,7 +322,7 @@ class WCS_Admin_Meta_Boxes { /** * Handles the action request to create a pending parent order. * - * @param array $subscription + * @param WC_Subscription $subscription * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3 */ public static function create_pending_parent_action_request( $subscription ) { @@ -351,7 +352,7 @@ class WCS_Admin_Meta_Boxes { /** * Removes order related emails from the available actions. * - * @param array $available_emails + * @param array $email_actions * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ public static function remove_order_email_actions( $email_actions ) { @@ -398,6 +399,7 @@ class WCS_Admin_Meta_Boxes { private static function can_renewal_order_be_retried( $order ) { $can_be_retried = false; + $is_automatic = false; if ( wcs_order_contains_renewal( $order ) && $order->needs_payment() && '' != wcs_get_objects_property( $order, 'payment_method' ) ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison $supports_date_changes = false; @@ -461,11 +463,14 @@ class WCS_Admin_Meta_Boxes { $needs_price_lock = false; + /** + * @var WC_Order_Item_Product $line_item + */ foreach ( $order->get_items() as $line_item ) { $product = $line_item->get_product(); // If the line item price is above the current live price. - if ( $product && ( $line_item->get_subtotal() / $line_item->get_quantity() ) > $product->get_price() ) { + if ( $product && ( (float) $line_item->get_subtotal() / $line_item->get_quantity() ) > $product->get_price() ) { $needs_price_lock = true; break; } @@ -520,7 +525,7 @@ class WCS_Admin_Meta_Boxes { * * @param int $item_id The ID of the order item added. * @param WC_Order_Item_Product $line_item The line item added. - * @param WC_Abstract_Order $order The order or subscription the product was added to. + * @param WC_Order $order The order or subscription the product was added to. */ public static function store_item_base_location_tax( $item_id, $line_item, $order ) { if ( ! apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) { @@ -687,21 +692,25 @@ class WCS_Admin_Meta_Boxes { continue; } + /** + * @var WC_Order_Item_Product $line_item + */ $new_line_subtotal = wc_format_decimal( $new_line_subtotal ); $current_base_location_taxes = $line_item->get_meta( '_subtracted_base_location_taxes' ); $old_line_subtotal = $line_item->get_subtotal(); $old_line_quantity = $line_item->get_quantity(); $new_line_quantity = absint( $item_data['order_item_qty'][ $line_item_id ] ); + $new_base_taxes = array(); if ( $line_item->meta_exists( '_subtracted_base_location_rates' ) ) { $base_tax_rates = $line_item->get_meta( '_subtracted_base_location_rates' ); - $product_price = ( $new_line_subtotal + array_sum( WC_Tax::calc_exclusive_tax( $new_line_subtotal, $base_tax_rates ) ) ) / $new_line_quantity; + $product_price = ( (float) $new_line_subtotal + array_sum( WC_Tax::calc_exclusive_tax( $new_line_subtotal, $base_tax_rates ) ) ) / $new_line_quantity; $new_base_taxes = WC_Tax::calc_tax( $product_price, $base_tax_rates, true ); } else { // Update all the base taxes for the new product subtotal. foreach ( $current_base_location_taxes as $rate_id => $tax_amount ) { - $new_base_taxes[ $rate_id ] = ( ( $new_line_subtotal / $new_line_quantity ) / ( $old_line_subtotal / $old_line_quantity ) ) * $tax_amount; + $new_base_taxes[ $rate_id ] = ( ( (float) $new_line_subtotal / $new_line_quantity ) / ( (float) $old_line_subtotal / $old_line_quantity ) ) * $tax_amount; } } diff --git a/includes/core/admin/class-wcs-admin-post-types.php b/includes/core/admin/class-wcs-admin-post-types.php index d8564d2..e70f049 100644 --- a/includes/core/admin/class-wcs-admin-post-types.php +++ b/includes/core/admin/class-wcs-admin-post-types.php @@ -82,7 +82,7 @@ class WCS_Admin_Post_Types { // Add Subscription list table status views when HPOS is enabled. add_filter( 'views_woocommerce_page_wc-orders--shop_subscription', array( $this, 'filter_subscription_list_table_views' ) ); - add_action( 'list_table_primary_column', array( $this, 'list_table_primary_column' ), 10, 2 ); + add_filter( 'list_table_primary_column', array( $this, 'list_table_primary_column' ), 10, 2 ); add_filter( 'post_row_actions', array( $this, 'shop_subscription_row_actions' ), 10, 2 ); add_filter( 'handle_bulk_actions-woocommerce_page_wc-orders--shop_subscription', [ $this, 'handle_subscription_bulk_actions' ], 10, 3 ); @@ -212,8 +212,6 @@ class WCS_Admin_Post_Types { * Displays the dropdown for the product filter * * @param string $order_type The type of order. This will be 'shop_subscription' for Subscriptions. - * - * @return string the html dropdown element */ public function restrict_by_product( $order_type = '' ) { if ( '' === $order_type ) { @@ -1171,6 +1169,7 @@ class WCS_Admin_Post_Types { payment_gateways->get_available_payment_gateways() as $gateway_id => $gateway ) { echo ''; } @@ -1243,7 +1242,7 @@ class WCS_Admin_Post_Types { * Get the HTML for order item meta to display on the Subscription list table. * * @param WC_Order_Item $item - * @param WC_Product $product + * @param WC_Product $_product * @return string */ protected static function get_item_name_html( $item, $_product, $include_quantity = 'include_quantity' ) { @@ -1276,9 +1275,9 @@ class WCS_Admin_Post_Types { * On the Subscriptions list table, subscriptions with multiple items display those line items in a table. * This function generates an individual row for a specific line item. * - * @param WC_Line_Item_Product $item The line item product object. - * @param string $item_name The line item's name. - * @param string $item_meta_html The line item's meta HTML generated by @see wc_display_item_meta(). + * @param WC_Order_Item_Product $item The line item product object. + * @param string $item_name The line item's name. + * @param string $item_meta_html The line item's meta HTML generated by @see wc_display_item_meta(). * * @return string The table row HTML content for a line item. */ @@ -1489,7 +1488,7 @@ class WCS_Admin_Post_Types { /** * Handles bulk updating the status subscriptions. * - * @param array $ids Subscription IDs to be trashed or deleted. + * @param array $subscription_ids Subscription IDs to be trashed or deleted. * @param string $new_status The new status to update the subscriptions to. * * @return array Array of query args to redirect to after handling the bulk action request. @@ -1529,7 +1528,7 @@ class WCS_Admin_Post_Types { /** * Handles bulk trashing and deleting of subscriptions. * - * @param array $ids Subscription IDs to be trashed or deleted. + * @param array $subscription_ids Subscription IDs to be trashed or deleted. * @param bool $force_delete When set, the subscription will be completed deleted. Otherwise, it will be trashed. * * @return array Array of query args to redirect to after handling the bulk action request. @@ -1557,7 +1556,7 @@ class WCS_Admin_Post_Types { /** * Handles bulk untrashing of subscriptions. * - * @param array $ids Subscription IDs to be restored. + * @param array $subscription_ids Subscription IDs to be restored. * * @return array Array of query args to redirect to after handling the bulk action request. */ @@ -1696,13 +1695,13 @@ class WCS_Admin_Post_Types { * * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v3.0.7 * - * @param WC_Line_Item_Product $item The subscription line item object. - * @param WC_Subscription $subscription The subscription object. This variable is no longer used. - * @param string $element The type of element to generate. Can be 'div' or 'row'. Default is 'div'. + * @param WC_Order_Item_Product $item The subscription line item object. + * @param WC_Subscription $subscription The subscription object. This variable is no longer used. + * @param string $element The type of element to generate. Can be 'div' or 'row'. Default is 'div'. * * @return string The line item column HTML content for a line item. */ - protected static function get_item_display( $item, $subscription = '', $element = 'div' ) { + protected static function get_item_display( $item, $subscription = null, $element = 'div' ) { wcs_deprecated_function( __METHOD__, '3.0.7' ); $_product = $item->get_product(); $item_meta_html = self::get_item_meta_html( $item ); @@ -1723,9 +1722,9 @@ class WCS_Admin_Post_Types { * * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v3.0.7 * - * @param WC_Line_Item_Product $item The line item object. - * @param string $item_name The line item's name. - * @param string $item_meta_html The line item's meta HTML. + * @param WC_Order_Item_Product $item The line item object. + * @param string $item_name The line item's name. + * @param string $item_meta_html The line item's meta HTML. * * @return string The subscription line item column HTML content. */ @@ -1814,7 +1813,7 @@ class WCS_Admin_Post_Types { * - Low performance: This method uses a subquery to get the last payment date for each subscription. * * @param string[] $pieces Associative array of the clauses for the query. - * @param OrdersTableQuery $query The query object. + * @param string $query The query object. * @param array $args Query args. * * @return string[] $pieces Associative array of the clauses for the query. @@ -1869,6 +1868,7 @@ class WCS_Admin_Post_Types { * @return string[] $pieces Updated associative array of clauses for the query. */ private function orders_table_clauses_low_performance( $pieces ) { + // @phpstan-ignore-next-line $order_datastore = wc_get_container()->get( \Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore::class ); $order_table = $order_datastore::get_orders_table_name(); $meta_table = $order_datastore::get_meta_table_name(); @@ -1899,6 +1899,7 @@ class WCS_Admin_Post_Types { private function orders_table_clauses_high_performance( $pieces ) { global $wpdb; + // @phpstan-ignore-next-line $order_datastore = wc_get_container()->get( \Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore::class ); $order_table = $order_datastore::get_orders_table_name(); $meta_table = $order_datastore::get_meta_table_name(); diff --git a/includes/core/admin/debug-tools/class-wcs-debug-tool-factory.php b/includes/core/admin/debug-tools/class-wcs-debug-tool-factory.php index 4220ea5..2c8c2b7 100644 --- a/includes/core/admin/debug-tools/class-wcs-debug-tool-factory.php +++ b/includes/core/admin/debug-tools/class-wcs-debug-tool-factory.php @@ -53,7 +53,7 @@ final class WCS_Debug_Tool_Factory { /** * Get the string used to identify the tool. * - * @param string The name of the cache tool being created + * @param string $tool_name The name of the cache tool being created * @return string The key used to identify the tool - sanitized name with wcs_ prefix. */ protected static function get_tool_key( $tool_name ) { diff --git a/includes/core/admin/meta-boxes/class-wcs-meta-box-schedule.php b/includes/core/admin/meta-boxes/class-wcs-meta-box-schedule.php index 70670e1..5944710 100644 --- a/includes/core/admin/meta-boxes/class-wcs-meta-box-schedule.php +++ b/includes/core/admin/meta-boxes/class-wcs-meta-box-schedule.php @@ -46,8 +46,8 @@ class WCS_Meta_Box_Schedule { * * @see woocommerce_process_shop_order_meta * - * @param int $subscription_id The subscription ID to save the schedule for. - * @param WC_Subscription/WP_Post $subscription The subscription object to save the schedule for. + * @param int $subscription_id The subscription ID to save the schedule for. + * @param WC_Subscription $subscription The subscription object to save the schedule for. */ public static function save( $subscription_id, $subscription ) { @@ -71,7 +71,8 @@ class WCS_Meta_Box_Schedule { $subscription->set_billing_period( wc_clean( wp_unslash( $_POST['_billing_period'] ) ) ); } - $dates = array(); + $dates = array(); + $invalid_dates = array(); foreach ( wcs_get_subscription_date_types() as $date_type => $date_label ) { $date_key = wcs_normalise_date_type_key( $date_type ); @@ -91,7 +92,13 @@ class WCS_Meta_Box_Schedule { continue; } - $dates[ $date_key ] = gmdate( 'Y-m-d H:i:s', $datetime ); + $timestamp = wcs_date_to_time( $datetime ); + + if ( null !== $timestamp ) { + $dates[ $date_key ] = $timestamp; + } else { + $invalid_dates[ $date_key ] = $datetime; + } } try { @@ -101,10 +108,57 @@ class WCS_Meta_Box_Schedule { if ( ! wcs_is_custom_order_tables_usage_enabled() ) { wp_cache_delete( $subscription_id, 'posts' ); } - } catch ( Exception $e ) { + + $subscription->save(); + + if ( ! empty( $invalid_dates ) ) { + $subscription_date_types = wcs_get_subscription_date_types(); + $invalid_dates_labels = array_map( + function ( $date_type ) use ( $subscription_date_types ) { + // Fallback to the date type key in case there is no translation string. + return isset( $subscription_date_types[ $date_type ] ) ? $subscription_date_types[ $date_type ] : $date_type; + }, + array_keys( $invalid_dates ) + ); + + $warning_message = sprintf( + // translators: 1$ is a comma-separated list of invalid dates fields like "Start Date", "Next Payment", 2$-3$: opening and closing tags. + __( 'Some subscription dates could not be updated because they contain invalid values: %2$s%1$s%3$s. Please correct these dates and save the changes.', 'woocommerce-subscriptions' ), + esc_html( implode( ', ', $invalid_dates_labels ) ), + '', + '' + ); + + wc_get_logger()->warning( + $warning_message, + array( + 'subscription_id' => $subscription_id, + 'invalid_dates' => $invalid_dates, + ) + ); + + wcs_add_admin_notice( + $warning_message, + 'error', // There is no warning level for admin notices, so using error level. + get_current_user_id(), + get_current_screen()->id + ); + } + } catch ( \Throwable $e ) { + // Log the error. + wc_get_logger()->error( + sprintf( + 'Error updating subscription #%d: %s', + $subscription_id, + $e->getMessage(), + ), + array( + 'stack_trace' => $e->getTraceAsString(), + ) + ); + + // Display an admin notice. wcs_add_admin_notice( $e->getMessage(), 'error' ); } - - $subscription->save(); } } diff --git a/includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php b/includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php index 27907bd..f126fdd 100644 --- a/includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php +++ b/includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php @@ -44,7 +44,10 @@ class WCS_Meta_Box_Subscription_Data extends WC_Meta_Box_Order_Data { #post-body-content, #titlediv, #major-publishing-actions, #minor-publishing-actions, #visibility, #submitdiv { display:none }
- +
diff --git a/includes/core/admin/meta-boxes/views/html-subscription-schedule.php b/includes/core/admin/meta-boxes/views/html-subscription-schedule.php index a7f6569..e35b437 100644 --- a/includes/core/admin/meta-boxes/views/html-subscription-schedule.php +++ b/includes/core/admin/meta-boxes/views/html-subscription-schedule.php @@ -61,6 +61,10 @@ if ( ! defined( 'ABSPATH' ) ) { get_date_to_display( $internal_date_key ) ); ?> +

diff --git a/includes/core/class-wc-product-variable-subscription.php b/includes/core/class-wc-product-variable-subscription.php index 364b833..37dce7c 100644 --- a/includes/core/class-wc-product-variable-subscription.php +++ b/includes/core/class-wc-product-variable-subscription.php @@ -287,8 +287,10 @@ class WC_Product_Variable_Subscription extends WC_Product_Variable { } /** + * Use WC core add-to-cart handlers for subscription products. * - * @param string $product_type A string representation of a product type + * @param string $handler The name of the handler to use when adding product to the cart + * @param WC_Product $product */ public function add_to_cart_handler( $handler, $product ) { wcs_deprecated_function( __METHOD__, '2.2.0', 'WC_Subscriptions_Cart::add_to_cart_handler( $handler, $product )' ); @@ -298,10 +300,11 @@ class WC_Product_Variable_Subscription extends WC_Product_Variable { /** * Sync variable product prices with the children lowest/highest prices. * - * @access public + * @param int $product_id The ID of the product to sync. + * * @return void */ - public function variable_product_sync( $product_id = '' ) { + public function variable_product_sync( $product_id = 0 ) { wcs_deprecated_function( __METHOD__, '2.2,0', 'WC_Subscriptions_Product::variable_subscription_product_sync( $this )' ); if ( empty( $product_id ) ) { diff --git a/includes/core/class-wc-subscription-query-controller.php b/includes/core/class-wc-subscription-query-controller.php index 5b3adac..61a9a73 100644 --- a/includes/core/class-wc-subscription-query-controller.php +++ b/includes/core/class-wc-subscription-query-controller.php @@ -75,8 +75,8 @@ class WC_Subscription_Query_Controller { /** * Filters the subscription query results by product ID or variation ID. * - * @param WC_Subscriptions[] $subscriptions - * @return WC_Subscriptions[] The filtered subscriptions. + * @param WC_Subscription[] $subscriptions + * @return WC_Subscription[] The filtered subscriptions. */ public function filter_subscriptions( $subscriptions ) { $filtered_subscriptions = []; diff --git a/includes/core/class-wc-subscription.php b/includes/core/class-wc-subscription.php index c4864e3..21598ce 100644 --- a/includes/core/class-wc-subscription.php +++ b/includes/core/class-wc-subscription.php @@ -332,7 +332,7 @@ class WC_Subscription extends WC_Order { switch ( $new_status ) { case 'pending': - if ( $this->has_status( array( 'auto-draft', 'draft' ) ) ) { + if ( $this->has_status( array( 'auto-draft', 'draft', 'on-hold' ) ) ) { $can_be_updated = true; } else { $can_be_updated = false; @@ -358,7 +358,7 @@ class WC_Subscription extends WC_Order { break; case 'failed': // core WC order status mapped internally to avoid exceptions case 'on-hold': - if ( $this->payment_method_supports( 'subscription_suspension' ) && $this->has_status( array( 'active', 'pending' ) ) ) { + if ( $this->payment_method_supports( 'subscription_suspension' ) && $this->has_status( array( 'active' ) ) ) { $can_be_updated = true; } else { $can_be_updated = false; @@ -420,11 +420,12 @@ class WC_Subscription extends WC_Order { * * @param string $new_status Status to change the order to. No internal wc- prefix is required. * @param string $note (default: '') Optional note to add + * @return bool */ public function update_status( $new_status, $note = '', $manual = false ) { if ( ! $this->get_id() ) { - return; + return false; } // Standardise status names. @@ -574,6 +575,8 @@ class WC_Subscription extends WC_Order { throw $e; } } + + return true; } /** @@ -662,9 +665,9 @@ class WC_Subscription extends WC_Order { * * @since 5.1.0 * - * @param string $new_status The new status. - * @param string $note Optional. The note to add to the subscription. - * @param bool $manual Optional. Is the status change triggered manually? Default is false. + * @param string $new_status The new status. + * @param string $note Optional. The note to add to the subscription. + * @param bool $manual_update Optional. Is the status change triggered manually? Default is false. */ public function set_status( $new_status, $note = '', $manual_update = false ) { if ( ! $this->object_read && in_array( $new_status, [ 'draft', 'auto-draft' ], true ) ) { @@ -1191,7 +1194,7 @@ class WC_Subscription extends WC_Order { * Used for WC 3.0 compatibility and for WC_Subscription_Legacy to override. * * @param string $date_type 'trial_end', 'next_payment', 'cancelled', 'payment_retry' or 'end' - * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if their is no date. + * @param string|integer|null $value UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if their is no date. */ protected function set_date_prop( $date_type, $value ) { parent::set_date_prop( $this->get_date_prop_key( $date_type ), $value ); @@ -1323,7 +1326,7 @@ class WC_Subscription extends WC_Order { /** * Formats a subscription date timestamp for display. * - * @param int $timestamp The subscription date in a timestamp format. + * @param int $timestamp_gmt The subscription date in a timestamp format. * @param string $date_type The subscription date type to display. @see WC_Subscription::get_valid_date_types() * * @return string The formatted date to display. @@ -1385,14 +1388,67 @@ class WC_Subscription extends WC_Order { /** * Set the dates on the subscription. * - * Because dates are interdependent on each other, this function will take an array of dates, make sure that all - * dates are in the right order in the right format, that there is at least something to update. + * This method is more strict than update_valid_dates() in that it will throw an exception if any of the dates are not in the correct format or are not compatible with the current subscription dates. + * + * @see update_valid_dates() for a more permissive alternative that allows ignoring invalid dates. * * @param array $dates array containing dates with keys: 'date_created', 'trial_end', 'next_payment', 'last_order_date_created' or 'end'. Values are MySQL formatted date/time strings in UTC timezone. * @param string $timezone The timezone of the $datetime param. Default 'gmt'. + * @return bool True if the dates were updated, false otherwise. + * @throws InvalidArgumentException if the dates are not in the correct format or are not compatible with the current subscription dates. */ - public function update_dates( $dates, $timezone = 'gmt' ) { - $dates = $this->validate_date_updates( $dates, $timezone ); + public function update_dates( $dates, $timezone = 'gmt' ): bool { + return $this->flexible_update_dates( + $dates, + array( + 'timezone' => $timezone, + 'ignore_invalid_dates' => false, + ) + ); + } + + /** + * Set the dates on the subscription. + * + * This method is more permissive than update_dates() in that it will ignore invalid date values and save only valid values. + * It still throws an exception if the date values are in the wrong order. + * + * @see update_dates() for a more strict alternative that will throw an exception if any of the dates are not in the correct format or are not compatible with the current subscription dates. + * + * @param array $dates array containing dates with keys: 'date_created', 'trial_end', 'next_payment', 'last_order_date_created' or 'end'. Values are MySQL formatted date/time strings in UTC timezone. + * @param string $timezone The timezone of the $datetime param. Default 'gmt'. + * @return bool True if the dates were updated, false otherwise. + * @throws InvalidArgumentException if the dates are not in the correct format or are not compatible with the current subscription dates. + * + * @since 7.7.0 More permissive alternative to update_dates(). + */ + public function update_valid_dates( $dates, $timezone = 'gmt' ): bool { + return $this->flexible_update_dates( + $dates, + array( + 'timezone' => $timezone, + 'ignore_invalid_dates' => true, + ) + ); + } + + /** + * Set the dates on the subscription. + * + * Because dates are interdependent on each other, this function will take an array of dates, + * make sure that all dates are in the right order in the right format, and that there is at least something to update. + * + * @param array $dates array containing dates with keys: 'date_created', 'trial_end', 'next_payment', 'last_order_date_created' or 'end'. Values are MySQL formatted date/time strings in UTC timezone. + * @param array $validation_options array containing the following validation options: + * - timezone: The timezone of the $datetime param. Default 'gmt'. + * - ignore_invalid_dates: Whether to ignore invalid dates. Default false. When invalid date is ignored, the current value stored on subscription (if any) is used instead. + * @return bool True if the dates were updated, false otherwise. + * @throws InvalidArgumentException if the dates are not in the correct format or are not compatible with the current subscription dates. + * + * @since 7.7.0 Shared logic for update_dates() and update_valid_dates(). + */ + private function flexible_update_dates( $dates, $validation_options = array() ): bool { + $dates = $this->prepare_dates_for_update( $dates, $validation_options ); // If an exception hasn't been thrown by this point, we can safely update the dates $is_updated = false; @@ -1454,6 +1510,8 @@ class WC_Subscription extends WC_Order { do_action( 'woocommerce_subscription_date_updated', $this, $date_type, $datetime ); } } + + return $is_updated; } /** @@ -1907,11 +1965,12 @@ class WC_Subscription extends WC_Order { * Process payment on the subscription, which mainly means processing it for the last order on the subscription. * * @param $transaction_id string Optional transaction id to store in post meta + * @return bool */ public function payment_complete( $transaction_id = '' ) { if ( WC_Subscriptions_Change_Payment_Gateway::$is_request_to_change_payment ) { - return; + return false; } // Clear the cached renewal payment counts, kept here for backward compat even though it's also reset in $this->process_payment_complete() @@ -1927,12 +1986,14 @@ class WC_Subscription extends WC_Order { } $this->payment_complete_for_order( $last_order ); + + return true; } /** * When payment is completed for a related order, reset any renewal related counters and reactive the subscription. * - * @param WC_Order $order + * @param WC_Order $last_order */ public function payment_complete_for_order( $last_order ) { @@ -2370,6 +2431,7 @@ class WC_Subscription extends WC_Order { if ( is_a( $payment_method, 'WC_Payment_Gateway' ) ) { $payment_gateway = $payment_method; } else { + // @phpstan-ignore property.notFound $payment_gateways = WC()->payment_gateways->payment_gateways(); $payment_gateway = isset( $payment_gateways[ $payment_method_id ] ) ? $payment_gateways[ $payment_method_id ] : null; } @@ -2444,7 +2506,7 @@ class WC_Subscription extends WC_Order { /** * Check if the subscription has a line item for a specific product, by ID. * - * @param int A product or variation ID to check for. + * @param int $product_id A product or variation ID to check for. * @return bool */ public function has_product( $product_id ) { @@ -2498,14 +2560,15 @@ class WC_Subscription extends WC_Order { * The single quantity sign-up fee will be returned instead of the total sign-up fee paid. For example, if 3 x a product * with a 10 BTC sign-up fee was purchased, a total 30 BTC was paid as the sign-up fee but this function will return 10 BTC. * - * @param array|int Either an order item (in the array format returned by self::get_items()) or the ID of an order item. - * @param string $tax_inclusive_or_exclusive Whether or not to adjust sign up fee if prices inc tax - ensures that the sign up fee paid amount includes the paid tax if inc + * @param WC_Order_Item_Product|int $line_item Either an order item (in the array format returned by self::get_items()) or the ID of an order item. + * @param string $tax_inclusive_or_exclusive Whether or not to adjust sign up fee if prices inc tax - ensures that the sign up fee paid amount includes the paid tax if inc * @return bool * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ public function get_items_sign_up_fee( $line_item, $tax_inclusive_or_exclusive = 'exclusive_of_tax' ) { if ( ! is_object( $line_item ) ) { + /** @var WC_Order_Item_Product $line_item */ $line_item = wcs_get_order_item( $line_item, $this ); } @@ -2517,12 +2580,10 @@ class WC_Subscription extends WC_Order { $sign_up_fee = 0; } else { - - $original_order_item = ''; - // Find the matching item on the order foreach ( $parent_order->get_items() as $order_item ) { if ( wcs_get_canonical_product_id( $line_item ) == wcs_get_canonical_product_id( $order_item ) ) { + /** @var WC_Order_Item_Product $original_order_item */ $original_order_item = $order_item; break; } @@ -2541,7 +2602,7 @@ class WC_Subscription extends WC_Order { // The synced sign up fee meta contains the raw product sign up fee, if the subscription totals are inclusive of tax, we need to adjust the synced sign up fee to match tax inclusivity. if ( $this->get_prices_include_tax() ) { - $line_item_total = (float) $original_order_item->get_total( 'edit' ) + $original_order_item->get_total_tax( 'edit' ); + $line_item_total = (float) $original_order_item->get_total( 'edit' ) + (float) $original_order_item->get_total_tax( 'edit' ); $signup_fee_portion = $sign_up_fee / $line_item_total; $sign_up_fee = (float) $original_order_item->get_total( 'edit' ) * $signup_fee_portion; } @@ -2555,8 +2616,8 @@ class WC_Subscription extends WC_Order { // If prices don't inc tax, ensure that the sign up fee amount includes the tax. if ( 'inclusive_of_tax' === $tax_inclusive_or_exclusive && ! empty( $original_order_item ) && ! empty( $sign_up_fee ) ) { - $sign_up_fee_proportion = $sign_up_fee / ( $original_order_item->get_total( 'edit' ) / $original_order_item->get_quantity( 'edit' ) ); - $sign_up_fee_tax = $original_order_item->get_total_tax( 'edit' ) * $sign_up_fee_proportion; + $sign_up_fee_proportion = $sign_up_fee / ( (float) $original_order_item->get_total( 'edit' ) / $original_order_item->get_quantity( 'edit' ) ); + $sign_up_fee_tax = (float) $original_order_item->get_total_tax( 'edit' ) * $sign_up_fee_proportion; $sign_up_fee += $sign_up_fee_tax; $sign_up_fee = wc_format_decimal( $sign_up_fee, wc_get_price_decimals() ); @@ -2632,11 +2693,43 @@ class WC_Subscription extends WC_Order { * Validates subscription date updates ensuring the proposed date changes are in the correct format and are compatible with * the current subscription dates. Also returns the dates in the gmt timezone - ready for setting/deleting. * - * @param array $dates array containing dates with keys: 'date_created', 'trial_end', 'next_payment', 'last_order_date_created' or 'end'. Values are MySQL formatted date/time strings in UTC timezone. + * @see prepare_dates_for_update() as a preferrable and more flexible alternative. + * + * @param array $dates array containing dates with keys: 'date_created', 'trial_end', 'next_payment', 'last_order_date_created' or 'end'. Values are timestamps or MySQL formatted date/time strings in UTC timezone. * @param string $timezone The timezone of the $datetime param. Default 'gmt'. * @return array $dates array of dates in gmt timezone. + * @throws InvalidArgumentException if the dates are not in the correct format or are not compatible with the current subscription dates. + * + * @deprecated 7.7.0 - Use prepare_dates_for_update() instead. This method remains in place for backwards compatibility. */ - public function validate_date_updates( $dates, $timezone = 'gmt' ) { + public function validate_date_updates( array $dates, string $timezone = 'gmt' ): array { + return $this->prepare_dates_for_update( + $dates, + array( + 'timezone' => $timezone, + 'ignore_invalid_dates' => false, + ) + ); + } + + /** + * Prepares the dates for setting/deleting by validating values and adjusting to the gmt timezone. + * + * Validates subscription date updates ensuring the proposed date changes are in the correct format and are compatible with + * the current subscription dates. Also allows excluding invalid dates from the results. + * + * @param array $dates array containing dates with keys: 'date_created', 'trial_end', 'next_payment', 'last_order_date_created' or 'end'. Values are timestamps or MySQL formatted date/time strings in UTC timezone. + * @param array $options array containing the following validation options: + * - timezone: The timezone of the $datetime param. Default 'gmt'. + * - ignore_invalid_dates: Whether to ignore invalid dates. Default false. When invalid date is ignored, the current value stored on subscription (if any) is used instead. + * @return array $dates array of dates in gmt timezone. + * @throws InvalidArgumentException if the dates are not in the correct format or are not compatible with the current subscription dates. + * + * @since 7.7.0 Alternative to validate_date_updates() for more flexible validation options. + */ + public function prepare_dates_for_update( $dates, $options = array() ): array { + $timezone = isset( $options['timezone'] ) ? $options['timezone'] : 'gmt'; + $ignore_invalid_dates = isset( $options['ignore_invalid_dates'] ) ? $options['ignore_invalid_dates'] : false; if ( ! is_array( $dates ) ) { throw new InvalidArgumentException( __( 'Invalid format. First parameter needs to be an array.', 'woocommerce-subscriptions' ) ); @@ -2654,9 +2747,10 @@ class WC_Subscription extends WC_Order { } // Use the normalised keys for the array - $dates = array_combine( $passed_date_keys, array_values( $dates ) ); - - $timestamps = $delete_date_types = array(); + $dates = array_combine( $passed_date_keys, array_values( $dates ) ); + $timestamps = array(); + $delete_date_types = array(); + $messages = array(); // Get a full set of subscription dates made up of passed and current dates foreach ( $this->get_valid_date_types() as $date_type ) { @@ -2675,15 +2769,39 @@ class WC_Subscription extends WC_Order { if ( isset( $dates[ $date_type ] ) ) { $datetime = $dates[ $date_type ]; - if ( ! empty( $datetime ) && false === wcs_is_datetime_mysql_format( $datetime ) ) { - // translators: placeholder is date type (e.g. "end", "next_payment"...) - throw new InvalidArgumentException( sprintf( _x( 'Invalid %s date. The date must be of the format: "Y-m-d H:i:s".', 'appears in an error message if date is wrong format', 'woocommerce-subscriptions' ), $date_type ) ); - } - - if ( empty( $datetime ) ) { - + // When timestamp is passed, we don't need to convert into time string and backwards. + if ( wcs_is_timestamp( $datetime ) ) { + // Timestamps could be passed as strings, so we need to cast them to int. + $timestamps[ $date_type ] = (int) $datetime; + } elseif ( empty( $datetime ) ) { $timestamps[ $date_type ] = 0; + } elseif ( false === wcs_is_datetime_mysql_format( $datetime ) ) { + // Date in invalid format passed, so we might need to handle it gracefully. + if ( $ignore_invalid_dates ) { + // Fallback to the current subscription time instead. + $timestamps[ $date_type ] = $this->get_time( $date_type ); + // Skip invalid date formats instead of throwing an exception - corrupted dates are ignored during update. + $logger = wc_get_logger(); + $logger->warning( + sprintf( + 'Value for %1$s is ignored due to invalid date format. Value: %2$s', + $date_type, + esc_html( $datetime ), + ), + array( + 'subscription_id' => $this->get_id(), + 'date_type' => $date_type, + ) + ); + } else { + $timestamps[ $date_type ] = 0; + $messages[] = sprintf( + // translators: placeholder is date type (e.g. "end", "next_payment"...) + _x( 'Invalid %s date. The date must be of the format: "Y-m-d H:i:s".', 'appears in an error message if date is wrong format', 'woocommerce-subscriptions' ), + esc_html( $date_type ) + ); + } } else { if ( 'gmt' !== strtolower( $timezone ) ) { @@ -2692,14 +2810,14 @@ class WC_Subscription extends WC_Order { $timestamps[ $date_type ] = wcs_date_to_time( $datetime ); } - // otherwise get the current subscription time + // Otherwise get the current subscription time. } else { $timestamps[ $date_type ] = $this->get_time( $date_type ); } - if ( 0 == $timestamps[ $date_type ] ) { + if ( 0 === (int) $timestamps[ $date_type ] ) { // Last payment is not in the UI, and it should NOT be deleted as that would mess with scheduling - if ( 'last_order_date_created' != $date_type && 'date_created' != $date_type ) { + if ( 'last_order_date_created' !== $date_type && 'date_created' !== $date_type ) { // We need to separate the dates which need deleting, so they don't interfere in the remaining validation $delete_date_types[ $date_type ] = 0; } @@ -2707,8 +2825,6 @@ class WC_Subscription extends WC_Order { } } - $messages = array(); - // And then iterate over them checking the relationships between them. foreach ( $timestamps as $date_type => $timestamp ) { switch ( $date_type ) { @@ -2741,7 +2857,7 @@ class WC_Subscription extends WC_Order { } } - $dates[ $date_type ] = gmdate( 'Y-m-d H:i:s', $timestamp ); + $dates[ $date_type ] = gmdate( wcs_get_db_datetime_format(), $timestamp ); } // Don't validate dates while the subscription is being read, only dates set outside of instantiation require the strict validation rules to apply @@ -2757,9 +2873,9 @@ class WC_Subscription extends WC_Order { * Add a product line item to the subscription. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.1.4 - * @param WC_Product product - * @param int line item quantity. - * @param array args + * @param WC_Product $product + * @param int $qty quantity. + * @param array $args * @return int|bool Item ID or false. */ public function add_product( $product, $qty = 1, $args = array() ) { diff --git a/includes/core/class-wc-subscriptions-addresses.php b/includes/core/class-wc-subscriptions-addresses.php index 51c7fed..a618798 100644 --- a/includes/core/class-wc-subscriptions-addresses.php +++ b/includes/core/class-wc-subscriptions-addresses.php @@ -82,7 +82,7 @@ class WC_Subscriptions_Addresses { if ( ! self::can_user_edit_subscription_address( absint( $_GET['subscription'] ) ) ) { wc_add_notice( 'Invalid subscription.', 'error' ); - wp_redirect( wc_get_account_endpoint_url( 'dashboard' ) ); + wp_safe_redirect( wc_get_account_endpoint_url( 'dashboard' ) ); exit(); } } @@ -252,7 +252,7 @@ class WC_Subscriptions_Addresses { } $address_type = ( 'billing' === $address_type || 'shipping' === $address_type ) ? $address_type : ''; - $address_fields = WC()->countries->get_address_fields( esc_attr( $_POST[ $address_type . '_country' ] ), $address_type . '_' ); + $address_fields = WC()->countries->get_address_fields( esc_attr( wc_clean( wp_unslash( $_POST[ $address_type . '_country' ] ) ) ), $address_type . '_' ); $address = array(); foreach ( $address_fields as $key => $field ) { diff --git a/includes/core/class-wc-subscriptions-cart-validator.php b/includes/core/class-wc-subscriptions-cart-validator.php index 6091e28..89a8c6a 100644 --- a/includes/core/class-wc-subscriptions-cart-validator.php +++ b/includes/core/class-wc-subscriptions-cart-validator.php @@ -132,7 +132,7 @@ class WC_Subscriptions_Cart_Validator { * * @return bool Whether the product can be added to the cart. */ - public static function can_add_product_to_cart( $can_add, $product_id, $quantity, $variation_id = '', $variations = array(), $item_data = array() ) { + public static function can_add_product_to_cart( $can_add, $product_id, $quantity, $variation_id = 0, $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' ); @@ -161,7 +161,7 @@ class WC_Subscriptions_Cart_Validator { # Force error on add_to_cart() to redirect add_filter( 'woocommerce_add_to_cart_validation', '__return_false', 10 ); - add_filter( 'woocommerce_cart_redirect_after_error', 'wc_get_cart_url', 10, 2 ); + add_filter( 'woocommerce_cart_redirect_after_error', 'wc_get_cart_url', 10 ); do_action( 'wc_ajax_add_to_cart' ); return $fragments; diff --git a/includes/core/class-wc-subscriptions-cart.php b/includes/core/class-wc-subscriptions-cart.php index 06efd98..f7f201e 100644 --- a/includes/core/class-wc-subscriptions-cart.php +++ b/includes/core/class-wc-subscriptions-cart.php @@ -502,7 +502,6 @@ class WC_Subscriptions_Cart { * This is attached as a callback to hooks triggered whenever a product is removed from the cart. * * @param $cart_item_key string The key for a cart item about to be removed from the cart. - * @return null * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.15 */ public static function maybe_reset_chosen_shipping_methods( $cart_item_key ) { @@ -1131,7 +1130,7 @@ class WC_Subscriptions_Cart { /** * Checks the cart to see if it contains a specific product. * - * @param int The product ID or variation ID to look for. + * @param int $product_id The product ID or variation ID to look for. * @return bool Whether the product is in the cart. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.13 */ @@ -1154,7 +1153,7 @@ class WC_Subscriptions_Cart { /** * Checks the cart to see if it contains any subscription product other than a specific product. * - * @param int The product ID or variation ID other than which to look for. + * @param int $product_id The product ID or variation ID other than which to look for. * @return bool Whether another subscription product is in the cart. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v3.0.5 */ @@ -1252,7 +1251,7 @@ class WC_Subscriptions_Cart { /** * Allow third-parties to apply fees which apply to the cart to recurring carts. * - * @param WC_Cart + * @param WC_Cart $cart * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.16 */ public static function apply_recurring_fees( $cart ) { @@ -1367,7 +1366,7 @@ class WC_Subscriptions_Cart { * @param array $available_methods set of shipping rates for this calculation * @param int $package_index WC doesn't pass the package index to callbacks on the 'woocommerce_shipping_chosen_method' filter (yet) so we set a default value of 0 for it in the function params * - * @return $default_method + * @return string */ public static function set_chosen_shipping_method( $default_method, $available_methods, $package_index = 0 ) { $chosen_methods = WC()->session->get( 'chosen_shipping_methods', array() ); @@ -1409,8 +1408,8 @@ class WC_Subscriptions_Cart { return $url; } - $quantity = isset( $_REQUEST['quantity'] ) ? $_REQUEST['quantity'] : 1; - $product_id = $_REQUEST['add-to-cart']; + $quantity = isset( $_REQUEST['quantity'] ) ? wc_clean( wp_unslash( $_REQUEST['quantity'] ) ) : 1; + $product_id = wc_clean( wp_unslash( $_REQUEST['add-to-cart'] ) ); $add_to_cart_notice = wc_add_to_cart_message( array( $product_id => $quantity ), true, true ); @@ -1497,12 +1496,15 @@ class WC_Subscriptions_Cart { * * Returns the cart_item containing the product renewal, else false. * + * @param string $role The role of the cart item to check for. + * @return array|false The cart item containing the renewal, else false. + * * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.3 */ public static function cart_contains_subscription_renewal( $role = '' ) { _deprecated_function( __METHOD__, '2.0', 'wcs_cart_contains_renewal( $role )' ); - return wcs_cart_contains_renewal( $role ); + return wcs_cart_contains_renewal(); } /** @@ -1590,8 +1592,8 @@ class WC_Subscriptions_Cart { /** * Returns individual coupon's formatted discount amount for WooCommerce 2.1+ * - * @param string $discount_html String of the coupon's discount amount - * @param string $coupon WC_Coupon object for the coupon to which this line item relates + * @param string $cart_totals_fee_html String of the coupon's discount amount + * @param string $fee WC_Coupon object for the coupon to which this line item relates * @return string formatted subscription price string if the cart includes a coupon being applied to recurring amount * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.4.6 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 @@ -1695,6 +1697,8 @@ class WC_Subscriptions_Cart { public static function get_cart_subscription_period() { _deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' ); + $period = ''; + if ( self::cart_contains_subscription() ) { foreach ( WC()->cart->cart_contents as $cart_item ) { if ( WC_Subscriptions_Product::is_subscription( $cart_item['data'] ) ) { @@ -1718,6 +1722,8 @@ class WC_Subscriptions_Cart { public static function get_cart_subscription_interval() { _deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' ); + $interval = 0; + foreach ( WC()->cart->cart_contents as $cart_item ) { if ( WC_Subscriptions_Product::is_subscription( $cart_item['data'] ) ) { $interval = WC_Subscriptions_Product::get_interval( $cart_item['data'] ); @@ -2110,6 +2116,8 @@ class WC_Subscriptions_Cart { public static function calculate_recurring_shipping() { _deprecated_function( __METHOD__, '2.0', 'values from WC()->cart->recurring_carts' ); + $recurring_total = 0; + foreach ( WC()->cart->recurring_carts as $cart ) { $recurring_total = $cart->shipping_total; } @@ -2302,8 +2310,8 @@ class WC_Subscriptions_Cart { /** * Return a localized free trial period string. * - * @param int An interval in the range 1-6 - * @param string One of day, week, month or year. + * @param int $number An interval in the range 1-6 + * @param string $period One of day, week, month or year. */ public static function format_free_trial_period( $number, $period ) { if ( 'day' === $period ) { @@ -2399,6 +2407,8 @@ class WC_Subscriptions_Cart { } break; } + + return ''; } /** * Adds meta data so it can be displayed in the Cart. @@ -2456,7 +2466,7 @@ class WC_Subscriptions_Cart { * sends the shipping methods with a numerical index. * * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v3.1.0 - * @return null + * @return void * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.12 */ public static function add_shipping_method_post_data() { @@ -2467,7 +2477,7 @@ class WC_Subscriptions_Cart { check_ajax_referer( 'update-order-review', 'security' ); - parse_str( $_POST['post_data'], $form_data ); + parse_str( wc_clean( wp_unslash( $_POST['post_data'] ) ), $form_data ); // In case we have only free trials/sync'd products in the cart and shipping methods aren't being displayed if ( ! isset( $_POST['shipping_method'] ) ) { diff --git a/includes/core/class-wc-subscriptions-change-payment-gateway.php b/includes/core/class-wc-subscriptions-change-payment-gateway.php index 2156841..d491040 100644 --- a/includes/core/class-wc-subscriptions-change-payment-gateway.php +++ b/includes/core/class-wc-subscriptions-change-payment-gateway.php @@ -218,10 +218,10 @@ class WC_Subscriptions_Change_Payment_Gateway { * @param WC_Subscription $subscription * @return bool Whether the request is valid or not. */ - private static function validate_change_payment_request( $subscription ) { + private static function validate_change_payment_request( $subscription = null ) { $is_valid = true; - if ( wp_verify_nonce( $_GET['_wpnonce'] ) === false ) { + if ( wp_verify_nonce( wc_clean( wp_unslash( $_GET['_wpnonce'] ) ) ) === false ) { $is_valid = false; wc_add_notice( __( 'There was an error with your request. Please try again.', 'woocommerce-subscriptions' ), 'error' ); } elseif ( empty( $subscription ) ) { @@ -244,8 +244,8 @@ class WC_Subscriptions_Change_Payment_Gateway { /** * Add a "Change Payment Method" button to the "My Subscriptions" table. * - * @param array $all_actions The $subscription_key => $actions array with all actions that will be displayed for a subscription on the "My Subscriptions" table - * @param array $subscriptions All of a given users subscriptions that will be displayed on the "My Subscriptions" table + * @param array $actions The $subscription_key => $actions array with all actions that will be displayed for a subscription on the "My Subscriptions" table + * @param WC_Subscription $subscription The subscription. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.4 */ public static function change_payment_method_button( $actions, $subscription ) { @@ -272,24 +272,23 @@ class WC_Subscriptions_Change_Payment_Gateway { * * Based on the @see woocommerce_pay_action() function. * - * @access public * @return void * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.4 */ public static function change_payment_method_via_pay_shortcode() { - if ( ! isset( $_POST['_wcsnonce'] ) || ! wp_verify_nonce( $_POST['_wcsnonce'], 'wcs_change_payment_method' ) ) { + if ( ! isset( $_POST['_wcsnonce'] ) || ! wp_verify_nonce( wc_clean( wp_unslash( $_POST['_wcsnonce'] ) ), 'wcs_change_payment_method' ) ) { return; } - $subscription_id = absint( $_POST['woocommerce_change_payment'] ); + $subscription_id = absint( wc_clean( wp_unslash( $_POST['woocommerce_change_payment'] ) ) ); $subscription = wcs_get_subscription( $subscription_id ); do_action( 'woocommerce_subscription_change_payment_method_via_pay_shortcode', $subscription ); ob_start(); - if ( $subscription->get_order_key() == $_GET['key'] ) { + if ( $subscription->get_order_key() == wc_clean( wp_unslash( $_GET['key'] ) ) ) { $subscription_billing_country = $subscription->get_billing_country(); $subscription_billing_state = $subscription->get_billing_state(); @@ -366,7 +365,7 @@ class WC_Subscriptions_Change_Payment_Gateway { // Does the customer want all current subscriptions to be updated to this payment method? if ( isset( $_POST['update_all_subscriptions_payment_method'] ) - && $_POST['update_all_subscriptions_payment_method'] + && wc_clean( wp_unslash( $_POST['update_all_subscriptions_payment_method'] ) ) && WC_Subscriptions_Change_Payment_Gateway::can_update_all_subscription_payment_methods( $available_gateways[ $new_payment_method ], $subscription ) ) { // Allow some payment gateways which can't process the payment immediately, like PayPal, to do it later after the payment/sign-up is confirmed @@ -409,7 +408,7 @@ class WC_Subscriptions_Change_Payment_Gateway { $subscription->save_meta_data(); } - $payment_meta_table = WCS_Payment_tokens::get_subscription_payment_meta( $subscription, $new_payment_method ); + $payment_meta_table = WCS_Payment_Tokens::get_subscription_payment_meta( $subscription, $new_payment_method ); if ( ! is_array( $payment_meta_table ) ) { return false; } @@ -498,7 +497,7 @@ class WC_Subscriptions_Change_Payment_Gateway { * @param array $new_payment_method_meta The meta for the new payment method. Optional. Default false. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.4 */ - public static function update_payment_method( $subscription, $new_payment_method, $new_payment_method_meta = false ) { + public static function update_payment_method( $subscription, $new_payment_method, $new_payment_method_meta = [] ) { $old_payment_method = $subscription->get_payment_method(); $old_payment_method_title = $subscription->get_payment_method_title(); @@ -618,7 +617,7 @@ class WC_Subscriptions_Change_Payment_Gateway { public static function maybe_zero_total( $total, $subscription ) { global $wp; - if ( ! empty( $_POST['_wcsnonce'] ) && wp_verify_nonce( $_POST['_wcsnonce'], 'wcs_change_payment_method' ) && isset( $_POST['woocommerce_change_payment'] ) && wcs_is_subscription( $subscription ) && $subscription->get_order_key() == $_GET['key'] && $subscription->get_id() == absint( $_POST['woocommerce_change_payment'] ) ) { + if ( ! empty( $_POST['_wcsnonce'] ) && wp_verify_nonce( wc_clean( wp_unslash( $_POST['_wcsnonce'] ) ), 'wcs_change_payment_method' ) && isset( $_POST['woocommerce_change_payment'] ) && wcs_is_subscription( $subscription ) && $subscription->get_order_key() == $_GET['key'] && $subscription->get_id() == absint( $_POST['woocommerce_change_payment'] ) ) { $total = 0; } elseif ( ! self::$is_request_to_change_payment && isset( $wp->query_vars['order-pay'] ) && wcs_is_subscription( absint( $wp->query_vars['order-pay'] ) ) ) { // if the request to pay for the order belongs to a subscription but there's no GET params for changing payment method, the receipt page is being used to collect credit card details so we still need to $0 the total @@ -635,7 +634,7 @@ class WC_Subscriptions_Change_Payment_Gateway { */ public static function get_return_url( $return_url ) { - if ( ! empty( $_POST['_wcsnonce'] ) && wp_verify_nonce( $_POST['_wcsnonce'], 'wcs_change_payment_method' ) && isset( $_POST['woocommerce_change_payment'] ) ) { + if ( ! empty( $_POST['_wcsnonce'] ) && wp_verify_nonce( wc_clean( wp_unslash( $_POST['_wcsnonce'] ) ), 'wcs_change_payment_method' ) && isset( $_POST['woocommerce_change_payment'] ) ) { $return_url = get_permalink( wc_get_page_id( 'myaccount' ) ); } @@ -649,15 +648,15 @@ class WC_Subscriptions_Change_Payment_Gateway { * Also trigger a hook for payment gateways to update any meta on the original order for a subscription. * * @param WC_Order $renewal_order The order which recorded the successful payment (to make up for the failed automatic payment). - * @param WC_Order $original_order The original order in which the subscription was purchased. + * @param WC_Subscription $subscription The subscription. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.4 */ public static function change_failing_payment_method( $renewal_order, $subscription ) { if ( ! $subscription->is_manual() ) { - if ( ! empty( $_POST['_wcsnonce'] ) && wp_verify_nonce( $_POST['_wcsnonce'], 'wcs_change_payment_method' ) && isset( $_POST['payment_method'] ) ) { - $new_payment_method = wc_clean( $_POST['payment_method'] ); + if ( ! empty( $_POST['_wcsnonce'] ) && wp_verify_nonce( wc_clean( wp_unslash( $_POST['_wcsnonce'] ) ), 'wcs_change_payment_method' ) && isset( $_POST['payment_method'] ) ) { + $new_payment_method = wc_clean( wp_unslash( $_POST['payment_method'] ) ); } else { $new_payment_method = wcs_get_objects_property( $renewal_order, 'payment_method' ); } @@ -796,7 +795,6 @@ class WC_Subscriptions_Change_Payment_Gateway { * This is causing `$gateway->payment_fields()` to be called multiple times. * * @param bool $needs_payment - * @param WC_Subscription $subscription * @return bool * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.7 */ @@ -845,7 +843,9 @@ class WC_Subscriptions_Change_Payment_Gateway { /** * Update the recurring payment method on a subscription order. * - * @param array $available_gateways The payment gateways which are currently being allowed. + * @param string $subscription_key The subscription key. + * @param WC_Order $order The order. + * @param string $new_payment_method The new payment method. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.4 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ diff --git a/includes/core/class-wc-subscriptions-checkout.php b/includes/core/class-wc-subscriptions-checkout.php index 9c51df0..f4cb36d 100644 --- a/includes/core/class-wc-subscriptions-checkout.php +++ b/includes/core/class-wc-subscriptions-checkout.php @@ -116,7 +116,7 @@ class WC_Subscriptions_Checkout { * The function doesn't validate whether the cart item is a subscription product, meaning it can be used for any cart item, * but the item will need a `subscription_period` and `subscription_period_interval` value set on it, at a minimum. * - * @param WC_Order $order + * @param WC_Subscription $order * @param WC_Cart $cart * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ @@ -152,6 +152,7 @@ class WC_Subscriptions_Checkout { } // Set the subscription's billing and shipping address + /** @var WC_Subscription $subscription */ $subscription = wcs_copy_order_address( $order, $subscription ); $subscription->update_dates( @@ -336,7 +337,7 @@ class WC_Subscriptions_Checkout { /** * Remove the Backordered meta data from subscription line items added on the checkout. * - * @param WC_Order_Item_Product $order_item + * @param WC_Order_Item_Product $item * @param string $cart_item_key The hash used to identify the item in the cart * @param array $cart_item The cart item's data. * @param WC_Order|WC_Subscription $subscription The order or subscription object to which the line item relates @@ -451,9 +452,9 @@ class WC_Subscriptions_Checkout { * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v3.0.10 * - * @param WC_Line_Item_Product $line_item The line item added to the order/subscription. - * @param string $cart_item_key The key of the cart item being added to the cart. - * @param array $cart_item The cart item data. + * @param WC_Order_Item_Product $line_item The line item added to the order/subscription. + * @param string $cart_item_key The key of the cart item being added to the cart. + * @param array $cart_item The cart item data. */ public static function store_line_item_base_location_taxes( $line_item, $cart_item_key, $cart_item ) { if ( isset( $cart_item['_subtracted_base_location_taxes'] ) ) { @@ -671,6 +672,7 @@ class WC_Subscriptions_Checkout { $switch_items = wcs_cart_contains_switches(); $renewal_item = wcs_cart_contains_renewal(); $resubscribe_item = wcs_cart_contains_resubscribe(); + $subscription_id = null; if ( ! $switch_items && ! $renewal_item && ! $resubscribe_item ) { return $ship_to_different_address; diff --git a/includes/core/class-wc-subscriptions-core-plugin.php b/includes/core/class-wc-subscriptions-core-plugin.php index 580ee61..04015f9 100644 --- a/includes/core/class-wc-subscriptions-core-plugin.php +++ b/includes/core/class-wc-subscriptions-core-plugin.php @@ -161,10 +161,13 @@ class WC_Subscriptions_Core_Plugin { add_action( 'plugins_loaded', array( $this, 'init_version_dependant_classes' ) ); // Initialised the related order and customter data store instances. + // @phpstan-ignore return.void add_action( 'plugins_loaded', 'WCS_Related_Order_Store::instance' ); + // @phpstan-ignore return.void add_action( 'plugins_loaded', 'WCS_Customer_Store::instance' ); // Initialise the batch processing controller. + // @phpstan-ignore return.void add_action( 'init', 'WCS_Batch_Processing_Controller::instance' ); // Initialise the scheduler. @@ -213,7 +216,7 @@ class WC_Subscriptions_Core_Plugin { /** * Allow third-party code to enable running v2.0 hook deprecation handling for stores that might want to check for deprecated code. * - * @param bool Whether the hook deprecation handlers should be loaded. False by default. + * @param bool $value Whether the hook deprecation handlers should be loaded. False by default. */ if ( apply_filters( 'woocommerce_subscriptions_load_deprecation_handlers', false ) ) { new WCS_Action_Deprecator(); @@ -527,11 +530,11 @@ class WC_Subscriptions_Core_Plugin { } // if this is the first time activating WooCommerce Subscription we want to enable PayPal debugging by default. - if ( '0' == get_option( WC_Subscriptions_Admin::$option_prefix . '_previous_version', '0' ) && false == get_option( WC_Subscriptions_admin::$option_prefix . '_paypal_debugging_default_set', false ) ) { + if ( '0' == get_option( WC_Subscriptions_Admin::$option_prefix . '_previous_version', '0' ) && false == get_option( WC_Subscriptions_Admin::$option_prefix . '_paypal_debugging_default_set', false ) ) { $paypal_settings = get_option( 'woocommerce_paypal_settings' ); $paypal_settings['debug'] = 'yes'; update_option( 'woocommerce_paypal_settings', $paypal_settings ); - update_option( WC_Subscriptions_admin::$option_prefix . '_paypal_debugging_default_set', 'true' ); + update_option( WC_Subscriptions_Admin::$option_prefix . '_paypal_debugging_default_set', 'true' ); } // Enable customer notifications by default for new stores. diff --git a/includes/core/class-wc-subscriptions-coupon.php b/includes/core/class-wc-subscriptions-coupon.php index c0a9699..647a721 100644 --- a/includes/core/class-wc-subscriptions-coupon.php +++ b/includes/core/class-wc-subscriptions-coupon.php @@ -90,7 +90,7 @@ class WC_Subscriptions_Coupon { add_filter( 'woocommerce_cart_totals_coupon_label', __CLASS__ . '::get_pseudo_coupon_label', 10, 2 ); - add_filter( 'woocommerce_cart_totals_coupon_html', __CLASS__ . '::mark_recurring_coupon_in_initial_cart_for_hiding', 10, 3 ); + add_filter( 'woocommerce_cart_totals_coupon_html', __CLASS__ . '::mark_recurring_coupon_in_initial_cart_for_hiding', 10, 2 ); add_filter( 'woocommerce_coupon_is_valid_for_product', array( __CLASS__, 'validate_subscription_coupon_for_product' ), 10, 3 ); add_filter( 'woocommerce_coupon_get_apply_quantity', array( __CLASS__, 'override_applied_quantity_for_recurring_carts' ), 10, 3 ); @@ -101,7 +101,7 @@ class WC_Subscriptions_Coupon { * Mark such recurring coupons with a dummy span with class wcs-hidden-coupon so that it can be hidden. * * @param string $coupon_html Html string of the recurring coupon's cell in the Cart totals table - * @param WC_coupon $coupon WC_Coupon object of the recurring coupon + * @param WC_Coupon $coupon WC_Coupon object of the recurring coupon * @return string $coupon_html Modified html string of the coupon containing the marking * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3 */ @@ -300,6 +300,8 @@ class WC_Subscriptions_Coupon { * This is so rows with different tax rates get a fair discount, and so rows with no price (free) don't get discounted. * * BUT... we also need the subtotal to exclude non renewal products, so user the renewal subtotal + * + * @phpstan-ignore binaryOp.invalid */ $discount_percent = ( $discounting_amount * $cart_item['quantity'] ) / self::get_renewal_subtotal( wcs_get_coupon_property( $coupon, 'code' ) ); @@ -562,6 +564,7 @@ class WC_Subscriptions_Coupon { * @param WC_Coupon $coupon The coupon object. * @param string $coupon_type The coupon's discount_type property. * @param string $calculation_type The current calculation type. + * @param WC_Cart $cart The cart object. */ if ( apply_filters( 'wcs_bypass_coupon_removal', false, $coupon, $coupon_type, $calculation_type, $cart ) ) { continue; @@ -926,6 +929,8 @@ class WC_Subscriptions_Coupon { /** * Restores discount coupons which had been removed for special subscription calculations. * + * @param WC_Cart $cart The cart object. + * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.3.5 */ public static function restore_coupons( $cart ) { diff --git a/includes/core/class-wc-subscriptions-data-copier.php b/includes/core/class-wc-subscriptions-data-copier.php index 2d8e13a..acd2853 100644 --- a/includes/core/class-wc-subscriptions-data-copier.php +++ b/includes/core/class-wc-subscriptions-data-copier.php @@ -157,6 +157,7 @@ class WC_Subscriptions_Data_Copier { * } * @param WC_Order $from_object The object to copy data from. * @param WC_Order $to_object The object to copy data to. + * @param string $copy_type The type of copy. Can be 'subscription', 'parent', 'renewal_order' or 'resubscribe_order'. */ $data = apply_filters( 'wc_subscriptions_object_data', $data, $this->to_object, $this->from_object, $this->copy_type ); @@ -376,6 +377,9 @@ class WC_Subscriptions_Data_Copier { } if ( $this->has_filter_on_meta_query_hook() ) { + $data_copier_to_object = $this->to_object; + $data_copier_from_object = $this->from_object; + /** * Filters the data to be copied from one object to another. * @@ -388,10 +392,10 @@ class WC_Subscriptions_Data_Copier { * @deprecated subscriptions-core 2.5.0 * * @param string $meta_query The SQL query to fetch the meta data to be copied. - * @param WC_Order $this->to_object The object to copy data to. - * @param WC_Order $this->from_object The object to copy data from. + * @param WC_Order $data_copier_to_object The object to copy data to. + * @param WC_Order $data_copier_from_object The object to copy data from. */ - $meta_query = apply_filters( "wcs_{$this->copy_type}_meta_query", $meta_query, $this->to_object, $this->from_object ); + $meta_query = apply_filters( "wcs_{$this->copy_type}_meta_query", $meta_query, $data_copier_to_object, $data_copier_from_object ); wcs_deprecated_hook( "wcs_{$this->copy_type}_meta_query", 'subscriptions-core 2.5.0', "wc_subscriptions_{$this->copy_type}_data" ); } @@ -424,6 +428,9 @@ class WC_Subscriptions_Data_Copier { wcs_deprecated_hook( "wcs_{$this->copy_type}_meta", 'wcs-core 2.5.0', "wc_subscriptions_{$this->copy_type}_data" ); + $data_copier_to_object = $this->to_object; + $data_copier_from_object = $this->from_object; + /** * Filters the data to be copied from one object to another. * @@ -445,10 +452,10 @@ class WC_Subscriptions_Data_Copier { * @type mixed $meta_value The meta value to be copied. * } * } - * @param WC_Order $this->to_object The object to copy data to. - * @param WC_Order $this->from_object The object to copy data from. + * @param WC_Order $data_copier_to_object The object to copy data to. + * @param WC_Order $data_copier_from_object The object to copy data from. */ - $data_array = apply_filters( "wcs_{$this->copy_type}_meta", $data_array, $this->to_object, $this->from_object ); + $data_array = apply_filters( "wcs_{$this->copy_type}_meta", $data_array, $data_copier_to_object, $data_copier_from_object ); // Return the data to a key => value format. return wp_list_pluck( $data_array, 'meta_value', 'meta_key' ); diff --git a/includes/core/class-wc-subscriptions-email-notifications.php b/includes/core/class-wc-subscriptions-email-notifications.php index f5c22dd..8a375ca 100644 --- a/includes/core/class-wc-subscriptions-email-notifications.php +++ b/includes/core/class-wc-subscriptions-email-notifications.php @@ -54,10 +54,10 @@ class WC_Subscriptions_Email_Notifications { add_filter( 'woocommerce_subscription_settings', [ __CLASS__, 'add_settings' ], 20 ); // Bump settings update time whenever related options change. - add_action( 'update_option_' . WC_Subscriptions_Admin::$option_prefix . self::$offset_setting_string, [ __CLASS__, 'set_notification_settings_update_time' ], 10, 3 ); - add_action( 'update_option_' . WC_Subscriptions_Admin::$option_prefix . self::$switch_setting_string, [ __CLASS__, 'set_notification_settings_update_time' ], 10, 3 ); - add_action( 'add_option_' . WC_Subscriptions_Admin::$option_prefix . self::$offset_setting_string, [ __CLASS__, 'set_notification_settings_update_time' ], 10, 2 ); - add_action( 'add_option_' . WC_Subscriptions_Admin::$option_prefix . self::$switch_setting_string, [ __CLASS__, 'set_notification_settings_update_time' ], 10, 2 ); + add_action( 'update_option_' . WC_Subscriptions_Admin::$option_prefix . self::$offset_setting_string, [ __CLASS__, 'set_notification_settings_update_time' ], 10 ); + add_action( 'update_option_' . WC_Subscriptions_Admin::$option_prefix . self::$switch_setting_string, [ __CLASS__, 'set_notification_settings_update_time' ], 10 ); + add_action( 'add_option_' . WC_Subscriptions_Admin::$option_prefix . self::$offset_setting_string, [ __CLASS__, 'set_notification_settings_update_time' ], 10 ); + add_action( 'add_option_' . WC_Subscriptions_Admin::$option_prefix . self::$switch_setting_string, [ __CLASS__, 'set_notification_settings_update_time' ], 10 ); } /** diff --git a/includes/core/class-wc-subscriptions-email.php b/includes/core/class-wc-subscriptions-email.php index 13d96c5..45240d6 100644 --- a/includes/core/class-wc-subscriptions-email.php +++ b/includes/core/class-wc-subscriptions-email.php @@ -45,7 +45,7 @@ class WC_Subscriptions_Email { add_action( 'woocommerce_subscriptions_email_order_details', __CLASS__ . '::order_download_details', 10, 4 ); add_action( 'woocommerce_subscriptions_email_order_details', __CLASS__ . '::order_details', 10, 4 ); - add_action( 'woocommerce_subscription_status_pending-cancel_to_active', __CLASS__ . '::maybe_clear_cancelled_email_flag', 10, 4 ); + add_action( 'woocommerce_subscription_status_pending-cancel_to_active', __CLASS__ . '::maybe_clear_cancelled_email_flag', 10 ); } /** @@ -102,9 +102,9 @@ class WC_Subscriptions_Email { return; } - add_action( 'woocommerce_subscription_status_updated', __CLASS__ . '::send_cancelled_email', 10, 2 ); - add_action( 'woocommerce_subscription_status_expired', __CLASS__ . '::send_expired_email', 10, 2 ); - add_action( 'woocommerce_customer_changed_subscription_to_on-hold', __CLASS__ . '::send_on_hold_email', 10, 2 ); + add_action( 'woocommerce_subscription_status_updated', __CLASS__ . '::send_cancelled_email', 10 ); + add_action( 'woocommerce_subscription_status_expired', __CLASS__ . '::send_expired_email', 10 ); + add_action( 'woocommerce_customer_changed_subscription_to_on-hold', __CLASS__ . '::send_on_hold_email', 10 ); add_action( 'woocommerce_subscriptions_switch_completed', __CLASS__ . '::send_switch_order_email', 10 ); foreach ( $order_email_actions as $action ) { @@ -268,7 +268,7 @@ class WC_Subscriptions_Email { * @param WC_Order $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 WC_Email $email + * @param string $email * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.1 */ public static function order_details( $order, $sent_to_admin = false, $plain_text = false, $email = '' ) { @@ -336,8 +336,8 @@ class WC_Subscriptions_Email { /** * Detach WC transactional emails from a specific hook. * - * @param string Optional. The action hook or filter to detach WC core's transactional emails from. Defaults to the current filter. - * @param int Optional. The priority the function runs on. Default 10. + * @param string $hook Optional. The action hook or filter to detach WC core's transactional emails from. Defaults to the current filter. + * @param int $priority Optional. The priority the function runs on. Default 10. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.3 */ public static function detach_woocommerce_transactional_email( $hook = '', $priority = 10 ) { @@ -354,9 +354,9 @@ class WC_Subscriptions_Email { /** * Attach WC transactional emails to a specific hook. * - * @param string Optional. The action hook or filter to attach WC core's transactional emails to. Defaults to the current filter. - * @param int Optional. The priority the function should run on. Default 10. - * @param int Optional. The number of arguments the function accepts. Default 10. + * @param string $hook Optional. The action hook or filter to attach WC core's transactional emails to. Defaults to the current filter. + * @param int $priority Optional. The priority the function should run on. Default 10. + * @param int $accepted_args Optional. The number of arguments the function accepts. Default 10. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.3 */ public static function attach_woocommerce_transactional_email( $hook = '', $priority = 10, $accepted_args = 10 ) { @@ -378,7 +378,7 @@ class WC_Subscriptions_Email { * @param WC_Order $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 WC_Email $email + * @param string $email * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.17 */ public static function order_download_details( $order, $sent_to_admin = false, $plain_text = false, $email = '' ) { diff --git a/includes/core/class-wc-subscriptions-extend-store-endpoint.php b/includes/core/class-wc-subscriptions-extend-store-endpoint.php index 4cd73a0..3df0371 100644 --- a/includes/core/class-wc-subscriptions-extend-store-endpoint.php +++ b/includes/core/class-wc-subscriptions-extend-store-endpoint.php @@ -53,8 +53,11 @@ class WC_Subscriptions_Extend_Store_Endpoint { return; } + // @phpstan-ignore class.notFound self::$schema = class_exists( 'Automattic\WooCommerce\StoreApi\StoreApi' ) ? Automattic\WooCommerce\StoreApi\StoreApi::container()->get( Automattic\WooCommerce\StoreApi\SchemaController::class ) : Package::container()->get( Automattic\WooCommerce\Blocks\StoreApi\SchemaController::class ); + // @phpstan-ignore class.notFound self::$money_formatter = function_exists( 'woocommerce_store_api_get_formatter' ) ? woocommerce_store_api_get_formatter( 'money' ) : Package::container()->get( ExtendRestApi::class )->get_formatter( 'money' ); + // @phpstan-ignore class.notFound self::$currency_formatter = function_exists( 'woocommerce_store_api_get_formatter' ) ? woocommerce_store_api_get_formatter( 'currency' ) : Package::container()->get( ExtendRestApi::class )->get_formatter( 'currency' ); self::extend_store(); } @@ -68,6 +71,7 @@ class WC_Subscriptions_Extend_Store_Endpoint { if ( function_exists( 'woocommerce_store_api_register_endpoint_data' ) ) { woocommerce_store_api_register_endpoint_data( $args ); } else { + // @phpstan-ignore class.notFound Package::container()->get( ExtendRestApi::class )->register_endpoint_data( $args ); } } @@ -81,6 +85,7 @@ class WC_Subscriptions_Extend_Store_Endpoint { if ( function_exists( 'woocommerce_store_api_register_payment_requirements' ) ) { woocommerce_store_api_register_payment_requirements( $args ); } else { + // @phpstan-ignore class.notFound Package::container()->get( ExtendRestApi::class )->register_payment_requirements( $args ); } } @@ -92,6 +97,7 @@ class WC_Subscriptions_Extend_Store_Endpoint { // Register into `cart/items` self::register_endpoint_data( array( + // @phpstan-ignore class.notFound 'endpoint' => CartItemSchema::IDENTIFIER, 'namespace' => self::IDENTIFIER, 'data_callback' => array( 'WC_Subscriptions_Extend_Store_Endpoint', 'extend_cart_item_data' ), @@ -103,6 +109,7 @@ class WC_Subscriptions_Extend_Store_Endpoint { // Register into `cart` self::register_endpoint_data( array( + // @phpstan-ignore class.notFound 'endpoint' => CartSchema::IDENTIFIER, 'namespace' => self::IDENTIFIER, 'data_callback' => array( 'WC_Subscriptions_Extend_Store_Endpoint', 'extend_cart_data' ), @@ -266,7 +273,7 @@ class WC_Subscriptions_Extend_Store_Endpoint { // Add extra package data to array. if ( count( $packages ) ) { $packages = array_map( - function( $key, $package, $index ) use ( $cart, $cart_key ) { + function( $key, $package, $index ) use ( $cart ) { $package['package_id'] = isset( $package['package_id'] ) ? $package['package_id'] : $key; $package['package_name'] = isset( $package['package_name'] ) ? $package['package_name'] : self::get_shipping_package_name( $package, $cart ); return $package; diff --git a/includes/core/class-wc-subscriptions-manager.php b/includes/core/class-wc-subscriptions-manager.php index 1561930..3f7e12c 100644 --- a/includes/core/class-wc-subscriptions-manager.php +++ b/includes/core/class-wc-subscriptions-manager.php @@ -284,9 +284,10 @@ class WC_Subscriptions_Manager { * function directly, do not call this function also. * * @param WC_Order|int $order The order or ID of the order for which subscription payments should be marked against. + * @param int $product_id The ID of the product. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0 */ - public static function process_subscription_payments_on_order( $order, $product_id = '' ) { + public static function process_subscription_payments_on_order( $order, $product_id = 0 ) { wcs_deprecated_function( __METHOD__, '2.6.0' ); $subscriptions = wcs_get_subscriptions_for_order( $order ); @@ -307,9 +308,10 @@ class WC_Subscriptions_Manager { * function directly, do not call this function also. * * @param int|WC_Order $order The order or ID of the order for which subscription payments should be marked against. + * @param int $product_id The ID of the product. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0 */ - public static function process_subscription_payment_failure_on_order( $order, $product_id = '' ) { + public static function process_subscription_payment_failure_on_order( $order, $product_id = 0 ) { wcs_deprecated_function( __METHOD__, '2.6.0' ); $subscriptions = wcs_get_subscriptions_for_order( $order ); @@ -364,7 +366,7 @@ class WC_Subscriptions_Manager { foreach ( $subscriptions as $subscription ) { try { - if ( ! $subscription->has_status( wcs_get_subscription_ended_statuses() ) ) { + if ( $subscription->can_be_updated_to( 'on-hold' ) ) { $subscription->update_status( 'on-hold' ); } } catch ( Exception $e ) { @@ -443,30 +445,39 @@ class WC_Subscriptions_Manager { $subscriptions = wcs_get_subscriptions_for_order( $order, array( 'order_type' => 'parent' ) ); - if ( ! empty( $subscriptions ) ) { - - if ( ! is_object( $order ) ) { - $order = wc_get_order( $order ); - } - - // Set subscription status to failed and log failure - if ( $order->has_status( 'failed' ) ) { - $order->update_status( 'failed', __( 'Subscription sign up failed.', 'woocommerce-subscriptions' ) ); - } - - foreach ( $subscriptions as $subscription ) { - - try { - $subscription->payment_failed(); - - } catch ( Exception $e ) { - // translators: $1: order number, $2: error message - $subscription->add_order_note( sprintf( __( 'Failed to process failed payment on subscription for order #%1$s: %2$s', 'woocommerce-subscriptions' ), is_object( $order ) ? $order->get_order_number() : $order, $e->getMessage() ) ); - } - } - - do_action( 'failed_subscription_sign_ups_for_order', $order ); + if ( empty( $subscriptions ) ) { + return; } + + if ( ! is_object( $order ) ) { + $order = wc_get_order( $order ); + } + + // Set subscription status to failed and log failure + if ( $order->has_status( 'failed' ) ) { + $order->update_status( 'failed', __( 'Subscription sign up failed.', 'woocommerce-subscriptions' ) ); + } + + foreach ( $subscriptions as $subscription ) { + + try { + $new_status = $subscription->has_status( 'pending' ) && $subscription->get_parent_id() === $order->get_id() ? 'pending' : 'on-hold'; + $subscription->payment_failed( $new_status ); + + } catch ( Exception $e ) { + // translators: $1: order number, $2: error message + $subscription->add_order_note( sprintf( __( 'Failed to process failed payment on subscription for order #%1$s: %2$s', 'woocommerce-subscriptions' ), is_object( $order ) ? $order->get_order_number() : $order, $e->getMessage() ) ); + } + } + + /** + * Fires after an order is marked as failed. + * + * @param WC_Order $order Failed order object. + * + * @since 1.5.0 + */ + do_action( 'failed_subscription_sign_ups_for_order', $order ); } /** @@ -633,7 +644,7 @@ class WC_Subscriptions_Manager { public static function process_subscriptions_on_checkout( $order ) { _deprecated_function( __METHOD__, '2.0', 'WC_Subscriptions_Checkout::process_checkout()' ); - if ( ! empty( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'woocommerce-process_checkout' ) ) { + if ( ! empty( $_POST['_wpnonce'] ) && wp_verify_nonce( wc_clean( wp_unslash( $_POST['_wpnonce'] ) ), 'woocommerce-process_checkout' ) ) { WC_Subscriptions_Checkout::process_checkout( $order, $_POST ); } } @@ -675,6 +686,7 @@ class WC_Subscriptions_Manager { case 'pending': _deprecated_argument( __METHOD__, '2.0', 'The "pending" status value is deprecated.' ); default: + // @phpstan-ignore arguments.count self::create_pending_subscription_for_order( $order ); break; } @@ -1083,7 +1095,7 @@ class WC_Subscriptions_Manager { * @param int $user_id The ID of the user who owns the subscriptions. Although this parameter is optional, if you have the User ID you should pass it to improve performance. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0 */ - public static function can_subscription_be_changed_to( $new_status_or_meta, $subscription_key, $user_id = '' ) { + public static function can_subscription_be_changed_to( $new_status_or_meta, $subscription_key, $user_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'WC_Subscription::can_be_updated_to( $new_status_or_meta )' ); @@ -1127,7 +1139,7 @@ class WC_Subscriptions_Manager { * Return an associative array of a given subscriptions details (if it exists). * * @param string $subscription_key A subscription key of the form created by @see self::get_subscription_key() - * @param deprecated don't use + * @param mixed $deprecated Don't use * @return array Subscription details * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.1 */ @@ -1217,7 +1229,7 @@ class WC_Subscriptions_Manager { * M – for months; allowable range is 1 to 24 * Y – for years; allowable range is 1 to 5 * - * @param subscription_period string (optional) One of day, week, month or year. If empty, all subscription ranges are returned. + * @param string $subscription_period string (optional) One of day, week, month or year. If empty, all subscription ranges are returned. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ @@ -1230,7 +1242,7 @@ class WC_Subscriptions_Manager { * Returns an array of allowable trial periods. * * @see self::get_subscription_ranges() - * @param subscription_period string (optional) One of day, week, month or year. If empty, all subscription ranges are returned. + * @param string $subscription_period string (optional) One of day, week, month or year. If empty, all subscription ranges are returned. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.1 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ @@ -1265,12 +1277,12 @@ class WC_Subscriptions_Manager { /** * Returns the string key for a subscription purchased in an order specified by $order_id * - * @param order_id int The ID of the order in which the subscription was purchased. - * @param product_id int The ID of the subscription product. + * @param int $order_id The ID of the order in which the subscription was purchased. + * @param int $product_id The ID of the subscription product. * @return string The key representing the given subscription. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0 */ - public static function get_subscription_key( $order_id, $product_id = '' ) { + public static function get_subscription_key( $order_id, $product_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'wcs_get_old_subscription_key( WC_Subscription $subscription )' ); @@ -1313,7 +1325,7 @@ class WC_Subscriptions_Manager { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ - public static function get_subscriptions_failed_payment_count( $subscription_key, $user_id = '' ) { + public static function get_subscriptions_failed_payment_count( $subscription_key, $user_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'WC_Subscription::get_failed_payment_count()' ); return apply_filters( 'woocommerce_subscription_failed_payment_count', wcs_get_subscription_from_key( $subscription_key )->get_failed_payment_count(), $user_id, $subscription_key ); } @@ -1322,7 +1334,6 @@ class WC_Subscriptions_Manager { * Returns the number of completed payments for a given subscription (including the initial payment). * * @param string $subscription_key A subscription key of the form created by @see self::get_subscription_key() - * @param int $user_id The ID of the user who owns the subscriptions. Although this parameter is optional, if you have the User ID you should pass it to improve performance. * @return int The number of outstanding failed payments on the subscription, if any. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.4 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 @@ -1342,7 +1353,7 @@ class WC_Subscriptions_Manager { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.1 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ - public static function get_subscription_expiration_date( $subscription_key, $user_id = '', $type = 'mysql' ) { + public static function get_subscription_expiration_date( $subscription_key, $user_id = 0, $type = 'mysql' ) { _deprecated_function( __METHOD__, '2.0', 'WC_Subscription::get_date( "end" )' ); $subscription = wcs_get_subscription_from_key( $subscription_key ); $expiration_date = ( 'mysql' == $type ) ? $subscription->get_date( 'end' ) : $subscription->get_time( 'end' ); @@ -1354,12 +1365,12 @@ class WC_Subscriptions_Manager { * * @param string $subscription_key A subscription key of the form created by @see self::get_subscription_key() * @param int $user_id (optional) The ID of the user who owns the subscriptions. Although this parameter is optional, if you have the User ID you should pass it to improve performance. - * @param (optional) $next_payment string | int The date and time the next payment is due, either as MySQL formatted datetime string or a Unix timestamp. If empty, @see self::calculate_subscription_expiration_date() will be called. + * @param string|int $expiration_date (optional)The date and time the subscription will expire, either as MySQL formatted datetime string or a Unix timestamp. If empty, @see self::calculate_subscription_expiration_date() will be called. * @return mixed If the expiration does not get set, returns false, otherwise it will return a MySQL datetime formatted string for the new date when the subscription will expire * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2.4 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ - public static function set_expiration_date( $subscription_key, $user_id = '', $expiration_date = '' ) { + public static function set_expiration_date( $subscription_key, $user_id = 0, $expiration_date = '' ) { _deprecated_function( __METHOD__, '2.0', 'WC_Subscription::update_dates( array( "end" => $expiration_date ) )' ); if ( is_int( $expiration_date ) ) { $expiration_date = gmdate( 'Y-m-d H:i:s', $expiration_date ); @@ -1382,7 +1393,7 @@ class WC_Subscriptions_Manager { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.1 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ - public static function calculate_subscription_expiration_date( $subscription_key, $user_id = '', $type = 'mysql' ) { + public static function calculate_subscription_expiration_date( $subscription_key, $user_id = 0, $type = 'mysql' ) { _deprecated_function( __METHOD__, '2.0', 'WC_Subscription::get_date( "end" )' ); $subscription = wcs_get_subscription_from_key( $subscription_key ); $expiration_date = ( 'mysql' == $type ) ? $subscription->get_date( 'end' ) : $subscription->get_time( 'end' ); @@ -1399,7 +1410,7 @@ class WC_Subscriptions_Manager { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ - public static function get_next_payment_date( $subscription_key, $user_id = '', $type = 'mysql' ) { + public static function get_next_payment_date( $subscription_key, $user_id = 0, $type = 'mysql' ) { _deprecated_function( __METHOD__, '2.0', 'WC_Subscription::get_date( "next_payment" )' ); $subscription = wcs_get_subscription_from_key( $subscription_key ); $next_payment = ( 'mysql' == $type ) ? $subscription->get_date( 'next_payment' ) : $subscription->get_time( 'next_payment' ); @@ -1414,12 +1425,12 @@ class WC_Subscriptions_Manager { * * @param string $subscription_key A subscription key of the form created by @see self::get_subscription_key() * @param int $user_id (optional) The ID of the user who owns the subscriptions. Although this parameter is optional, if you have the User ID you should pass it to improve performance. - * @param (optional) $next_payment string | int The date and time the next payment is due, either as MySQL formatted datetime string or a Unix timestamp. If empty, @see self::calculate_next_payment_date() will be called. + * @param string|int $next_payment (optional) The date and time the next payment is due, either as MySQL formatted datetime string or a Unix timestamp. If empty, @see self::calculate_next_payment_date() will be called. * @return mixed If there is no future payment set, returns 0, otherwise it will return a MySQL datetime formatted string for the date of the next payment * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ - public static function set_next_payment_date( $subscription_key, $user_id = '', $next_payment = '' ) { + public static function set_next_payment_date( $subscription_key, $user_id = 0, $next_payment = '' ) { _deprecated_function( __METHOD__, '2.0', 'WC_Subscription::update_dates( array( "next_payment" => $next_payment ) )' ); if ( is_int( $next_payment ) ) { @@ -1441,7 +1452,7 @@ class WC_Subscriptions_Manager { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ - public static function get_last_payment_date( $subscription_key, $user_id = '', $type = 'mysql' ) { + public static function get_last_payment_date( $subscription_key, $user_id = 0, $type = 'mysql' ) { _deprecated_function( __METHOD__, '2.0', 'WC_Subscription::get_date( "last_payment" )' ); $subscription = wcs_get_subscription_from_key( $subscription_key ); $last_payment_date = ( 'mysql' == $type ) ? $subscription->get_date( 'last_order_date_created' ) : $subscription->get_time( 'last_order_date_created' ); @@ -1457,7 +1468,7 @@ class WC_Subscriptions_Manager { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ - public static function update_wp_cron_lock( $subscription_key, $lock_time, $user_id = '' ) { + public static function update_wp_cron_lock( $subscription_key, $lock_time, $user_id = 0 ) { _deprecated_function( __METHOD__, '2.0' ); } @@ -1471,7 +1482,7 @@ class WC_Subscriptions_Manager { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ - public static function calculate_next_payment_date( $subscription_key, $user_id = '', $type = 'mysql', $from_date = '' ) { + public static function calculate_next_payment_date( $subscription_key, $user_id = 0, $type = 'mysql', $from_date = '' ) { _deprecated_function( __METHOD__, '2.0', 'WC_Subscription::calculate_date( "next_payment" )' ); $subscription = wcs_get_subscription_from_key( $subscription_key ); $next_payment = $subscription->calculate_date( 'next_payment' ); @@ -1486,7 +1497,7 @@ class WC_Subscriptions_Manager { * @return mixed If the subscription has no trial period, returns 0, otherwise it will return the date the trial period ends or ended in the form specified by $type * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 */ - public static function get_trial_expiration_date( $subscription_key, $user_id = '', $type = 'mysql' ) { + public static function get_trial_expiration_date( $subscription_key, $user_id = 0, $type = 'mysql' ) { _deprecated_function( __METHOD__, '2.0', 'WC_Subscription::get_date( "trial_end" )' ); $subscription = wcs_get_subscription_from_key( $subscription_key ); $trial_end_date = ( 'mysql' == $type ) ? $subscription->get_date( 'trial_end' ) : $subscription->get_time( 'trial_end' ); @@ -1498,11 +1509,11 @@ class WC_Subscriptions_Manager { * * @param string $subscription_key A subscription key of the form created by @see self::get_subscription_key() * @param int $user_id (optional) The ID of the user who owns the subscription. Although this parameter is optional, if you have the User ID you should pass it to improve performance. - * @param (optional) $next_payment string | int The date and time the next payment is due, either as MySQL formatted datetime string or a Unix timestamp. If empty, @see self::calculate_next_payment_date() will be called. + * @param string|int $trial_expiration_date (optional) The date and time the trial will expire, either as MySQL formatted datetime string or a Unix timestamp. If empty, @see self::calculate_next_payment_date() will be called. * @return mixed If the trial expiration does not get set, returns false, otherwise it will return a MySQL datetime formatted string for the new date when the trial will expire * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2.4 */ - public static function set_trial_expiration_date( $subscription_key, $user_id = '', $trial_expiration_date = '' ) { + public static function set_trial_expiration_date( $subscription_key, $user_id = 0, $trial_expiration_date = '' ) { _deprecated_function( __METHOD__, '2.0', 'WC_Subscription::update_dates( array( "trial_end" => $expiration_date ) )' ); if ( is_int( $trial_expiration_date ) ) { $trial_expiration_date = gmdate( 'Y-m-d H:i:s', $trial_expiration_date ); @@ -1520,7 +1531,7 @@ class WC_Subscriptions_Manager { * @param string $type (optional) The format for the Either 'mysql' or 'timestamp'. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.1 */ - public static function calculate_trial_expiration_date( $subscription_key, $user_id = '', $type = 'mysql' ) { + public static function calculate_trial_expiration_date( $subscription_key, $user_id = 0, $type = 'mysql' ) { _deprecated_function( __METHOD__, '2.0', 'WC_Subscription::calculate_date( "trial_end" )' ); $subscription = wcs_get_subscription_from_key( $subscription_key ); $trial_end = $subscription->calculate_date( 'trial_end' ); @@ -1551,7 +1562,7 @@ class WC_Subscriptions_Manager { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ - public static function requires_manual_renewal( $subscription_key, $user_id = '' ) { + public static function requires_manual_renewal( $subscription_key, $user_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'WC_Subscription::is_manual()' ); return wcs_get_subscription_from_key( $subscription_key )->is_manual(); } @@ -1588,7 +1599,7 @@ class WC_Subscriptions_Manager { public static function user_owns_subscription( $subscription_key, $user_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'WC_Subscriptions::get_user_id()' ); - if ( 0 === $user_id || empty( $user_id ) ) { + if ( 0 === $user_id ) { $user_id = get_current_user_id(); } @@ -1607,12 +1618,12 @@ class WC_Subscriptions_Manager { * Check if a user has a subscription, optionally specified with $product_id. * * @param int $user_id (optional) The id of the user whose subscriptions you want. Defaults to the currently logged in user. - * @param product_id int (optional) The ID of a subscription product. - * @param status string (optional) A subscription status to check against. For example, for a $status of 'active', a subscriber must have an active subscription for a return value of true. + * @param int $product_id (optional) The ID of a subscription product. + * @param string $status (optional) A subscription status to check against. For example, for a $status of 'active', a subscriber must have an active subscription for a return value of true. * @return bool True if the user has the subscription (or any subscription if no subscription specified), otherwise false. * @version 1.0.0 - Migrated from WooCommerce Subscriptions v1.3.5 */ - public static function user_has_subscription( $user_id = 0, $product_id = '', $status = 'any' ) { + public static function user_has_subscription( $user_id = 0, $product_id = 0, $status = 'any' ) { _deprecated_function( __METHOD__, '2.0', 'wcs_user_has_subscription()' ); return wcs_user_has_subscription( $user_id, $product_id, $status ); } @@ -1625,6 +1636,7 @@ class WC_Subscriptions_Manager { */ public static function get_all_users_subscriptions() { _deprecated_function( __METHOD__, '2.0' ); + $subscriptions_in_old_format = array(); foreach ( get_users() as $user ) { foreach ( wcs_get_users_subscriptions( $user->ID ) as $subscription ) { @@ -1660,7 +1672,7 @@ class WC_Subscriptions_Manager { * @param int $user_id (optional) The id of the user whose subscriptions you want. Defaults to the currently logged in user. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0 */ - public static function get_users_trashed_subscriptions( $user_id = '' ) { + public static function get_users_trashed_subscriptions( $user_id = 0 ) { $subscriptions = self::get_users_subscriptions( $user_id ); @@ -1717,7 +1729,7 @@ class WC_Subscriptions_Manager { * * A wrapper for the @see woocommerce_paying_customer() function. * - * @param int $order_id The id of the order for which customers should be pulled from and marked as paying. + * @param int $order The order for which customers should be pulled from and marked as paying. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ @@ -1789,7 +1801,7 @@ class WC_Subscriptions_Manager { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ - public static function update_next_payment_date( $new_payment_date, $subscription_key, $user_id = '', $timezone = 'server' ) { + public static function update_next_payment_date( $new_payment_date, $subscription_key, $user_id = 0, $timezone = 'server' ) { _deprecated_function( __METHOD__, '2.0', 'WC_Subscription::update_dates( array( "next_payment" => $new_payment_date ) )' ); $new_payment_timestamp = ( is_numeric( $new_payment_date ) ) ? $new_payment_date : wcs_date_to_time( $new_payment_date ); @@ -2317,7 +2329,7 @@ class WC_Subscriptions_Manager { $subscription->update_status( 'cancelled' ); } - wp_trash_post( $subscription->get_id(), true ); + wp_trash_post( $subscription->get_id() ); do_action( 'subscription_trashed', $user_id, $subscription_key ); } @@ -2352,7 +2364,7 @@ class WC_Subscriptions_Manager { wp_delete_post( $subscription->get_id(), true ); - do_action( 'subscription_deleted', $user_id, $subscription_key, $subscription, $item ); + do_action( 'subscription_deleted', $user_id, $subscription_key, $subscription, null ); } } @@ -2370,7 +2382,7 @@ class WC_Subscriptions_Manager { $response = array( 'status' => 'error' ); - if ( ! wp_verify_nonce( $_POST['wcs_nonce'], 'woocommerce-subscriptions' ) ) { + if ( ! wp_verify_nonce( wc_clean( wp_unslash( $_POST['wcs_nonce'] ) ), 'woocommerce-subscriptions' ) ) { $response['message'] = '
' . __( 'Invalid security token, please reload the page and try again.', 'woocommerce-subscriptions' ) . '
'; @@ -2385,7 +2397,7 @@ class WC_Subscriptions_Manager { } else { $new_payment_date = sprintf( '%s-%s-%s %s', (int) $_POST['wcs_year'], zeroise( (int) $_POST['wcs_month'], 2 ), zeroise( (int) $_POST['wcs_day'], 2 ), gmdate( 'H:i:s', current_time( 'timestamp' ) ) ); - $new_payment_timestamp = self::update_next_payment_date( $new_payment_date, $_POST['wcs_subscription_key'], self::get_user_id_from_subscription_key( $_POST['wcs_subscription_key'] ), 'user' ); + $new_payment_timestamp = self::update_next_payment_date( $new_payment_date, wc_clean( wp_unslash( $_POST['wcs_subscription_key'] ) ), self::get_user_id_from_subscription_key( wc_clean( wp_unslash( $_POST['wcs_subscription_key'] ) ) ), 'user' ); if ( is_wp_error( $new_payment_timestamp ) ) { @@ -2412,6 +2424,7 @@ class WC_Subscriptions_Manager { } } + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wcs_json_encode( $response ); exit(); @@ -2462,7 +2475,7 @@ class WC_Subscriptions_Manager { $subscription = wcs_get_subscription_from_key( $subscription_key ); // Don't reschedule for cancelled, suspended or expired subscriptions - if ( ! $subscription->has_status( 'expired', 'cancelled', 'on-hold' ) ) { + if ( ! $subscription->has_status( [ 'expired', 'cancelled', 'on-hold' ] ) ) { // Reschedule the 'scheduled_subscription_payment' hook if ( $subscription->can_date_be_updated( 'next_payment' ) ) { diff --git a/includes/core/class-wc-subscriptions-order.php b/includes/core/class-wc-subscriptions-order.php index 486889c..be92fac 100644 --- a/includes/core/class-wc-subscriptions-order.php +++ b/includes/core/class-wc-subscriptions-order.php @@ -106,7 +106,7 @@ class WC_Subscriptions_Order { * This may return 0 if there no non-subscription products in the cart, or otherwise it will be the sum of the * line totals for each non-subscription product. * - * @param mixed $order A WC_Order object or the ID of the order which the subscription was purchased in. + * @param WC_Order|int $order A WC_Order object or the ID of the order which the subscription was purchased in. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.5.3 */ public static function get_non_subscription_total( $order ) { @@ -136,7 +136,7 @@ class WC_Subscriptions_Order { * @return float The initial sign-up fee charged when the subscription product in the order was first purchased, if any. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0 */ - public static function get_sign_up_fee( $order, $product_id = '' ) { + public static function get_sign_up_fee( $order, $product_id = 0 ) { $sign_up_fee = 0; @@ -182,7 +182,7 @@ class WC_Subscriptions_Order { * @param int $product_id The product/post ID of a subscription product. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2.5 */ - public static function get_item_by_product_id( $order, $product_id = '' ) { + public static function get_item_by_product_id( $order, $product_id = 0 ) { if ( ! is_object( $order ) ) { $order = wc_get_order( $order ); @@ -200,8 +200,7 @@ class WC_Subscriptions_Order { /** * Gets an item by a subscription key of the form created by @see WC_Subscriptions_Manager::get_subscription_key(). * - * @param WC_Order|int $order The WC_Order object or ID of the order for which the meta should be sought. - * @param int $product_id The product/post ID of a subscription product. + * @param string $subscription_key The subscription key. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2.5 */ public static function get_item_by_subscription_key( $subscription_key ) { @@ -217,8 +216,7 @@ class WC_Subscriptions_Order { * Gets the ID of a subscription item which belongs to a subscription key of the form created * by @see WC_Subscriptions_Manager::get_subscription_key(). * - * @param WC_Order|int $order The WC_Order object or ID of the order for which the meta should be sought. - * @param int $product_id The product/post ID of a subscription product. + * @param string $subscription_key The subscription key. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.4 */ public static function get_item_id_by_subscription_key( $subscription_key ) { @@ -242,9 +240,7 @@ class WC_Subscriptions_Order { /** * Gets an individual order item by ID without requiring the order ID associated with it. * - * @param WC_Order|int $order The WC_Order object or ID of the order for which the meta should be sought. - * @param int $item_id The product/post ID of a subscription. Option - if no product id is provided, the first item's meta will be returned - * @return array $item An array containing the order_item_id, order_item_name, order_item_type, order_id and any item_meta. Array structure matches that returned by WC_Order::get_items() + * @param int $order_item_id The product/post ID of a subscription. Option - if no product id is provided, the first item's meta will be returned * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2.5 */ public static function get_item_by_id( $order_item_id ) { @@ -282,7 +278,7 @@ class WC_Subscriptions_Order { * @param mixed $default (optional) The default value to return if the meta key does not exist. Default 0. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 */ - public static function get_item_meta( $order, $meta_key, $product_id = '', $default = 0 ) { + public static function get_item_meta( $order, $meta_key, $product_id = 0, $default = 0 ) { $meta_value = $default; @@ -332,7 +328,7 @@ class WC_Subscriptions_Order { * @param int $product_id The product/post ID of a subscription. Option - if no product id is provided, it is expected that only one item exists and the last item's meta will be returned * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 */ - public static function get_item_name( $order, $product_id = '' ) { + public static function get_item_name( $order, $product_id = 0 ) { $item = self::get_item_by_product_id( $order, $product_id ); @@ -412,7 +408,7 @@ class WC_Subscriptions_Order { * Output a hidden element on the Edit Order screen to provide information about whether the order displayed * in that row contains a subscription or not. * - * @param string $column The string of the current column. + * @param string $order_id The ID of the order. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.1 */ public static function contains_subscription_hidden_field( $order_id ) { @@ -581,8 +577,10 @@ class WC_Subscriptions_Order { $subscription->payment_complete_for_order( $order ); $was_activated = true; - } elseif ( 'failed' == $new_order_status ) { - $subscription->payment_failed(); + } elseif ( 'failed' === $new_order_status ) { + // When parent order fails, we want to keep the subscription status as pending unless it had another status before. + $new_status = $subscription->has_status( 'pending' ) && $subscription->get_parent_id() === $order->get_id() ? 'pending' : 'on-hold'; + $subscription->payment_failed( $new_status ); } } @@ -597,7 +595,7 @@ class WC_Subscriptions_Order { * Checks if a given order item matches a line item from a subscription purchased in the order. * * @param WC_Order|int $order A WC_Order object or ID of a WC_Order order. - * @param array $item | int An array representing an order item or a product ID of an item in an order (not an order item ID) + * @param array $order_item An array representing an order item or a product ID of an item in an order (not an order item ID) * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 */ public static function is_item_subscription( $order, $order_item ) { @@ -1041,7 +1039,7 @@ class WC_Subscriptions_Order { * @return null|object A subscription from the order, either with an item to the product ID (if any) or just the first subscription purchase in the order. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ - private static function get_matching_subscription( $order, $product_id = '' ) { + private static function get_matching_subscription( $order, $product_id = 0 ) { $subscriptions = wcs_get_subscriptions_for_order( $order, array( 'order_type' => 'parent' ) ); $matching_subscription = null; @@ -1079,7 +1077,7 @@ class WC_Subscriptions_Order { * @return array The line item for this product on the subscription object * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ - private static function get_matching_subscription_item( $order, $product_id = '' ) { + private static function get_matching_subscription_item( $order, $product_id = 0 ) { $matching_item = array(); $subscription = self::get_matching_subscription( $order, $product_id ); @@ -1149,7 +1147,7 @@ class WC_Subscriptions_Order { /** * If the subscription is pending cancellation and a latest order is refunded, cancel the subscription. * - * @param $order_id + * @param WC_Order $order The order object. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ @@ -1423,7 +1421,7 @@ class WC_Subscriptions_Order { * @param array $query_clauses The query clauses. * @param OrdersTableQuery $order_query The order query object. * - * @return $query_clauses The modified query clauses to include/exclude parent orders. + * @return array The modified query clauses to include/exclude parent orders. */ public static function filter_orders_query_by_parent_orders( $query_clauses, $order_query ) { $include_parent_orders = $order_query->get( 'subscription_parent' ); @@ -1491,7 +1489,8 @@ class WC_Subscriptions_Order { * Deprecated because editing a subscription's values is now done from the Edit Subscription screen and those values * are stored against a 'shop_subscription' post, not the 'shop_order' used to purchase the subscription. * - * @param item_id int An order_item_id as returned by the insert statement of @see woocommerce_add_order_item() + * @param WC_Order_Item $item + * @param int $item_id An order_item_id as returned by the insert statement of @see woocommerce_add_order_item() * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2.5 * @version 1.0.0 - Migrated from WooCommerce Subscriptions v1.4 * @return void @@ -1620,10 +1619,11 @@ class WC_Subscriptions_Order { * fee and price per period. This function should be used by payment gateways for the initial payment. * * @param mixed $order A WC_Order object or the ID of the order which the subscription was purchased in. + * @param int $product_id The ID of the product. * @return float The total initial amount charged when the subscription product in the order was first purchased, if any. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.1 */ - public static function get_total_initial_payment( $order, $product_id = '' ) { + public static function get_total_initial_payment( $order, $product_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'WC_Order::get_total()' ); if ( ! is_object( $order ) ) { @@ -1659,9 +1659,10 @@ class WC_Subscriptions_Order { * Returns the proportion of cart discount that is recurring for the product specified with $product_id * * @param WC_Order|int $order A WC_Order object or ID of a WC_Order order. + * @param int $product_id The ID of the product. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 */ - public static function get_recurring_discount_cart( $order, $product_id = '' ) { + public static function get_recurring_discount_cart( $order, $product_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'the value for the subscription object rather than the value on the original order. The value is stored against the subscription since Subscriptions v2.0 as an order can be used to create multiple different subscriptions with different discounts, so use the subscription object' ); $recurring_discount_cart = 0; @@ -1689,9 +1690,10 @@ class WC_Subscriptions_Order { * Returns the proportion of cart discount tax that is recurring for the product specified with $product_id * * @param WC_Order|int $order A WC_Order object or ID of a WC_Order order. + * @param int $product_id The ID of the product. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 */ - public static function get_recurring_discount_cart_tax( $order, $product_id = '' ) { + public static function get_recurring_discount_cart_tax( $order, $product_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'the value for the subscription object rather than the value on the original order. The value is stored against the subscription since Subscriptions v2.0 as an order can be used to create multiple different subscriptions with different discounts, so use the subscription object' ); $recurring_discount_cart_tax = 0; @@ -1719,9 +1721,10 @@ class WC_Subscriptions_Order { * Returns the proportion of total discount that is recurring for the product specified with $product_id * * @param WC_Order|int $order A WC_Order object or ID of a WC_Order order. + * @param int $product_id The ID of the product. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 */ - public static function get_recurring_discount_total( $order, $product_id = '' ) { + public static function get_recurring_discount_total( $order, $product_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'the value for the subscription object rather than the value on the original order. The value is stored against the subscription since Subscriptions v2.0 as an order can be used to create multiple different subscriptions with different discounts, so use the subscription object' ); $ex_tax = ( 'excl' === get_option( 'woocommerce_tax_display_cart' ) && wcs_get_objects_property( $order, 'display_totals_ex_tax' ) ); @@ -1765,9 +1768,10 @@ class WC_Subscriptions_Order { * this is equal to @see WC_Order::get_total_tax() * * @param WC_Order|int $order A WC_Order object or ID of a WC_Order order. + * @param int $product_id The ID of the product. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 */ - public static function get_recurring_shipping_tax_total( $order, $product_id = '' ) { + public static function get_recurring_shipping_tax_total( $order, $product_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'the value for the subscription object rather than the value on the original order. The value is stored against the subscription since Subscriptions v2.0 as an order can be used to create multiple different subscriptions with different amounts, so use the subscription object' ); $recurring_shipping_tax_total = 0; @@ -1797,9 +1801,10 @@ class WC_Subscriptions_Order { * equal to @see WC_Order::get_total_shipping() * * @param WC_Order|int $order A WC_Order object or ID of a WC_Order order. + * @param int $product_id The ID of the product. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 */ - public static function get_recurring_shipping_total( $order, $product_id = '' ) { + public static function get_recurring_shipping_total( $order, $product_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'the value for the subscription object rather than the value on the original order. The value is stored against the subscription since Subscriptions v2.0 as an order can be used to create multiple different subscriptions with different amounts, so use the subscription object' ); $recurring_shipping_total = 0; @@ -1862,9 +1867,10 @@ class WC_Subscriptions_Order { * Returns the proportion of total tax on an order that is recurring for the product specified with $product_id * * @param WC_Order|int $order A WC_Order object or ID of a WC_Order order. + * @param int $product_id The ID of the product. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 */ - public static function get_recurring_total_tax( $order, $product_id = '' ) { + public static function get_recurring_total_tax( $order, $product_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'the value for the subscription object rather than the value on the original order. The value is stored against the subscription since Subscriptions v2.0 as an order can be used to create multiple different subscriptions with different amounts, so use the subscription object' ); $recurring_total_tax = 0; @@ -1892,9 +1898,10 @@ class WC_Subscriptions_Order { * Returns the proportion of total before tax on an order that is recurring for the product specified with $product_id * * @param WC_Order|int $order A WC_Order object or ID of a WC_Order order. + * @param int $product_id The ID of the product. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 */ - public static function get_recurring_total_ex_tax( $order, $product_id = '' ) { + public static function get_recurring_total_ex_tax( $order, $product_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'the value for the subscription object rather than the value on the original order. The value is stored against the subscription since Subscriptions v2.0 as an order can be used to create multiple different subscriptions with different amounts, so use the subscription object' ); return self::get_recurring_total( $order, $product_id ) - self::get_recurring_total_tax( $order, $product_id ); } @@ -1903,10 +1910,10 @@ class WC_Subscriptions_Order { * Returns the price per period for a subscription in an order. * * @param mixed $order A WC_Order object or the ID of the order which the subscription was purchased in. - * @param int $product_id (optional) The post ID of the subscription WC_Product object purchased in the order. Defaults to the ID of the first product purchased in the order. + * @param int $product_id The ID of the product. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 */ - public static function get_recurring_total( $order ) { + public static function get_recurring_total( $order, $product_id = 0 ) { $recurring_total = 0; foreach ( wcs_get_subscriptions_for_order( $order, array( 'order_type' => 'parent' ) ) as $subscription ) { @@ -1932,8 +1939,8 @@ class WC_Subscriptions_Order { * Creates a string representation of the subscription period/term for each item in the cart * * @param WC_Order $order A WC_Order object. - * @param mixed $deprecated Never used. - * @param mixed $deprecated Never used. + * @param mixed $deprecated_price Never used. + * @param mixed $deprecated_sign_up_fee Never used. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0 */ public static function get_order_subscription_string( $order, $deprecated_price = '', $deprecated_sign_up_fee = '' ) { @@ -1996,7 +2003,7 @@ class WC_Subscriptions_Order { * @return string A string representation of the period for the subscription, i.e. day, week, month or year. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0 */ - public static function get_subscription_period( $order, $product_id = '' ) { + public static function get_subscription_period( $order, $product_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'the billing period for each individual subscription object. Since Subscriptions v2.0, an order can be used to create multiple different subscriptions with different billing schedules, so use the subscription object' ); $billing_period = ''; @@ -2031,7 +2038,7 @@ class WC_Subscriptions_Order { * @return int The billing interval for a each subscription product in an order. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0 */ - public static function get_subscription_interval( $order, $product_id = '' ) { + public static function get_subscription_interval( $order, $product_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'the billing interval for each individual subscription object. Since Subscriptions v2.0, an order can be used to create multiple different subscriptions with different billing schedules, so use the subscription object' ); $billing_interval = ''; @@ -2066,7 +2073,7 @@ class WC_Subscriptions_Order { * @return int The number of periods for which the subscription will recur. For example, a $5/month subscription for one year would return 12. A $10 every 3 month subscription for one year would also return 12. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0 */ - public static function get_subscription_length( $order, $product_id = '' ) { + public static function get_subscription_length( $order, $product_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'the end date each individual subscription object. Since Subscriptions v2.0, an order can be used to create multiple different subscriptions with different billing schedules. The length of a subscription is also no longer stored against the subscription and instead, it is used simply to calculate the end date for the subscription when it is purchased. Therefore, you must use the end date of a subscription object' ); return null; } @@ -2083,7 +2090,7 @@ class WC_Subscriptions_Order { * @return int The number of periods the trial period lasts for. For no trial, this will return 0, for a 3 period trial, it will return 3. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.1 */ - public static function get_subscription_trial_length( $order, $product_id = '' ) { + public static function get_subscription_trial_length( $order, $product_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'the first payment date for each individual subscription object. Since Subscriptions v2.0, an order can be used to create multiple different subscriptions with different billing schedules. The trial length of a subscription is also no longer stored against the subscription and instead, it is used simply to calculate the first payment date for the subscription when it is purchased. Therefore, you must use the first payment date of a subscription object' ); return null; } @@ -2100,7 +2107,7 @@ class WC_Subscriptions_Order { * @return string A string representation of the period for the subscription, i.e. day, week, month or year. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 */ - public static function get_subscription_trial_period( $order, $product_id = '' ) { + public static function get_subscription_trial_period( $order, $product_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'the billing period for each individual subscription object. Since Subscriptions v2.0, an order can be used to create multiple different subscriptions with different billing schedules. The trial period of a subscription is also no longer stored against the subscription and instead, it is used simply to calculate the first payment date for the subscription when it is purchased. Therefore, you must use the billing period of a subscription object' ); return self::get_subscription_period( $order, $product_id ); } @@ -2162,10 +2169,9 @@ class WC_Subscriptions_Order { * * A convenience wrapper for @see WC_Subscriptions_Manager::get_last_payment_date() to get the next * payment date for a subscription when all you have is the order and product. - * +f * * @param mixed $order A WC_Order object or the ID of the order which the subscription was purchased in. * @param int $product_id The product/post ID of the subscription - * @param mixed $deprecated Never used. * @return mixed If no more payments are due, returns 0, otherwise it returns the MySQL formatted date/time string for the next payment date. * @version 1.0.0 - Migrated from WooCommerce Subscriptions v1.2.1 * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0 @@ -2224,7 +2230,7 @@ class WC_Subscriptions_Order { * Returns the number of failed payments for a given subscription. * * @param WC_Order $order The WC_Order object of the order for which you want to determine the number of failed payments. - * @param product_id int The ID of the subscription product. + * @param int $product_id The ID of the subscription product. * @return string The key representing the given subscription. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0 */ @@ -2247,7 +2253,7 @@ class WC_Subscriptions_Order { * than one subscription. * * @param WC_Order $order The WC_Order object of the order for which you want to determine the number of failed payments. - * @param product_id int The ID of the subscription product. + * @param int $product_id The ID of the subscription product. * @return string The key representing the given subscription. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.0 */ @@ -2264,8 +2270,7 @@ class WC_Subscriptions_Order { /** * Once payment is completed on an order, set a lock on payments until the next subscription payment period. * - * @param int $user_id The id of the user who purchased the subscription - * @param string $subscription_key A subscription key of the form created by @see WC_Subscriptions_Manager::get_subscription_key() + * @param int $order_id The id of the order. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.1.2 */ public static function safeguard_scheduled_payments( $order_id ) { @@ -2370,7 +2375,7 @@ class WC_Subscriptions_Order { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ - public static function get_item_sign_up_fee( $order, $product_id = '' ) { + public static function get_item_sign_up_fee( $order, $product_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'WC_Subscription::get_items_sign_up_fee() or WC_Subscriptions_Order::get_sign_up_fee()' ); return self::get_sign_up_fee( $order, $product_id ); } @@ -2446,6 +2451,7 @@ class WC_Subscriptions_Order { if ( isset( $order->$meta_key ) ) { // WC 2.1+ magic __isset() & __get() methods $meta_value = $order->$meta_key; + // @phpstan-ignore-next-line } elseif ( is_array( $order->order_custom_fields ) && isset( $order->order_custom_fields[ '_' . $meta_key ][0] ) && $order->order_custom_fields[ '_' . $meta_key ][0] ) { // < WC 2.1+ $meta_value = maybe_unserialize( $order->order_custom_fields[ '_' . $meta_key ][0] ); } else { @@ -2504,9 +2510,6 @@ class WC_Subscriptions_Order { foreach ( $subscription_ids as $subscription_id ) { $subscription = wcs_get_subscription( $subscription_id ); - if ( ! $subscription ) { - continue; - } self::update_subscription_last_order_date_created( $subscription, $exclude_statuses ); } @@ -2527,9 +2530,6 @@ class WC_Subscriptions_Order { } $subscription = wcs_get_subscription( $object_id ); - if ( ! $subscription ) { - return; - } self::update_subscription_last_order_date_created( $subscription ); } @@ -2541,8 +2541,17 @@ class WC_Subscriptions_Order { * @param array $exclude_statuses The order statuses to exclude. */ private static function update_subscription_last_order_date_created( $subscription, $exclude_statuses = [] ) { + if ( ! $subscription ) { + return; + } + $last_order_date_created = $subscription->get_time( 'last_order_date_created', 'gmt', $exclude_statuses ); + // If the last order date created hasn't changed, no need to update and save. + if ( $last_order_date_created === (int) $subscription->get_last_order_date_created() ) { + return; + } + $subscription->set_last_order_date_created( $last_order_date_created ); $subscription->save(); } diff --git a/includes/core/class-wc-subscriptions-product.php b/includes/core/class-wc-subscriptions-product.php index 460ba11..5c6cc20 100644 --- a/includes/core/class-wc-subscriptions-product.php +++ b/includes/core/class-wc-subscriptions-product.php @@ -61,8 +61,8 @@ class WC_Subscriptions_Product { add_action( 'wp_scheduled_delete', __CLASS__ . '::prevent_scheduled_deletion', 9 ); // Trash variations instead of deleting them to prevent headaches from deleted products - add_action( 'wp_ajax_woocommerce_remove_variation', __CLASS__ . '::remove_variations', 9, 2 ); - add_action( 'wp_ajax_woocommerce_remove_variations', __CLASS__ . '::remove_variations', 9, 2 ); + add_action( 'wp_ajax_woocommerce_remove_variation', __CLASS__ . '::remove_variations', 9 ); + add_action( 'wp_ajax_woocommerce_remove_variations', __CLASS__ . '::remove_variations', 9 ); // Handle bulk edits to subscription data in WC 2.4 add_action( 'woocommerce_bulk_edit_variations', __CLASS__ . '::bulk_edit_variations', 10, 4 ); @@ -202,7 +202,7 @@ class WC_Subscriptions_Product { * For example "$20 per Month for 3 Months with a $10 sign-up fee". * * @param WC_Product|int $product A WC_Product object or ID of a WC_Product. - * @param array $inclusions An associative array of flags to indicate how to calculate the price and what to include, values: + * @param array $include An associative array of flags to indicate how to calculate the price and what to include, values: * 'tax_calculation' => false to ignore tax, 'include_tax' or 'exclude_tax' To indicate that tax should be added or excluded respectively * 'subscription_length' => true to include subscription's length (default) or false to exclude it * 'sign_up_fee' => true to include subscription's sign up fee (default) or false to exclude it @@ -537,7 +537,6 @@ class WC_Subscriptions_Product { * * @param int|WC_Product $product The product instance or product/post ID of a subscription product. * @param mixed $from_date A MySQL formatted date/time string from which to calculate the expiration date, or empty (default), which will use today's date/time. - * @param string $type The return format for the date, either 'mysql', or 'timezone'. Default 'mysql'. * @param string $timezone The timezone for the returned date, either 'site' for the site's timezone, or 'gmt'. Default, 'site'. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ @@ -560,7 +559,6 @@ class WC_Subscriptions_Product { * * @param int|WC_Product $product The product instance or product/post ID of a subscription product. * @param mixed $from_date A MySQL formatted date/time string from which to calculate the expiration date, or empty (default), which will use today's date/time. - * @param string $type The return format for the date, either 'mysql', or 'timezone'. Default 'mysql'. * @param string $timezone The timezone for the returned date, either 'site' for the site's timezone, or 'gmt'. Default, 'site'. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ @@ -816,14 +814,14 @@ class WC_Subscriptions_Product { public static function prevent_scheduled_deletion() { global $wpdb; - $query = "UPDATE $wpdb->postmeta - INNER JOIN $wpdb->posts ON $wpdb->postmeta.post_id = $wpdb->posts.ID - SET $wpdb->postmeta.meta_key = '_wc_trash_meta_time' - WHERE $wpdb->postmeta.meta_key = '_wp_trash_meta_time' - AND $wpdb->posts.post_type IN ( 'product', 'product_variation') - AND $wpdb->posts.post_status = 'trash'"; - - $wpdb->query( $query ); + $wpdb->query( + "UPDATE $wpdb->postmeta + INNER JOIN $wpdb->posts ON $wpdb->postmeta.post_id = $wpdb->posts.ID + SET $wpdb->postmeta.meta_key = '_wc_trash_meta_time' + WHERE $wpdb->postmeta.meta_key = '_wp_trash_meta_time' + AND $wpdb->posts.post_type IN ( 'product', 'product_variation') + AND $wpdb->posts.post_status = 'trash'" + ); } /** @@ -842,12 +840,12 @@ class WC_Subscriptions_Product { if ( isset( $_POST['variation_id'] ) ) { // removing single variation check_ajax_referer( 'delete-variation', 'security' ); - $variation_ids = array( $_POST['variation_id'] ); + $variation_ids = array( wc_clean( wp_unslash( $_POST['variation_id'] ) ) ); } else { // removing multiple variations check_ajax_referer( 'delete-variations', 'security' ); - $variation_ids = (array) $_POST['variation_ids']; + $variation_ids = (array) wc_clean( wp_unslash( $_POST['variation_ids'] ) ); } @@ -910,7 +908,7 @@ class WC_Subscriptions_Product { return; } else { // Since 2.5 we have the product type information available so we don't have to wait for the product to be saved to check if it is a subscription - if ( empty( $_POST['security'] ) || ! wp_verify_nonce( $_POST['security'], 'bulk-edit-variations' ) || 'variable-subscription' !== $_POST['product_type'] ) { + if ( empty( $_POST['security'] ) || ! wp_verify_nonce( wc_clean( wp_unslash( $_POST['security'] ) ), 'bulk-edit-variations' ) || 'variable-subscription' !== $_POST['product_type'] ) { return; } } @@ -936,8 +934,10 @@ class WC_Subscriptions_Product { if ( '%' === substr( $value, -1 ) ) { $percent = wc_format_decimal( substr( $value, 0, -1 ) ); + // @phpstan-ignore binaryOp.invalid $subscription_price += ( ( $subscription_price / 100 ) * $percent ) * "{$operator}1"; } else { + // @phpstan-ignore binaryOp.invalid $subscription_price += $value * "{$operator}1"; } @@ -987,7 +987,7 @@ class WC_Subscriptions_Product { check_admin_referer( 'one_time_shipping', 'nonce' ); - $product = wc_get_product( $_POST['product_id'] ); + $product = wc_get_product( wc_clean( wp_unslash( $_POST['product_id'] ) ) ); $is_synced_or_has_trial = false; if ( WC_Subscriptions_Product::is_subscription( $product ) ) { @@ -1028,15 +1028,15 @@ class WC_Subscriptions_Product { check_admin_referer( 'one_time_shipping', 'nonce' ); - $one_time_shipping_enabled = $_POST['one_time_shipping_enabled']; - $one_time_shipping_selected = $_POST['one_time_shipping_selected']; + $one_time_shipping_enabled = wc_clean( wp_unslash( $_POST['one_time_shipping_enabled'] ) ); + $one_time_shipping_selected = wc_clean( wp_unslash( $_POST['one_time_shipping_selected'] ) ); $subscription_one_time_shipping = 'no'; if ( 'false' !== $one_time_shipping_enabled && 'true' === $one_time_shipping_selected ) { $subscription_one_time_shipping = 'yes'; } - update_post_meta( $_POST['product_id'], '_subscription_one_time_shipping', $subscription_one_time_shipping ); + update_post_meta( wc_clean( wp_unslash( $_POST['product_id'] ) ), '_subscription_one_time_shipping', $subscription_one_time_shipping ); wp_send_json( array( 'one_time_shipping' => $subscription_one_time_shipping ) ); } @@ -1148,7 +1148,7 @@ class WC_Subscriptions_Product { /** * Get an array of parent IDs from a potential child product, used to determine if a product belongs to a group. * - * @param WC_Product The product object to get parents from. + * @param WC_Product $product The product object to get parents from. * @return array Parent IDs * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.4 */ @@ -1159,12 +1159,13 @@ class WC_Subscriptions_Product { if ( wcs_is_woocommerce_pre( '3.0' ) && isset( $product->post->post_parent ) ) { $parent_product_ids[] = $product->get_parent(); } else { - $parent_product_ids = $wpdb->get_col( $wpdb->prepare( - "SELECT post_id - FROM {$wpdb->prefix}postmeta - WHERE meta_key = '_children' AND meta_value LIKE '%%i:%d;%%'", - $product->get_id() - ) ); + $parent_product_ids = $wpdb->get_col( + $wpdb->prepare( + // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.LikeWildcardsInQuery + "SELECT post_id FROM {$wpdb->prefix}postmeta WHERE meta_key = '_children' AND meta_value LIKE '%%i:%d;%%'", + $product->get_id() + ) + ); } return $parent_product_ids; @@ -1175,7 +1176,7 @@ class WC_Subscriptions_Product { * * Unlike @see WC_Subscriptions_Product::get_parent_ids(), this function will return parent products which still exist, are visible and are a grouped product. * - * @param WC_Product The product object to get parents from. + * @param WC_Product $product The product object to get parents from. * @return array The product's grouped parent IDs. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.0 */ @@ -1254,7 +1255,6 @@ class WC_Subscriptions_Product { /** * Check if the current session has an order awaiting payment for a subscription to a specific product line item. * - * @return 2.0.13 * @return bool **/ protected static function order_awaiting_payment_for_product( $product_id ) { diff --git a/includes/core/class-wc-subscriptions-renewal-order.php b/includes/core/class-wc-subscriptions-renewal-order.php index 9359b03..e09e6b5 100644 --- a/includes/core/class-wc-subscriptions-renewal-order.php +++ b/includes/core/class-wc-subscriptions-renewal-order.php @@ -28,7 +28,7 @@ class WC_Subscriptions_Renewal_Order { add_filter( 'wcs_renewal_order_created', array( __CLASS__, 'add_order_note' ), 10, 2 ); // Prevent customers from cancelling renewal orders. Needs to be hooked before WC_Form_Handler::cancel_order() (20) - add_action( 'wp_loaded', array( __CLASS__, 'prevent_cancelling_renewal_orders' ), 19, 3 ); + add_action( 'wp_loaded', array( __CLASS__, 'prevent_cancelling_renewal_orders' ), 19 ); // Don't copy switch order item meta to renewal order items add_filter( 'wcs_new_order_items', array( __CLASS__, 'remove_switch_item_meta_keys' ), 10, 1 ); @@ -51,7 +51,7 @@ class WC_Subscriptions_Renewal_Order { * Check if a given renewal order was created to replace a failed renewal order. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.5.12 - * @param int ID of the renewal order you want to check against + * @param int $renewal_order_id ID of the renewal order you want to check against * @return mixed If the renewal order did replace a failed order, the ID of the fail order, else false */ public static function get_failed_order_replaced_by( $renewal_order_id ) { @@ -170,7 +170,7 @@ class WC_Subscriptions_Renewal_Order { $order_id = absint( $_GET['order_id'] ); $order = wc_get_order( $order_id ); - $redirect = $_GET['redirect']; + $redirect = wc_clean( wp_unslash( $_GET['redirect'] ) ); if ( wcs_order_contains_renewal( $order ) ) { remove_action( 'wp_loaded', 'WC_Form_Handler::cancel_order', 20 ); @@ -268,6 +268,7 @@ class WC_Subscriptions_Renewal_Order { */ public static function maybe_generate_manual_renewal_order( $user_id, $subscription_key ) { _deprecated_function( __METHOD__, '2.0', __CLASS__ . '::maybe_create_manual_renewal_order( WC_Subscription $subscription )' ); + // @phpstan-ignore staticMethod.notFound self::maybe_create_manual_renewal_order( wcs_get_subscription_from_key( $subscription_key ) ); } @@ -277,7 +278,7 @@ class WC_Subscriptions_Renewal_Order { * Deprecated because a subscription's details are now stored in a WC_Subscription object, not the * parent order. * - * @param WC_Order|int $order The WC_Order object or ID of a WC_Order order. + * @param WC_Order|int $renewal_order The WC_Order object or ID of a WC_Order order. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ @@ -295,7 +296,7 @@ class WC_Subscriptions_Renewal_Order { * Deprecated because a subscription's details are now stored in a WC_Subscription object, not the * parent order. * - * @param WC_Order|int $order The WC_Order object or ID of a WC_Order order. + * @param WC_Order|int $renewal_order The WC_Order object or ID of a WC_Order order. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0, self::get_parent_subscription() is the better function to use now as a renewal order */ @@ -367,7 +368,7 @@ class WC_Subscriptions_Renewal_Order { * * Deprecated because the use of a $subscription_key is deprecated. * - * @param string $subscription_key A subscription key of the form created by @see WC_Subscriptions_Manager::get_subscription_key() + * @param string $product_id The ID of the product to renew. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ @@ -386,7 +387,7 @@ class WC_Subscriptions_Renewal_Order { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ - public static function can_subscription_be_renewed( $subscription_key, $user_id = '' ) { + public static function can_subscription_be_renewed( $subscription_key, $user_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'wcs_can_user_resubscribe_to( $subscription, $user_id )' ); return wcs_can_user_resubscribe_to( wcs_get_subscription_from_key( $subscription_key ), $user_id ); } @@ -417,7 +418,7 @@ class WC_Subscriptions_Renewal_Order { /** * Created a new order for renewing a subscription product based on the details of a previous order. * - * @param WC_Order|int $order The WC_Order object or ID of the order for which the a new order should be created. + * @param WC_Order|int $original_order The WC_Order object or ID of the order for which the a new order should be created. * @param string $product_id The ID of the subscription product in the order which needs to be added to the new order. * @param array $args (optional) An array of name => value flags: * 'new_order_role' string A flag to indicate whether the new order should become the master order for the subscription. Accepts either 'parent' or 'child'. Defaults to 'parent' - replace the existing order. @@ -531,6 +532,9 @@ class WC_Subscriptions_Renewal_Order { * * This is particularly important to ensure renewals of limited subscriptions can be completed. * + * @param string $pay_url The URL to the payment page. + * @param WC_Order|int $order The WC_Order object or ID of a WC_Order order. + * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.5.5 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ @@ -554,7 +558,7 @@ class WC_Subscriptions_Renewal_Order { * @see WC_Subscriptions_Manager::process_subscription_payments_on_order() function would * never be called. This function makes sure it is called. * - * @param WC_Order|int $order A WC_Order object or ID of a WC_Order order. + * @param int $order_id The ID of a WC_Order object. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ @@ -574,7 +578,7 @@ class WC_Subscriptions_Renewal_Order { /** * Records manual payment of a renewal order against a subscription. * - * @param WC_Order|int $order A WC_Order object or ID of a WC_Order order. + * @param int $order_id The ID of a WC_Order object. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ @@ -594,7 +598,7 @@ class WC_Subscriptions_Renewal_Order { /** * Records manual payment of a renewal order against a subscription. * - * @param WC_Order|int $order A WC_Order object or ID of a WC_Order order. + * @param WC_Order|int $order_id A WC_Order object or ID of a WC_Order order. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ @@ -615,8 +619,9 @@ class WC_Subscriptions_Renewal_Order { * If the payment for a renewal order has previously failed and is then paid, we need to make sure the * subscription payment function is called. * - * @param int $user_id The id of the user who purchased the subscription - * @param string $subscription_key A subscription key of the form created by @see WC_Subscriptions_Manager::get_subscription_key() + * @param int $order_id The ID of a WC_Order object. + * @param string $payment_status The status of the payment. + * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ diff --git a/includes/core/class-wc-subscriptions-synchroniser.php b/includes/core/class-wc-subscriptions-synchroniser.php index 906df28..555c3e5 100644 --- a/includes/core/class-wc-subscriptions-synchroniser.php +++ b/includes/core/class-wc-subscriptions-synchroniser.php @@ -321,7 +321,10 @@ class WC_Subscriptions_Synchroniser { ?> - +

'; @@ -370,7 +373,7 @@ class WC_Subscriptions_Synchroniser { */ public static function save_subscription_meta( $post_id ) { - if ( empty( $_POST['_wcsnonce'] ) || ! wp_verify_nonce( $_POST['_wcsnonce'], 'wcs_subscription_meta' ) ) { + if ( empty( $_POST['_wcsnonce'] ) || ! wp_verify_nonce( wc_clean( wp_unslash( $_POST['_wcsnonce'] ) ), 'wcs_subscription_meta' ) ) { return; } @@ -382,8 +385,8 @@ class WC_Subscriptions_Synchroniser { if ( 'year' == $_POST['_subscription_period'] ) { // save the day & month for the date rather than just the day $_POST[ self::$post_meta_key ] = array( - 'day' => isset( $_POST[ self::$post_meta_key_day ] ) ? $_POST[ self::$post_meta_key_day ] : 0, - 'month' => isset( $_POST[ self::$post_meta_key_month ] ) ? $_POST[ self::$post_meta_key_month ] : '01', + 'day' => isset( $_POST[ self::$post_meta_key_day ] ) ? wc_clean( wp_unslash( $_POST[ self::$post_meta_key_day ] ) ) : 0, + 'month' => isset( $_POST[ self::$post_meta_key_month ] ) ? wc_clean( wp_unslash( $_POST[ self::$post_meta_key_month ] ) ) : '01', ); } else { @@ -393,7 +396,7 @@ class WC_Subscriptions_Synchroniser { } } - update_post_meta( $post_id, self::$post_meta_key, $_POST[ self::$post_meta_key ] ); + update_post_meta( $post_id, self::$post_meta_key, wc_clean( wp_unslash( $_POST[ self::$post_meta_key ] ) ) ); } /** @@ -403,7 +406,7 @@ class WC_Subscriptions_Synchroniser { */ public static function process_product_meta_variable_subscription( $post_id ) { - if ( empty( $_POST['_wcsnonce_save_variations'] ) || ! wp_verify_nonce( $_POST['_wcsnonce_save_variations'], 'wcs_subscription_variations' ) || ! isset( $_POST['variable_post_id'] ) || ! is_array( $_POST['variable_post_id'] ) ) { + if ( empty( $_POST['_wcsnonce_save_variations'] ) || ! wp_verify_nonce( wc_clean( wp_unslash( $_POST['_wcsnonce_save_variations'] ) ), 'wcs_subscription_variations' ) || ! isset( $_POST['variable_post_id'] ) || ! is_array( $_POST['variable_post_id'] ) ) { return; } @@ -418,7 +421,7 @@ class WC_Subscriptions_Synchroniser { */ public static function save_product_variation( $variation_id, $index ) { - if ( empty( $_POST['_wcsnonce_save_variations'] ) || ! wp_verify_nonce( $_POST['_wcsnonce_save_variations'], 'wcs_subscription_variations' ) || ! isset( $_POST['variable_post_id'] ) || ! is_array( $_POST['variable_post_id'] ) ) { + if ( empty( $_POST['_wcsnonce_save_variations'] ) || ! wp_verify_nonce( wc_clean( wp_unslash( $_POST['_wcsnonce_save_variations'] ) ), 'wcs_subscription_variations' ) || ! isset( $_POST['variable_post_id'] ) || ! is_array( $_POST['variable_post_id'] ) ) { return; } @@ -428,15 +431,15 @@ class WC_Subscriptions_Synchroniser { if ( 'year' == $_POST['variable_subscription_period'][ $index ] ) { // save the day & month for the date rather than just the day $_POST[ 'variable' . self::$post_meta_key ][ $index ] = array( - 'day' => isset( $_POST[ $day_field ][ $index ] ) ? $_POST[ $day_field ][ $index ] : 0, - 'month' => isset( $_POST[ $month_field ][ $index ] ) ? $_POST[ $month_field ][ $index ] : 0, + 'day' => isset( $_POST[ $day_field ][ $index ] ) ? wc_clean( wp_unslash( $_POST[ $day_field ][ $index ] ) ) : 0, + 'month' => isset( $_POST[ $month_field ][ $index ] ) ? wc_clean( wp_unslash( $_POST[ $month_field ][ $index ] ) ) : 0, ); } elseif ( ! isset( $_POST[ 'variable' . self::$post_meta_key ][ $index ] ) ) { $_POST[ 'variable' . self::$post_meta_key ][ $index ] = 0; } - update_post_meta( $variation_id, self::$post_meta_key, $_POST[ 'variable' . self::$post_meta_key ][ $index ] ); + update_post_meta( $variation_id, self::$post_meta_key, wc_clean( wp_unslash( $_POST[ 'variable' . self::$post_meta_key ][ $index ] ) ) ); } /** @@ -608,6 +611,7 @@ class WC_Subscriptions_Synchroniser { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.5 */ public static function calculate_first_payment_date( $product, $type = 'mysql', $from_date = '' ) { + $first_payment_timestamp = 0; if ( ! is_object( $product ) ) { $product = wc_get_product( $product ); @@ -645,7 +649,7 @@ class WC_Subscriptions_Synchroniser { if ( 'week' == $period ) { // Get the day of the week for the from date - $from_day = gmdate( 'N', $from_timestamp ); + $from_day = (int) gmdate( 'N', $from_timestamp ); // To account for rollover of the weekdays. For example, if from day is Saturday and the payment date is Monday, // and the grace period is 2, Saturday is day 6 and Monday is day 1. @@ -665,7 +669,7 @@ class WC_Subscriptions_Synchroniser { $payment_day = gmdate( 't', $from_timestamp ); // the number of days in the month } - $from_day = gmdate( 'j', $from_timestamp ); + $from_day = (int) gmdate( 'j', $from_timestamp ); // If 'from day' is before 'sync day' in the month if ( $from_day <= $payment_day ) { @@ -677,7 +681,7 @@ class WC_Subscriptions_Synchroniser { $month_number = gmdate( 'm', wcs_add_months( $from_timestamp, $interval - 1 ) ); } } else { // If 'from day' is after 'sync day' in the month - $days_in_month = gmdate( 't', $from_timestamp ); + $days_in_month = (int) gmdate( 't', $from_timestamp ); // Use 'days in month' to account for end of month dates if ( $from_day + $no_fee_days - $days_in_month >= $payment_day ) { // In grace period $month = gmdate( 'F', wcs_add_months( $from_timestamp, 1 ) ); @@ -723,6 +727,8 @@ class WC_Subscriptions_Synchroniser { $year++; } + $first_payment_timestamp = 0; + if ( $from_timestamp + ( $no_fee_days * DAY_IN_SECONDS ) >= wcs_strtotime_dark_knight( "{$payment_day['day']} {$month} {$year}" ) ) { // In grace period $first_payment_timestamp = wcs_strtotime_dark_knight( "{$payment_day['day']} {$month} {$year}", $from_timestamp ); @@ -1037,10 +1043,9 @@ class WC_Subscriptions_Synchroniser { * Filters WC_Subscriptions_Order::get_sign_up_fee() to make sure the sign-up fee for a subscription product * that is synchronised is returned correctly. * - * @param float The initial sign-up fee charged when the subscription product in the order was first purchased, if any. - * @param mixed $order A WC_Order object or the ID of the order which the subscription was purchased in. + * @param float $sign_up_fee The initial sign-up fee charged when the subscription product in the order was first purchased, if any. + * @param WC_Subscription $subscription The subscription object. * @param int $product_id The post ID of the subscription WC_Product object purchased in the order. Defaults to the ID of the first product purchased in the order. - * @return float The initial sign-up fee charged when the subscription product in the order was first purchased, if any. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ public static function get_synced_sign_up_fee( $sign_up_fee, $subscription, $product_id ) { @@ -1072,15 +1077,17 @@ class WC_Subscriptions_Synchroniser { return $price; } + $days_in_cycle = 0; + switch ( WC_Subscriptions_Product::get_period( $product ) ) { case 'week': $days_in_cycle = 7 * WC_Subscriptions_Product::get_interval( $product ); break; case 'month': - $days_in_cycle = gmdate( 't' ) * WC_Subscriptions_Product::get_interval( $product ); + $days_in_cycle = (int) gmdate( 't' ) * WC_Subscriptions_Product::get_interval( $product ); break; case 'year': - $days_in_cycle = ( 365 + gmdate( 'L' ) ) * WC_Subscriptions_Product::get_interval( $product ); + $days_in_cycle = ( 365 + (int) gmdate( 'L' ) ) * WC_Subscriptions_Product::get_interval( $product ); break; } @@ -1132,7 +1139,7 @@ class WC_Subscriptions_Synchroniser { * * @param integer $qty the original quantity that would be taken out of the stock level * @param array $order order data - * @param array $item item data for each item in the order + * @param array $order_item item data for each item in the order * * @return int */ @@ -1159,7 +1166,7 @@ class WC_Subscriptions_Synchroniser { * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 * - * @param WC_Subscription|int Subscription object or ID. + * @param WC_Subscription|int $subscription Subscription object or ID. */ public static function maybe_add_subscription_meta( $subscription ) { if ( ! is_object( $subscription ) ) { @@ -1167,6 +1174,7 @@ class WC_Subscriptions_Synchroniser { } if ( $subscription && ! self::subscription_contains_synced_product( $subscription ) ) { + /** @var WC_Order_Item_Product $item */ foreach ( $subscription->get_items() as $item ) { $product = $item->get_product(); @@ -1183,8 +1191,8 @@ class WC_Subscriptions_Synchroniser { * When adding an item to an order/subscription via the Add/Edit Subscription administration interface, check if we should be setting * the sync meta on the subscription. * - * @param int The order item ID of an item that was just added to the order - * @param array The order item details + * @param int $item_id The order item ID of an item that was just added to the order + * @param array $item The order item details * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ public static function ajax_maybe_add_meta_for_item( $item_id, $item ) { @@ -1200,9 +1208,9 @@ class WC_Subscriptions_Synchroniser { * When adding a product to an order/subscription via the WC_Subscription::add_product() method, check if we should be setting * the sync meta on the subscription. * - * @param int The post ID of a WC_Order or child object - * @param int The order item ID of an item that was just added to the order - * @param object The WC_Product for which an item was just added + * @param int $subscription_id The post ID of a WC_Order or child object + * @param int $item_id The order item ID of an item that was just added to the order + * @param object $product The WC_Product for which an item was just added * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ public static function maybe_add_meta_for_new_product( $subscription_id, $item_id, $product ) { @@ -1216,7 +1224,7 @@ class WC_Subscriptions_Synchroniser { * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 * - * @param int|WC_Subscription Accepts either a subscription object or ID. + * @param int|WC_Subscription $subscription Accepts either a subscription object or ID. * @return bool True if the subscription is synced, false otherwise. */ public static function subscription_contains_synced_product( $subscription ) { @@ -1257,9 +1265,9 @@ class WC_Subscriptions_Synchroniser { * * Attached to WC 3.0+ hooks and uses WC 3.0 methods. * - * @param int The new line item id - * @param WC_Order_Item - * @param int The post ID of a WC_Subscription + * @param int $item_id The new line item id + * @param WC_Order_Item $item + * @param int $subscription_id The post ID of a WC_Subscription * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.3 */ public static function maybe_add_meta_for_new_line_item( $item_id, $item, $subscription_id ) { @@ -1552,7 +1560,7 @@ class WC_Subscriptions_Synchroniser { * Filters WC_Subscriptions_Order::get_sign_up_fee() to make sure the sign-up fee for a subscription product * that is synchronised is returned correctly. * - * @param float The initial sign-up fee charged when the subscription product in the order was first purchased, if any. + * @param float $sign_up_fee The initial sign-up fee charged when the subscription product in the order was first purchased, if any. * @param mixed $order A WC_Order object or the ID of the order which the subscription was purchased in. * @param int $product_id The post ID of the subscription WC_Product object purchased in the order. Defaults to the ID of the first product purchased in the order. * @return float The initial sign-up fee charged when the subscription product in the order was first purchased, if any. diff --git a/includes/core/class-wcs-action-scheduler-customer-notifications.php b/includes/core/class-wcs-action-scheduler-customer-notifications.php index b2c7fdb..0234784 100644 --- a/includes/core/class-wcs-action-scheduler-customer-notifications.php +++ b/includes/core/class-wcs-action-scheduler-customer-notifications.php @@ -50,7 +50,7 @@ class WCS_Action_Scheduler_Customer_Notifications extends WCS_Scheduler { 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( 'update_option_' . WC_Subscriptions_Admin::$option_prefix . WC_Subscriptions_Email_Notifications::$offset_setting_string, [ $this, 'set_time_offset_from_option' ], 5, 2 ); add_action( 'add_option_' . WC_Subscriptions_Admin::$option_prefix . WC_Subscriptions_Email_Notifications::$offset_setting_string, [ $this, 'set_time_offset_from_option' ], 5, 2 ); } diff --git a/includes/core/class-wcs-action-scheduler.php b/includes/core/class-wcs-action-scheduler.php index 5ab8b7d..47c28b5 100644 --- a/includes/core/class-wcs-action-scheduler.php +++ b/includes/core/class-wcs-action-scheduler.php @@ -85,9 +85,9 @@ class WCS_Action_Scheduler extends WCS_Scheduler { /** * When a subscription's status is updated, maybe schedule an event * - * @param object $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 - * @param string $datetime A MySQL formatted date/time string in the GMT/UTC timezone. + * @param WC_Subscription $subscription An instance of a WC_Subscription object + * @param string $new_status Can be 'trial_end', 'next_payment', 'end', 'end_of_prepaid_term' or a custom date type + * @param string $old_status */ public function update_status( $subscription, $new_status, $old_status ) { @@ -230,7 +230,7 @@ class WCS_Action_Scheduler extends WCS_Scheduler { /** * 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 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 ) { diff --git a/includes/core/class-wcs-batch-processing-controller.php b/includes/core/class-wcs-batch-processing-controller.php index c2c2b23..c4664e5 100644 --- a/includes/core/class-wcs-batch-processing-controller.php +++ b/includes/core/class-wcs-batch-processing-controller.php @@ -78,8 +78,7 @@ class WCS_Batch_Processing_Controller { function ( $batch_process ) { $this->process_next_batch_for_single_processor( $batch_process ); }, - 10, - 2 + 10 ); add_action( @@ -271,7 +270,7 @@ class WCS_Batch_Processing_Controller { * @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 { + 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; @@ -333,7 +332,8 @@ class WCS_Batch_Processing_Controller { * @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 ) ) { + $processor = null; + if ( 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(); } diff --git a/includes/core/class-wcs-cart-initial-payment.php b/includes/core/class-wcs-cart-initial-payment.php index 9697f71..e2b6157 100644 --- a/includes/core/class-wcs-cart-initial-payment.php +++ b/includes/core/class-wcs-cart-initial-payment.php @@ -44,7 +44,7 @@ class WCS_Cart_Initial_Payment extends WCS_Cart_Renewal { } // Pay for existing order - $order_key = $_GET['key']; + $order_key = wc_clean( wp_unslash( $_GET['key'] ) ); $order_id = absint( $wp->query_vars['order-pay'] ); $order = wc_get_order( $order_id ); @@ -135,11 +135,11 @@ class WCS_Cart_Initial_Payment extends WCS_Cart_Renewal { /** * Get the order object used to construct the initial payment cart. * - * @param Array The initial payment cart item. - * @return WC_Order | The order object + * @param array $cart_item The initial payment cart item. + * @return WC_Order The order object * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.13 */ - protected function get_order( $cart_item = '' ) { + protected function get_order( $cart_item = array() ) { $order = false; if ( empty( $cart_item ) ) { diff --git a/includes/core/class-wcs-cart-renewal.php b/includes/core/class-wcs-cart-renewal.php index 0bfb8f7..d67ec01 100644 --- a/includes/core/class-wcs-cart-renewal.php +++ b/includes/core/class-wcs-cart-renewal.php @@ -178,7 +178,7 @@ class WCS_Cart_Renewal { if ( isset( $_GET['pay_for_order'] ) && isset( $_GET['key'] ) && isset( $wp->query_vars['order-pay'] ) ) { // Pay for existing order - $order_key = $_GET['key']; + $order_key = isset( $_GET['key'] ) ? wc_clean( wp_unslash( $_GET['key'] ) ) : ''; $order_id = isset( $wp->query_vars['order-pay'] ) ? $wp->query_vars['order-pay'] : absint( $_GET['order_id'] ); $order = wc_get_order( $order_id ); @@ -211,7 +211,7 @@ class WCS_Cart_Renewal { do_action( 'wcs_before_renewal_setup_cart_subscription', $subscription, $order ); // Check if order/subscription can be paid for - if ( empty( $subscription ) || ! $subscription->has_status( array( 'on-hold', 'pending' ) ) ) { + if ( ! $subscription->has_status( array( 'on-hold', 'pending' ) ) ) { wc_add_notice( __( 'This order can no longer be paid because the corresponding subscription does not require payment at this time.', 'woocommerce-subscriptions' ), 'error' ); } else { // Add the existing subscription items to the cart @@ -279,9 +279,9 @@ class WCS_Cart_Renewal { * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0 * - * @param WC_Abstract_Order $subscription The subscription or Order object to set up the cart from. - * @param array $cart_item_data Additional cart item data to set on the cart items. - * @param string $validation_type Whether all items are required or not. Optional. Can be 'all_items_not_required' or 'all_items_required'. 'all_items_not_required' by default. + * @param WC_Subscription $subscription The subscription or Order object to set up the cart from. + * @param array $cart_item_data Additional cart item data to set on the cart items. + * @param string $validation_type Whether all items are required or not. Optional. Can be 'all_items_not_required' or 'all_items_required'. 'all_items_not_required' by default. * 'all_items_not_required' - If an order/subscription line item fails to be added to the cart, the remaining items will be added. * 'all_items_required' - If an order/subscription line item fails to be added to the cart, all items will be removed and the cart setup will be aborted. */ @@ -671,7 +671,7 @@ class WCS_Cart_Renewal { $subscriptions = wcs_get_subscriptions_for_renewal_order( $order ); foreach ( $subscriptions as $subscription ) { - if ( empty( $subscription ) || ! $subscription->has_status( array( 'on-hold', 'pending' ) ) ) { + if ( ! $subscription->has_status( array( 'on-hold', 'pending' ) ) ) { unset( $actions['pay'] ); break; } @@ -820,7 +820,7 @@ class WCS_Cart_Renewal { /** * Get original products for a renewal order - so that we can ensure renewal coupons are only applied to those * - * @param object WC_Order | WC_Subscription $order + * @param WC_Order|WC_Subscription $order * @return array $product_ids an array of product ids on a subscription/order * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.10 */ @@ -843,12 +843,12 @@ class WCS_Cart_Renewal { /** * Store renewal coupon information in a session variable so we can access it later when coupon data is being retrieved * - * @param int $subscription_id subscription id + * @param int $order_id order id * @param object $coupon coupon * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.10 */ protected function store_coupon( $order_id, $coupon ) { - if ( ! empty( $order_id ) && ! empty( $coupon ) ) { + if ( ! empty( $order_id ) ) { $renewal_coupons = WC()->session->get( 'wcs_renewal_coupons', array() ); $use_bools = wcs_is_woocommerce_pre( '3.0' ); // Some coupon properties have changed from accepting 'no' and 'yes' to true and false args. $coupon_properties = array(); @@ -975,11 +975,11 @@ class WCS_Cart_Renewal { /** * Get the order object used to construct the renewal cart. * - * @param Array The renewal cart item. - * @return WC_Order | The order object + * @param array $cart_item The renewal cart item. + * @return WC_Order The order object * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.13 */ - protected function get_order( $cart_item = '' ) { + protected function get_order( $cart_item = array() ) { $order = false; if ( empty( $cart_item ) ) { @@ -1027,8 +1027,8 @@ class WCS_Cart_Renewal { * Right before WC processes a renewal cart through the checkout, set the cart hash. * This ensures legitimate changes to taxes and shipping methods don't cause a new order to be created. * - * @param Mixed | An order generated by third party plugins - * @return Mixed | The unchanged order param + * @param mixed $order An order generated by third party plugins + * @return mixed The unchanged order param * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.11 */ public function update_cart_hash( $order ) { @@ -1054,8 +1054,8 @@ class WCS_Cart_Renewal { /** * Redirect back to pay for an order after successfully logging in. * - * @param string The redirect URL after successful login. - * @param WP_User The newly logged in user object. + * @param string $redirect The redirect URL after successful login. + * @param WP_User $user The newly logged in user object. * @return string * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.1.0 */ @@ -1064,7 +1064,7 @@ class WCS_Cart_Renewal { * Nonce verification is not needed here as it was already checked during the log-in process, see WC_Form_Handler::process_login(). */ if ( isset( $_GET['wcs_redirect'], $_GET['wcs_redirect_id'] ) && 'pay_for_order' === $_GET['wcs_redirect'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended - $order = wc_get_order( $_GET['wcs_redirect_id'] ); + $order = wc_get_order( wc_clean( wp_unslash( $_GET['wcs_redirect_id'] ) ) ); if ( $order && $order->get_user_id() && user_can( $user, 'pay_for_order', $order->get_id() ) ) { $redirect = $order->get_checkout_payment_url(); @@ -1099,12 +1099,12 @@ class WCS_Cart_Renewal { * Prevent compounding dynamic discounts on cart items. * Dynamic discounts are copied from the subscription to the renewal order and so don't need to be applied again in the cart. * - * @param bool Whether to apply the dynamic discount - * @param string The cart item key of the cart item the dynamic discount is being applied to. + * @param bool $adjust_price Whether to apply the dynamic discount + * @param string $cart_item_key The cart item key of the cart item the dynamic discount is being applied to. * @return bool * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.1.4 */ - function prevent_compounding_dynamic_discounts( $adjust_price, $cart_item_key ) { + public function prevent_compounding_dynamic_discounts( $adjust_price, $cart_item_key ) { if ( $adjust_price && isset( WC()->cart->cart_contents[ $cart_item_key ][ $this->cart_item_key ] ) ) { $adjust_price = false; @@ -1136,7 +1136,7 @@ class WCS_Cart_Renewal { * the cart so we can update it later. * * @param int|WC_Order $order_id - * @param array $checkout_posted_data + * @param array $posted_checkout_data * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.1 */ public function set_order_item_id( $order_id, $posted_checkout_data = array() ) { @@ -1298,7 +1298,7 @@ class WCS_Cart_Renewal { * Used when WC 3.0 or newer is active. When prior versions are active, * @see WCS_Cart_Renewal->add_order_item_meta() replaces this function * - * @param WC_Order_Item_Product + * @param WC_Order_Item_Product $item * @param string $cart_item_key * @param array $cart_item_data * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.11 @@ -1772,8 +1772,8 @@ class WCS_Cart_Renewal { * Right before WC processes a renewal cart through the checkout, set the cart hash. * This ensures legitimate changes to taxes and shipping methods don't cause a new order to be created. * - * @param Mixed | An order generated by third party plugins - * @return Mixed | The unchanged order param + * @param mixed $order An order generated by third party plugins + * @return mixed The unchanged order param * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.1.0 */ public function set_renewal_order_cart_hash( $order ) { @@ -1884,7 +1884,7 @@ class WCS_Cart_Renewal { * status as 'failed' until it is paid. By default, it will always set it to 'pending', but we need it left as 'failed' * so that we can correctly identify the status change in @see self::maybe_change_subscription_status(). * - * @param string Default order status for orders paid for via checkout. Default 'pending' + * @param string $order_status Default order status for orders paid for via checkout. Default 'pending' * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 * * @deprecated 6.3.0 diff --git a/includes/core/class-wcs-cart-resubscribe.php b/includes/core/class-wcs-cart-resubscribe.php index 29e229e..0faed9a 100644 --- a/includes/core/class-wcs-cart-resubscribe.php +++ b/includes/core/class-wcs-cart-resubscribe.php @@ -36,14 +36,22 @@ class WCS_Cart_Resubscribe extends WCS_Cart_Renewal { // Mock a free trial on the cart item to make sure the resubscribe total doesn't include any recurring amount when honoring prepaid term add_filter( 'woocommerce_before_calculate_totals', array( &$this, 'maybe_set_free_trial' ), 100, 1 ); + // @phpstan-ignore return.void add_action( 'woocommerce_subscription_cart_before_grouping', array( &$this, 'maybe_unset_free_trial' ) ); + // @phpstan-ignore return.void add_action( 'woocommerce_subscription_cart_after_grouping', array( &$this, 'maybe_set_free_trial' ) ); + // @phpstan-ignore return.void add_action( 'wcs_recurring_cart_start_date', array( &$this, 'maybe_unset_free_trial' ), 0, 1 ); + // @phpstan-ignore return.void add_action( 'wcs_recurring_cart_end_date', array( &$this, 'maybe_set_free_trial' ), 100, 1 ); add_filter( 'woocommerce_subscriptions_calculated_total', array( &$this, 'maybe_unset_free_trial' ), 10000, 1 ); + // @phpstan-ignore return.void add_action( 'woocommerce_cart_totals_before_shipping', array( &$this, 'maybe_set_free_trial' ) ); + // @phpstan-ignore return.void add_action( 'woocommerce_cart_totals_after_shipping', array( &$this, 'maybe_unset_free_trial' ) ); + // @phpstan-ignore return.void add_action( 'woocommerce_review_order_before_shipping', array( &$this, 'maybe_set_free_trial' ) ); + // @phpstan-ignore return.void add_action( 'woocommerce_review_order_after_shipping', array( &$this, 'maybe_unset_free_trial' ) ); add_action( 'woocommerce_order_status_changed', array( &$this, 'maybe_cancel_existing_subscription' ), 10, 3 ); @@ -62,15 +70,12 @@ class WCS_Cart_Resubscribe extends WCS_Cart_Renewal { if ( isset( $_GET['resubscribe'] ) && isset( $_GET['_wpnonce'] ) ) { - $subscription = wcs_get_subscription( $_GET['resubscribe'] ); + $subscription = wcs_get_subscription( wc_clean( wp_unslash( $_GET['resubscribe'] ) ) ); $redirect_to = get_permalink( wc_get_page_id( 'myaccount' ) ); - if ( wp_verify_nonce( $_GET['_wpnonce'], $subscription->get_id() ) === false ) { - + if ( wp_verify_nonce( wc_clean( wp_unslash( $_GET['_wpnonce'] ) ), $subscription->get_id() ) === false ) { wc_add_notice( __( 'There was an error with your request to resubscribe. Please try again.', 'woocommerce-subscriptions' ), 'error' ); - } elseif ( empty( $subscription ) ) { - wc_add_notice( __( 'That subscription does not exist. Has it been deleted?', 'woocommerce-subscriptions' ), 'error' ); } elseif ( ! current_user_can( 'subscribe_again', $subscription->get_id() ) ) { @@ -80,7 +85,6 @@ class WCS_Cart_Resubscribe extends WCS_Cart_Renewal { } elseif ( ! wcs_can_user_resubscribe_to( $subscription ) ) { wc_add_notice( __( 'You can not resubscribe to that subscription. Please contact us if you need assistance.', 'woocommerce-subscriptions' ), 'error' ); - } else { $this->setup_cart( $subscription, array( @@ -101,7 +105,7 @@ class WCS_Cart_Resubscribe extends WCS_Cart_Renewal { $order_id = ( isset( $wp->query_vars['order-pay'] ) ) ? $wp->query_vars['order-pay'] : absint( $_GET['order_id'] ); $order = wc_get_order( $wp->query_vars['order-pay'] ); - $order_key = $_GET['key']; + $order_key = wc_clean( wp_unslash( $_GET['key'] ) ); if ( wcs_get_objects_property( $order, 'order_key' ) == $order_key && $order->has_status( array( 'pending', 'failed' ) ) && wcs_order_contains_resubscribe( $order ) ) { @@ -200,21 +204,21 @@ class WCS_Cart_Resubscribe extends WCS_Cart_Renewal { * * @see wcs_cart_contains_resubscribe() * @param WC_Cart $cart The cart object to search in. - * @return bool | Array The cart item containing the renewal, else false. + * @return bool|array The cart item containing the renewal, else false. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.10 */ - protected function cart_contains( $cart = '' ) { + protected function cart_contains( $cart = null ) { return wcs_cart_contains_resubscribe( $cart ); } /** * Get the subscription object used to construct the resubscribe cart. * - * @param Array The resubscribe cart item. - * @return WC_Subscription | The subscription object. + * @param array $cart_item The resubscribe cart item. + * @return WC_Subscription The subscription object. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.13 */ - protected function get_order( $cart_item = '' ) { + protected function get_order( $cart_item = null ) { $subscription = false; if ( empty( $cart_item ) ) { diff --git a/includes/core/class-wcs-change-payment-method-admin.php b/includes/core/class-wcs-change-payment-method-admin.php index 290dd61..d683097 100644 --- a/includes/core/class-wcs-change-payment-method-admin.php +++ b/includes/core/class-wcs-change-payment-method-admin.php @@ -45,8 +45,11 @@ class WCS_Change_Payment_Method_Admin { } elseif ( count( $valid_payment_methods ) == 1 ) { echo '' . esc_html__( 'Payment method', 'woocommerce-subscriptions' ) . '
' . esc_html( current( $valid_payment_methods ) ); - // translators: %s: gateway ID. - echo wcs_help_tip( sprintf( _x( 'Gateway ID: [%s]', 'The gateway ID displayed on the Edit Subscriptions screen when editing payment method.', 'woocommerce-subscriptions' ), key( $valid_payment_methods ) ) ); + // @phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo wcs_help_tip( + // translators: %s: gateway ID. + sprintf( _x( 'Gateway ID: [%s]', 'The gateway ID displayed on the Edit Subscriptions screen when editing payment method.', 'woocommerce-subscriptions' ), key( $valid_payment_methods ) ) + ); echo ''; } @@ -101,7 +104,7 @@ class WCS_Change_Payment_Method_Admin { */ public static function save_meta( $subscription ) { - if ( empty( $_POST['_wcsnonce'] ) || ! wp_verify_nonce( $_POST['_wcsnonce'], 'wcs_change_payment_method_admin' ) ) { + if ( empty( $_POST['_wcsnonce'] ) || ! wp_verify_nonce( wc_clean( wp_unslash( $_POST['_wcsnonce'] ) ), 'wcs_change_payment_method_admin' ) ) { return; } @@ -125,7 +128,7 @@ class WCS_Change_Payment_Method_Admin { } foreach ( $meta as $meta_key => $meta_data ) { - $payment_method_meta[ $meta_table ][ $meta_key ]['value'] = isset( $_POST['_payment_method_meta'][ $payment_method ][ $meta_table ][ $meta_key ] ) ? $_POST['_payment_method_meta'][ $payment_method ][ $meta_table ][ $meta_key ] : ''; + $payment_method_meta[ $meta_table ][ $meta_key ]['value'] = isset( $_POST['_payment_method_meta'][ $payment_method ][ $meta_table ][ $meta_key ] ) ? wc_clean( wp_unslash( $_POST['_payment_method_meta'][ $payment_method ][ $meta_table ][ $meta_key ] ) ) : ''; } } } @@ -158,8 +161,8 @@ class WCS_Change_Payment_Method_Admin { * Get a list of possible gateways that a subscription could be changed to by admins. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 - * @param $subscription int | WC_Subscription - * @return + * @param int|WC_Subscription $subscription The subscription object + * @return array */ public static function get_valid_payment_methods( $subscription ) { diff --git a/includes/core/class-wcs-download-handler.php b/includes/core/class-wcs-download-handler.php index 94f9fb7..d6d95c0 100644 --- a/includes/core/class-wcs-download-handler.php +++ b/includes/core/class-wcs-download-handler.php @@ -77,7 +77,7 @@ class WCS_Download_Handler { foreach ( array_keys( $downloads ) as $download_id ) { // grant access on subscription if it does not already exist - if ( ! $wpdb->get_var( $wpdb->prepare( "SELECT download_id FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE `order_id` = %d AND `product_id` = %d AND `download_id` = '%s'", $subscription->get_id(), $product_id, $download_id ) ) ) { + if ( ! $wpdb->get_var( $wpdb->prepare( "SELECT download_id FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE `order_id` = %d AND `product_id` = %d AND `download_id` = %s", $subscription->get_id(), $product_id, $download_id ) ) ) { wc_downloadable_file_permission( $download_id, $product_id, $subscription, $item['qty'] ); } self::revoke_downloadable_file_permission( $product_id, $order_id, $order->get_user_id() ); diff --git a/includes/core/class-wcs-failed-scheduled-action-manager.php b/includes/core/class-wcs-failed-scheduled-action-manager.php index 6f21145..dac3372 100644 --- a/includes/core/class-wcs-failed-scheduled-action-manager.php +++ b/includes/core/class-wcs-failed-scheduled-action-manager.php @@ -121,6 +121,7 @@ class WCS_Failed_Scheduled_Action_Manager { // Log any exceptions caught by the exception listener in action logs. if ( ! empty( $context['exceptions'] ) ) { foreach ( $context['exceptions'] as $exception_message ) { + // @phpstan-ignore-next-line ActionScheduler_Logger::instance()->log( $action_id, $exception_message ); } } @@ -243,7 +244,7 @@ class WCS_Failed_Scheduled_Action_Manager { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.19 */ protected function maybe_disable_admin_notice() { - if ( isset( $_GET['_wcsnonce'] ) && wp_verify_nonce( $_GET['_wcsnonce'], 'wcs_scheduled_action_timeout_error_notice' ) && isset( $_GET['wcs_scheduled_action_timeout_error_notice'] ) ) { + if ( isset( $_GET['_wcsnonce'] ) && wp_verify_nonce( wc_clean( wp_unslash( $_GET['_wcsnonce'] ) ), 'wcs_scheduled_action_timeout_error_notice' ) && isset( $_GET['wcs_scheduled_action_timeout_error_notice'] ) ) { delete_option( WC_Subscriptions_Admin::$option_prefix . '_failed_scheduled_actions' ); } } diff --git a/includes/core/class-wcs-initial-cart-stock-manager.php b/includes/core/class-wcs-initial-cart-stock-manager.php index fbb4f20..008103c 100644 --- a/includes/core/class-wcs-initial-cart-stock-manager.php +++ b/includes/core/class-wcs-initial-cart-stock-manager.php @@ -24,7 +24,7 @@ class WCS_Initial_Cart_Stock_Manager extends WCS_Renewal_Cart_Stock_Manager { parent::attach_callbacks(); // The parent class attaches a filter not needed for initial carts. So we remove it and attach the parent order equivalent. - remove_action( 'wcs_before_renewal_setup_cart_subscription', 'WCS_Renewal_Cart_Stock_Manager::maybe_adjust_stock_cart', 10, 2 ); + remove_action( 'wcs_before_renewal_setup_cart_subscription', 'WCS_Renewal_Cart_Stock_Manager::maybe_adjust_stock_cart', 10 ); add_action( 'wcs_before_parent_order_setup_cart', array( get_called_class(), 'maybe_adjust_stock_cart' ), 10, 2 ); } diff --git a/includes/core/class-wcs-limiter.php b/includes/core/class-wcs-limiter.php index 1f2aa5e..b2e45e7 100644 --- a/includes/core/class-wcs-limiter.php +++ b/includes/core/class-wcs-limiter.php @@ -163,7 +163,7 @@ class WCS_Limiter { } // Adding to cart - if ( isset( $_GET['switch-subscription'] ) && array_key_exists( $_GET['switch-subscription'], self::get_user_subscriptions_to_product( $product, $user_id, $product_limitation ) ) ) { + if ( isset( $_GET['switch-subscription'] ) && array_key_exists( wc_clean( wp_unslash( $_GET['switch-subscription'] ) ), self::get_user_subscriptions_to_product( $product, $user_id, $product_limitation ) ) ) { $is_purchasable = true; } else { // If we have a variation product get the variable product's ID. We can't use the variation ID for comparison because this function sometimes receives a variable product. @@ -198,9 +198,10 @@ class WCS_Limiter { */ public static function is_purchasable_renewal( $is_purchasable, $product ) { if ( false === $is_purchasable && false === self::is_purchasable_product( $is_purchasable, $product ) ) { + $resubscribe_cart_item = wcs_cart_contains_resubscribe(); // Resubscribe logic - if ( isset( $_GET['resubscribe'] ) || false !== ( $resubscribe_cart_item = wcs_cart_contains_resubscribe() ) ) { + if ( isset( $_GET['resubscribe'] ) || false !== $resubscribe_cart_item ) { $subscription_id = ( isset( $_GET['resubscribe'] ) ) ? absint( $_GET['resubscribe'] ) : $resubscribe_cart_item['subscription_resubscribe']['subscription_id']; $subscription = wcs_get_subscription( $subscription_id ); diff --git a/includes/core/class-wcs-my-account-payment-methods.php b/includes/core/class-wcs-my-account-payment-methods.php index c4556dd..a00f20f 100644 --- a/includes/core/class-wcs-my-account-payment-methods.php +++ b/includes/core/class-wcs-my-account-payment-methods.php @@ -34,8 +34,8 @@ class WCS_My_Account_Payment_Methods { /** * Add additional query args to delete token URLs which are being used for subscription automatic payments. * - * @param array data about the token including a list of actions which can be triggered by the customer from their my account page - * @param WC_Payment_Token payment token object + * @param array $payment_token_data data about the token including a list of actions which can be triggered by the customer from their my account page + * @param WC_Payment_Token $payment_token payment token object * @return array payment token data * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.7 */ @@ -63,8 +63,8 @@ class WCS_My_Account_Payment_Methods { * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v3.1.0 * - * @param bool Whether the delete button should be shown for tokens linked to a subscription. true - show, false - not shown (default). - * @param WC_Payment_Token The payment token in question. + * @param bool $allow_deletion Whether the delete button should be shown for tokens linked to a subscription. true - show, false - not shown (default). + * @param WC_Payment_Token $payment_token The payment token in question. */ if ( isset( $payment_token_data['actions']['delete'] ) && ! apply_filters( 'wc_subscriptions_allow_subscription_token_deletion', false, $payment_token ) ) { // Cannot delete a token used for active subscriptions where there is no alternative. @@ -99,7 +99,7 @@ class WCS_My_Account_Payment_Methods { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.7 */ public static function maybe_update_subscriptions_payment_meta( $deleted_token_id, $deleted_token ) { - if ( ! isset( $_GET['delete_subscription_token'] ) || empty( $_GET['wcs_nonce'] ) || ! wp_verify_nonce( $_GET['wcs_nonce'], 'delete_subscription_token_' . $_GET['delete_subscription_token'] ) ) { + if ( ! isset( $_GET['delete_subscription_token'] ) || empty( $_GET['wcs_nonce'] ) || ! wp_verify_nonce( wc_clean( wp_unslash( $_GET['wcs_nonce'] ) ), 'delete_subscription_token_' . wc_clean( wp_unslash( $_GET['delete_subscription_token'] ) ) ) ) { return; } @@ -108,6 +108,7 @@ class WCS_My_Account_Payment_Methods { $new_token = WCS_Payment_Tokens::get_customers_alternative_token( $deleted_token ); + // @phpstan-ignore empty.variable if ( empty( $new_token ) ) { $notice = esc_html__( 'The deleted payment method was used for automatic subscription payments, we couldn\'t find an alternative token payment method token to change your subscriptions to.', 'woocommerce-subscriptions' ); wc_add_notice( $notice, 'error' ); @@ -146,7 +147,7 @@ class WCS_My_Account_Payment_Methods { * * @deprecated 1.0.0 - Migrated from WooCommerce Subscriptions v2.7.2 * - * @param WC_Payment_Token payment token object + * @param WC_Payment_Token $token payment token object * @return string WC_Payment_Token label * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.7 */ @@ -207,14 +208,14 @@ class WCS_My_Account_Payment_Methods { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3.3 */ public static function update_subscription_tokens() { - if ( ! isset( $_GET['update-subscription-tokens'], $_GET['token-id'], $_GET['_wcsnonce'] ) || ! wp_verify_nonce( $_GET['_wcsnonce'], 'wcs-update-subscription-tokens' ) ) { + if ( ! isset( $_GET['update-subscription-tokens'], $_GET['token-id'], $_GET['_wcsnonce'] ) || ! wp_verify_nonce( wc_clean( wp_unslash( $_GET['_wcsnonce'] ) ), 'wcs-update-subscription-tokens' ) ) { return; } // init payment gateways WC()->payment_gateways(); - $default_token_id = $_GET['token-id']; + $default_token_id = wc_clean( wp_unslash( $_GET['token-id'] ) ); $default_token = WC_Payment_Tokens::get( $default_token_id ); if ( ! $default_token ) { @@ -233,7 +234,7 @@ class WCS_My_Account_Payment_Methods { } } - wp_redirect( remove_query_arg( array( 'update-subscription-tokens', 'token-id', '_wcsnonce' ) ) ); + wp_safe_redirect( remove_query_arg( array( 'update-subscription-tokens', 'token-id', '_wcsnonce' ) ) ); exit(); } diff --git a/includes/core/class-wcs-object-data-cache-manager.php b/includes/core/class-wcs-object-data-cache-manager.php index 94439a6..84c47b8 100644 --- a/includes/core/class-wcs-object-data-cache-manager.php +++ b/includes/core/class-wcs-object-data-cache-manager.php @@ -51,8 +51,8 @@ class WCS_Object_Data_Cache_Manager extends WCS_Post_Meta_Cache_Manager { /** * Constructor. * - * @param string The post type this cache manage acts on. - * @param array The post meta keys this cache manager should act on. + * @param string $object_type The post type this cache manage acts on. + * @param array $data_keys The post meta keys this cache manager should act on. */ public function __construct( $object_type, $data_keys ) { $this->object_type = $object_type; @@ -82,33 +82,35 @@ class WCS_Object_Data_Cache_Manager extends WCS_Post_Meta_Cache_Manager { * Relevant changes to the object's data is stored in the $this->object_changes property * to be processed after the object is saved. See $this->action_object_cache_changes(). * - * @param WC_Data $object The object which is being saved. - * @param string $generate_type Optional. The data to generate the changes from. Defaults to 'changes_only' which will generate the data from changes to the object. 'all_fields' will fetch data from the object for all tracked data keys. + * @param WC_Subscription $subscription The object which is being saved. + * @param string $generate_type Optional. The data to generate the changes from. Defaults to 'changes_only' which will generate the data from changes to the object. 'all_fields' will fetch data from the object for all tracked data keys. */ - public function prepare_object_changes( $object, $generate_type = 'changes_only' ) { + public function prepare_object_changes( $subscription, $generate_type = 'changes_only' ) { // If the object hasn't been created yet, we can't do anything yet. We'll have to wait until after the object is saved. - if ( ! $object->get_id() ) { + if ( ! $subscription->get_id() ) { return; } $force_all_fields = 'all_fields' === $generate_type; - $changes = $object->get_changes(); - $base_data = $object->get_base_data(); - $meta_data = $object->get_meta_data(); + // @phpstan-ignore-next-line + $changes = $subscription->get_changes(); + $base_data = $subscription->get_base_data(); + // @phpstan-ignore-next-line + $meta_data = $subscription->get_meta_data(); // Deleted meta won't be included in the changes, so we need to fetch the previous value via the raw meta data. - $data_store = $object->get_data_store(); - $raw_meta_data = $data_store->read_meta( $object ); + $data_store = $subscription->get_data_store(); + $raw_meta_data = $data_store->read_meta( $subscription ); $raw_meta_key_map = wp_list_pluck( $raw_meta_data, 'meta_key' ); // Record the object ID so we know that it has been handled in $this->action_object_cache_changes(). - $this->object_changes[ $object->get_id() ] = []; + $this->object_changes[ $subscription->get_id() ] = []; foreach ( $this->data_keys as $data_key ) { // Check if the data key is a base property and if it has changed. if ( isset( $changes[ $data_key ] ) ) { - $this->object_changes[ $object->get_id() ][ $data_key ] = [ + $this->object_changes[ $subscription->get_id() ][ $data_key ] = [ 'new' => $changes[ $data_key ], 'previous' => isset( $base_data[ $data_key ] ) ? $base_data[ $data_key ] : null, 'type' => 'update', @@ -117,7 +119,7 @@ class WCS_Object_Data_Cache_Manager extends WCS_Post_Meta_Cache_Manager { continue; } elseif ( isset( $base_data[ $data_key ] ) && $force_all_fields ) { // If we're forcing all fields, fetch the base data as the new value. - $this->object_changes[ $object->get_id() ][ $data_key ] = [ + $this->object_changes[ $subscription->get_id() ][ $data_key ] = [ 'new' => $base_data[ $data_key ], 'type' => 'add', ]; @@ -135,20 +137,22 @@ class WCS_Object_Data_Cache_Manager extends WCS_Post_Meta_Cache_Manager { if ( empty( $meta->id ) ) { // If the value is being added. - $this->object_changes[ $object->get_id() ][ $data_key ] = [ + $this->object_changes[ $subscription->get_id() ][ $data_key ] = [ 'new' => $meta->value, 'type' => 'add', ]; } elseif ( $meta->get_changes() ) { // If the value is being updated. - $this->object_changes[ $object->get_id() ][ $data_key ] = [ + $this->object_changes[ $subscription->get_id() ][ $data_key ] = [ + // @phpstan-ignore-next-line 'new' => $meta->value, 'previous' => isset( $previous_meta['value'] ) ? $previous_meta['value'] : null, 'type' => 'update', ]; } elseif ( $force_all_fields ) { // If we're forcing all fields to be recorded. - $this->object_changes[ $object->get_id() ][ $data_key ] = [ + $this->object_changes[ $subscription->get_id() ][ $data_key ] = [ + // @phpstan-ignore-next-line 'new' => $meta->value, 'type' => 'add', ]; @@ -159,10 +163,10 @@ class WCS_Object_Data_Cache_Manager extends WCS_Post_Meta_Cache_Manager { } // If we got this far, then the data key is stored as meta and has been deleted. - // When meta is deleted it won't be returned by $object->get_meta_data(). So we need to check the raw meta data. + // When meta is deleted it won't be returned by $subscription->get_meta_data(). So we need to check the raw meta data. if ( in_array( $data_key, $raw_meta_key_map, true ) ) { $previous_meta = $raw_meta_data[ array_search( $data_key, $raw_meta_key_map, true ) ]->meta_value; - $this->object_changes[ $object->get_id() ][ $data_key ] = [ + $this->object_changes[ $subscription->get_id() ][ $data_key ] = [ 'previous' => $previous_meta, 'type' => 'delete', ]; @@ -316,7 +320,7 @@ class WCS_Object_Data_Cache_Manager extends WCS_Post_Meta_Cache_Manager { /** * Fetches an instance of the object with the given ID. * - * @param int $object_id The ID of the object to fetch. + * @param int $id The ID of the object to fetch. * * @return mixed The object instance, or null if it doesn't exist. */ diff --git a/includes/core/class-wcs-payment-tokens.php b/includes/core/class-wcs-payment-tokens.php index b0fd08f..47c0cb4 100644 --- a/includes/core/class-wcs-payment-tokens.php +++ b/includes/core/class-wcs-payment-tokens.php @@ -55,8 +55,8 @@ class WCS_Payment_Tokens extends WC_Payment_Tokens { /** * Enable third-party plugins to run their own updates and filter whether the token was updated or not. * - * @param bool Whether the token was updated. Default is true. - * @param WC_Subscription $subscription + * @param bool $updated Whether the token was updated. Default is true. + * @param WC_Subscription $subscription * @param WC_Payment_Token $new_token * @param WC_Payment_Token $old_token */ @@ -137,13 +137,13 @@ class WCS_Payment_Tokens extends WC_Payment_Tokens { /** * Get a list of customer payment tokens. Caches results to avoid multiple database queries per request * - * @param int (optional) The customer id - defaults to the current user. - * @param string (optional) Gateway ID for getting tokens for a specific gateway. + * @param int $customer_id (optional) The customer id - defaults to the current user. + * @param string $gateway_id (optional) Gateway ID for getting tokens for a specific gateway. * @return array of WC_Payment_Token objects. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.7 */ - public static function get_customer_tokens( $customer_id = '', $gateway_id = '' ) { - if ( '' === $customer_id ) { + public static function get_customer_tokens( $customer_id = 0, $gateway_id = '' ) { + if ( 0 === $customer_id ) { $customer_id = get_current_user_id(); } diff --git a/includes/core/class-wcs-post-meta-cache-manager.php b/includes/core/class-wcs-post-meta-cache-manager.php index eef58ed..ae252fe 100644 --- a/includes/core/class-wcs-post-meta-cache-manager.php +++ b/includes/core/class-wcs-post-meta-cache-manager.php @@ -22,8 +22,8 @@ class WCS_Post_Meta_Cache_Manager { /** * Constructor * - * @param string The post type this cache manage acts on. - * @param array The post meta keys this cache manager should act on. + * @param string $post_type The post type this cache manage acts on. + * @param array $meta_keys The post meta keys this cache manager should act on. */ public function __construct( $post_type, $meta_keys ) { $this->post_type = $post_type; @@ -48,10 +48,10 @@ class WCS_Post_Meta_Cache_Manager { add_action( 'deleted_post_meta', array( $this, 'meta_deleted' ), 10, 4 ); // Special handling for meta updates containing a previous order ID to make sure we also delete any previously linked relationship - add_action( 'update_post_metadata', array( $this, 'meta_updated_with_previous' ), 10, 5 ); + add_filter( 'update_post_metadata', array( $this, 'meta_updated_with_previous' ), 10, 5 ); // Special handling for meta deletion on all posts/orders, not a specific post/order ID - add_action( 'delete_post_metadata', array( $this, 'meta_deleted_all' ), 100, 5 ); + add_filter( 'delete_post_metadata', array( $this, 'meta_deleted_all' ), 100, 5 ); } /** diff --git a/includes/core/class-wcs-query.php b/includes/core/class-wcs-query.php index 45430ac..7d5fc54 100644 --- a/includes/core/class-wcs-query.php +++ b/includes/core/class-wcs-query.php @@ -117,7 +117,7 @@ class WCS_Query extends WC_Query { /** * Insert the new endpoint into the My Account menu. * - * @param array $items + * @param array $menu_items * @return array */ public function add_menu_items( $menu_items ) { @@ -281,8 +281,8 @@ class WCS_Query extends WC_Query { /** * Add UI option for changing Subscription endpoints in WC settings * - * @param mixed $account_settings - * @return mixed $account_settings + * @param mixed $settings + * @return mixed $settings */ public function add_endpoint_account_settings( $settings ) { $subscriptions_endpoint_setting = array( diff --git a/includes/core/class-wcs-remove-item.php b/includes/core/class-wcs-remove-item.php index 650ea30..1abbaa3 100644 --- a/includes/core/class-wcs-remove-item.php +++ b/includes/core/class-wcs-remove-item.php @@ -69,14 +69,14 @@ class WCS_Remove_Item { public static function maybe_remove_or_add_item_to_subscription() { if ( isset( $_GET['subscription_id'] ) && ( isset( $_GET['remove_item'] ) || isset( $_GET['undo_remove_item'] ) ) && isset( $_GET['_wpnonce'] ) ) { - - $subscription = wcs_is_subscription( $_GET['subscription_id'] ) ? wcs_get_subscription( $_GET['subscription_id'] ) : false; - $undo_request = isset( $_GET['undo_remove_item'] ); - $item_id = $undo_request ? $_GET['undo_remove_item'] : $_GET['remove_item']; + $subscription_id = wc_clean( wp_unslash( $_GET['subscription_id'] ) ); + $subscription = wcs_get_subscription( $subscription_id ); + $undo_request = isset( $_GET['undo_remove_item'] ); + $item_id = wc_clean( wp_unslash( $undo_request ? $_GET['undo_remove_item'] : $_GET['remove_item'] ) ); if ( false === $subscription ) { // translators: %d: subscription ID. - wc_add_notice( sprintf( _x( 'Subscription #%d does not exist.', 'hash before subscription ID', 'woocommerce-subscriptions' ), $_GET['subscription_id'] ), 'error' ); + wc_add_notice( sprintf( _x( 'Subscription #%d does not exist.', 'hash before subscription ID', 'woocommerce-subscriptions' ), $subscription_id ), 'error' ); wp_safe_redirect( wc_get_page_permalink( 'myaccount' ) ); exit; } @@ -175,7 +175,7 @@ class WCS_Remove_Item { $subscription_items = $subscription->get_items(); $response = false; - if ( ! wp_verify_nonce( $_GET['_wpnonce'], $_GET['subscription_id'] ) ) { + if ( ! wp_verify_nonce( wc_clean( wp_unslash( $_GET['_wpnonce'] ) ), wc_clean( wp_unslash( $_GET['subscription_id'] ) ) ) ) { wc_add_notice( __( 'Security error. Please contact us if you need assistance.', 'woocommerce-subscriptions' ), 'error' ); diff --git a/includes/core/class-wcs-select2.php b/includes/core/class-wcs-select2.php index 2e42227..bfb39c1 100644 --- a/includes/core/class-wcs-select2.php +++ b/includes/core/class-wcs-select2.php @@ -72,7 +72,7 @@ class WCS_Select2 { $value = wcs_json_encode( $value ); } - $html[] = $this->get_property_name( $property ) . '="' . esc_attr( $value, 'woocommerce-subscriptions' ) . '"'; + $html[] = $this->get_property_name( $property ) . '="' . esc_attr( $value ) . '"'; } return implode( ' ', $html ); @@ -87,6 +87,7 @@ class WCS_Select2 { $allowed_attributes = array_map( array( $this, 'get_property_name' ), array_keys( $this->attributes ) ); $allowed_attributes = array_fill_keys( $allowed_attributes, array() ); + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wp_kses_allow_underscores( $this->get_html(), array( diff --git a/includes/core/class-wcs-staging.php b/includes/core/class-wcs-staging.php index e9acb3f..a6fe82d 100644 --- a/includes/core/class-wcs-staging.php +++ b/includes/core/class-wcs-staging.php @@ -107,7 +107,7 @@ class WCS_Staging { if ( self::is_duplicate_site() && current_user_can( 'manage_options' ) ) { - if ( ! empty( $_REQUEST['_wcsnonce'] ) && wp_verify_nonce( $_REQUEST['_wcsnonce'], 'wcs_duplicate_site' ) && isset( $_GET['wc_subscription_duplicate_site'] ) ) { + if ( ! empty( $_REQUEST['_wcsnonce'] ) && wp_verify_nonce( wc_clean( wp_unslash( $_REQUEST['_wcsnonce'] ) ), 'wcs_duplicate_site' ) && isset( $_GET['wc_subscription_duplicate_site'] ) ) { if ( 'update' === $_GET['wc_subscription_duplicate_site'] ) { diff --git a/includes/core/class-wcs-template-loader.php b/includes/core/class-wcs-template-loader.php index 27a4ea5..75b1a11 100644 --- a/includes/core/class-wcs-template-loader.php +++ b/includes/core/class-wcs-template-loader.php @@ -367,7 +367,7 @@ class WCS_Template_Loader { * @param string $template_name * @param array $args * @param string $template_path - * @param + * @param string $default_path */ public static function handle_relocated_templates( $template, $template_name, $args, $template_path, $default_path ) { // We only want to relocate subscription template files that can't be found. diff --git a/includes/core/class-wcs-user-change-status-handler.php b/includes/core/class-wcs-user-change-status-handler.php index d8093af..39c39c3 100644 --- a/includes/core/class-wcs-user-change-status-handler.php +++ b/includes/core/class-wcs-user-change-status-handler.php @@ -28,9 +28,9 @@ class WCS_User_Change_Status_Handler { if ( isset( $_GET['change_subscription_to'], $_GET['subscription_id'], $_GET['_wpnonce'] ) && ! empty( $_GET['_wpnonce'] ) ) { $user_id = get_current_user_id(); $subscription = wcs_get_subscription( absint( $_GET['subscription_id'] ) ); - $new_status = wc_clean( $_GET['change_subscription_to'] ); + $new_status = wc_clean( wp_unslash( $_GET['change_subscription_to'] ) ); - if ( self::validate_request( $user_id, $subscription, $new_status, $_GET['_wpnonce'] ) ) { + if ( self::validate_request( $user_id, $subscription, $new_status, wc_clean( wp_unslash( $_GET['_wpnonce'] ) ) ) ) { self::change_users_subscription( $subscription, $new_status ); wp_safe_redirect( $subscription->get_view_order_url() ); @@ -45,6 +45,7 @@ class WCS_User_Change_Status_Handler { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ public static function change_users_subscription( $subscription, $new_status ) { + /** @var WC_Subscription $subscription */ $subscription = ( ! is_object( $subscription ) ) ? wcs_get_subscription( $subscription ) : $subscription; $changed = false; diff --git a/includes/core/data-stores/class-wcs-customer-store-cached-cpt.php b/includes/core/data-stores/class-wcs-customer-store-cached-cpt.php index 6fcc220..228499b 100644 --- a/includes/core/data-stores/class-wcs-customer-store-cached-cpt.php +++ b/includes/core/data-stores/class-wcs-customer-store-cached-cpt.php @@ -317,7 +317,7 @@ class WCS_Customer_Store_Cached_CPT extends WCS_Customer_Store_CPT implements WC /** * Run the update for a single item. * - * @param mixed $item The item to update. + * @param mixed $user_id The user ID to update. */ public function update_items_cache( $user_id ) { // Getting the subscription IDs also sets the cache when it's not already set diff --git a/includes/core/data-stores/class-wcs-orders-table-data-store-controller.php b/includes/core/data-stores/class-wcs-orders-table-data-store-controller.php index 02a159a..281cd8e 100644 --- a/includes/core/data-stores/class-wcs-orders-table-data-store-controller.php +++ b/includes/core/data-stores/class-wcs-orders-table-data-store-controller.php @@ -30,7 +30,7 @@ class WCS_Orders_Table_Data_Store_Controller { * @return void */ public function init_hooks() { - add_filter( 'woocommerce_subscription_data_store', array( $this, 'get_orders_table_data_store' ), 10, 2 ); + add_filter( 'woocommerce_subscription_data_store', array( $this, 'get_orders_table_data_store' ), 10 ); } /** diff --git a/includes/core/data-stores/class-wcs-orders-table-subscription-data-store.php b/includes/core/data-stores/class-wcs-orders-table-subscription-data-store.php index 0d0ef8e..531334d 100644 --- a/includes/core/data-stores/class-wcs-orders-table-subscription-data-store.php +++ b/includes/core/data-stores/class-wcs-orders-table-subscription-data-store.php @@ -58,7 +58,7 @@ class WCS_Orders_Table_Subscription_Data_Store extends \Automattic\WooCommerce\I * * All columns are inherited from orders except the `transaction_id` column isn't used for subscriptions. * - * @var \string[][] + * @var string[] */ protected $order_column_mapping = array( 'id' => array( @@ -139,7 +139,7 @@ class WCS_Orders_Table_Subscription_Data_Store extends \Automattic\WooCommerce\I * - recorded_sales * - date_completed_gmt * - * @var \string[][] + * @var string[] */ protected $operational_data_column_mapping = array( 'id' => array( 'type' => 'int' ), @@ -337,7 +337,7 @@ class WCS_Orders_Table_Subscription_Data_Store extends \Automattic\WooCommerce\I /** * Attempts to restore the specified subscription back to its original status (after having been trashed). * - * @param \WC_Subscription $order The order to be untrashed. + * @param \WC_Subscription $subscription The subscription to be untrashed. * * @return bool If the operation was successful. */ @@ -593,6 +593,7 @@ class WCS_Orders_Table_Subscription_Data_Store extends \Automattic\WooCommerce\I $is_date_prop = ( 'schedule_' === substr( $prop, 0, 9 ) ); if ( $is_date_prop ) { + // @phpstan-ignore method.notFound $meta_value = $subscription->get_date( $prop ); } else { $meta_value = $subscription->{"get_$prop"}( 'edit' ); @@ -612,6 +613,7 @@ class WCS_Orders_Table_Subscription_Data_Store extends \Automattic\WooCommerce\I if ( empty( $existing_meta_data ) ) { // If we're saving a start date for the first time and it's empty, set it to the created date as a default. if ( '_schedule_start' === $new_meta_data['key'] && empty( $new_meta_data['value'] ) ) { + // @phpstan-ignore method.notFound $new_meta_data['value'] = $subscription->get_date( 'date_created' ); } @@ -626,7 +628,7 @@ class WCS_Orders_Table_Subscription_Data_Store extends \Automattic\WooCommerce\I /** * Initializes the subscription based on data received from the database. * - * @param WC_Abstract_Order $subscription The subscription object. + * @param WC_Subscription $subscription The subscription object. * @param int $subscription_id The subscription's ID. * @param stdClass $subscription_data All the subscription's data, retrieved from the database. */ @@ -643,8 +645,8 @@ class WCS_Orders_Table_Subscription_Data_Store extends \Automattic\WooCommerce\I // Set subscription specific properties that we store in meta. $meta_data = wp_list_pluck( $subscription_data->meta_data, 'meta_value', 'meta_key' ); - $dates_to_set = []; - $props_to_set = []; + $dates_to_set = array(); + $props_to_set = array(); foreach ( $this->subscription_meta_keys_to_props as $meta_key => $prop_key ) { $is_scheduled_date = 0 === strpos( $prop_key, 'schedule' ); @@ -657,6 +659,7 @@ class WCS_Orders_Table_Subscription_Data_Store extends \Automattic\WooCommerce\I // If we're reading in the start date and it's missing, set it in memory to the created date. if ( 'schedule_start' === $prop_key && empty( $meta_data[ $meta_key ] ) ) { + // @phpstan-ignore method.notFound $meta_data[ $meta_key ] = $subscription->get_date( 'date_created' ); } @@ -673,8 +676,9 @@ class WCS_Orders_Table_Subscription_Data_Store extends \Automattic\WooCommerce\I } // Set the dates and props. - if ( $dates_to_set ) { - $subscription->update_dates( $dates_to_set ); + if ( $dates_to_set && $subscription instanceof \WC_Subscription ) { + // @phpstan-ignore method.notFound + $subscription->update_valid_dates( $dates_to_set ); } $subscription->set_props( $props_to_set ); @@ -784,6 +788,7 @@ class WCS_Orders_Table_Subscription_Data_Store extends \Automattic\WooCommerce\I $existing_meta_data = $subscription_meta_data[ $meta_key ] ?? false; $new_meta_data = [ 'key' => $meta_key, + // @phpstan-ignore method.notFound 'value' => $subscription->get_date( $date_type ), ]; @@ -794,6 +799,7 @@ class WCS_Orders_Table_Subscription_Data_Store extends \Automattic\WooCommerce\I $this->data_store_meta->add_meta( $subscription, (object) $new_meta_data ); } + // @phpstan-ignore method.notFound $dates_saved[ $date_prop ] = wcs_get_datetime_from( $subscription->get_time( $date_type ) ); } @@ -835,7 +841,7 @@ class WCS_Orders_Table_Subscription_Data_Store extends \Automattic\WooCommerce\I * * This function is hooked onto the 'woocommerce_order_table_search_query_meta_keys' filter. * - * @param array The default order search fields. + * @param array $search_fields The default order search fields. * * @return array The subscription search fields. */ diff --git a/includes/core/data-stores/class-wcs-related-order-store-cached-cpt.php b/includes/core/data-stores/class-wcs-related-order-store-cached-cpt.php index e288422..81bc4ef 100644 --- a/includes/core/data-stores/class-wcs-related-order-store-cached-cpt.php +++ b/includes/core/data-stores/class-wcs-related-order-store-cached-cpt.php @@ -147,6 +147,7 @@ class WCS_Related_Order_Store_Cached_CPT extends WCS_Related_Order_Store_CPT imp */ public function get_related_order_ids( WC_Order $subscription, $relation_type ) { $related_order_ids = $this->get_related_order_ids_from_cache( $subscription, $relation_type ); + $transient_key = null; // get_related_order_ids_from_cache() returns false if the ID is invalid. This can arise when the subscription hasn't been created yet. In any case, the related IDs should be an empty array to avoid a boolean return from this function. if ( false === $related_order_ids ) { @@ -219,8 +220,8 @@ class WCS_Related_Order_Store_Cached_CPT extends WCS_Related_Order_Store_CPT imp /** * Finds orders related to a given subscription in a given way from the cache. * - * @param WC_Subscription|int $subscription_id The Subscription ID or subscription object to fetch related orders. - * @param string $relation_type The relationship between the subscription and the orders. Must be 'renewal', 'switch' or 'resubscribe. + * @param WC_Subscription|int $subscription The subscription to fetch related orders. + * @param string $relation_type The relationship between the subscription and the orders. Must be 'renewal', 'switch' or 'resubscribe. * * @return string|array An array of related orders in the cache, or an empty string when no matching row is found for the given key, meaning it's cache is not set yet or has been deleted */ @@ -388,7 +389,7 @@ class WCS_Related_Order_Store_Cached_CPT extends WCS_Related_Order_Store_CPT imp /** * Clears all related order caches for a given subscription. * - * @param WC_Subscription|int $subscription_id The ID of a subscription that may have linked orders. + * @param WC_Subscription|int $subscription The subscription that may have linked orders. * @param string $relation_type The relationship between the subscription and the order. Must be 'renewal', 'switch' or 'resubscribe' unless custom relationships are implemented. Use 'any' to delete all cached. */ public function delete_caches_for_subscription( $subscription, $relation_type = 'any' ) { diff --git a/includes/core/data-stores/class-wcs-subscription-data-store-cpt.php b/includes/core/data-stores/class-wcs-subscription-data-store-cpt.php index 4c1bd3f..234278e 100644 --- a/includes/core/data-stores/class-wcs-subscription-data-store-cpt.php +++ b/includes/core/data-stores/class-wcs-subscription-data-store-cpt.php @@ -187,6 +187,7 @@ class WCS_Subscription_Data_Store_CPT extends WC_Order_Data_Store_CPT implements $date_type = str_replace( 'schedule_', '', $prop_key ); if ( 'start' === $date_type && ! $meta_value ) { + // @phpstan-ignore method.notFound $meta_value = $subscription->get_date( 'date_created' ); } @@ -212,6 +213,7 @@ class WCS_Subscription_Data_Store_CPT extends WC_Order_Data_Store_CPT implements $props_to_set['customer_id'] = $subscription->get_meta( '_customer_user', true ); } + // @phpstan-ignore method.notFound $subscription->update_dates( $dates_to_set ); $subscription->set_props( $props_to_set ); } @@ -341,7 +343,7 @@ class WCS_Subscription_Data_Store_CPT extends WC_Order_Data_Store_CPT implements /** * Return count of subscriptions with type. * - * @param string $type + * @param string $status * @return int * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0 */ @@ -562,18 +564,21 @@ class WCS_Subscription_Data_Store_CPT extends WC_Order_Data_Store_CPT implements $subscription_ids = array_unique( array_merge( $wpdb->get_col( - $wpdb->prepare( " - SELECT DISTINCT p1.post_id - FROM {$wpdb->postmeta} p1 - WHERE p1.meta_value LIKE '%%%s%%'", $wpdb->esc_like( wc_clean( $term ) ) ) . " AND p1.meta_key IN ('" . implode( "','", array_map( 'esc_sql', $search_fields ) ) . "')" + $wpdb->prepare( + // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.LikeWildcardsInQueryWithPlaceholder + "SELECT DISTINCT p1.post_id FROM {$wpdb->postmeta} p1 WHERE p1.meta_value LIKE '%%%s%%'", + $wpdb->esc_like( + wc_clean( $term ) + ) + ) . " AND p1.meta_key IN ('" . implode( "','", array_map( 'esc_sql', $search_fields ) ) . "')" ), $wpdb->get_col( $wpdb->prepare( " SELECT order_id FROM {$wpdb->prefix}woocommerce_order_items as order_items - WHERE order_item_name LIKE '%%%s%%' + WHERE order_item_name LIKE %s ", - $wpdb->esc_like( wc_clean( $term ) ) + '%' . $wpdb->esc_like( wc_clean( $term ) ) . '%' ) ), $wpdb->get_col( @@ -582,11 +587,11 @@ class WCS_Subscription_Data_Store_CPT extends WC_Order_Data_Store_CPT implements FROM {$wpdb->posts} p1 INNER JOIN {$wpdb->postmeta} p2 ON p1.ID = p2.post_id INNER JOIN {$wpdb->users} u ON p2.meta_value = u.ID - WHERE u.user_email LIKE '%%%s%%' + WHERE u.user_email LIKE %s AND p2.meta_key = '_customer_user' AND p1.post_type = 'shop_subscription' ", - esc_attr( $term ) + '%' . $wpdb->esc_like( wc_clean( $term ) ) . '%' ) ), $subscription_ids diff --git a/includes/core/deprecated/class-wcs-filter-deprecator.php b/includes/core/deprecated/class-wcs-filter-deprecator.php index 818f069..8dd1ed9 100644 --- a/includes/core/deprecated/class-wcs-filter-deprecator.php +++ b/includes/core/deprecated/class-wcs-filter-deprecator.php @@ -163,8 +163,7 @@ class WCS_Filter_Deprecator extends WCS_Hook_Deprecator { $original_id = self::get_order_id( $subscription ); // Now we need to find the new orders role, if the calling function is wcs_create_resubscribe_order(), the role is parent, otherwise it's child - $backtrace = debug_backtrace(); - $order_role = ( 'wcs_create_resubscribe_order' == $backtrace[1]['function'] ) ? 'parent' : 'child'; + $order_role = 'child'; // Old arg spec: $order_items, $original_order_id, $renewal_order_id, $product_id, $new_order_role if ( 'woocommerce_subscriptions_renewal_order_items' == $old_hook ) { @@ -195,9 +194,7 @@ class WCS_Filter_Deprecator extends WCS_Hook_Deprecator { $renewal_order = $new_callback_args[0]; $subscription = $new_callback_args[1]; - // Now we need to find the new orders role, if the calling function is wcs_create_resubscribe_order(), the role is parent, otherwise it's child - $backtrace = debug_backtrace(); - $order_role = ( 'wcs_create_resubscribe_order' == $backtrace[1]['function'] ) ? 'parent' : 'child'; + $order_role = 'child'; $renewal_order_id = apply_filters( $old_hook, $return_value->id, self::get_order( $subscription ), self::get_product_id( $subscription ), $order_role ); diff --git a/includes/core/gateways/class-wc-subscriptions-core-payment-gateways.php b/includes/core/gateways/class-wc-subscriptions-core-payment-gateways.php index c7ee59c..c48deee 100644 --- a/includes/core/gateways/class-wc-subscriptions-core-payment-gateways.php +++ b/includes/core/gateways/class-wc-subscriptions-core-payment-gateways.php @@ -98,7 +98,7 @@ class WC_Subscriptions_Core_Payment_Gateways { } elseif ( ! wcs_cart_contains_renewal() && ! WC_Subscriptions_Cart::cart_contains_subscription() && - ( ! isset( $_GET['order_id'] ) || ! wcs_order_contains_subscription( $_GET['order_id'] ) ) + ( ! isset( $_GET['order_id'] ) || ! wcs_order_contains_subscription( wc_clean( wp_unslash( $_GET['order_id'] ) ) ) ) ) { return $available_gateways; } @@ -115,8 +115,6 @@ class WC_Subscriptions_Core_Payment_Gateways { /** * Check the content of the cart and add required payment methods. * - * @param array $features of required features for the cart checkout. - * * @return array list of features required by cart items. */ public static function inject_payment_feature_requirements_for_cart_api() { @@ -327,7 +325,7 @@ class WC_Subscriptions_Core_Payment_Gateways { * * @param string $title Widget title. * @param array $instance Array of widget data. - * @param string $widget ID/name of the widget being displayed. + * @param string $widget_id ID/name of the widget being displayed. * * @return string */ @@ -346,7 +344,7 @@ class WC_Subscriptions_Core_Payment_Gateways { * * @param string $title Widget title. * @param array $instance Array of widget data. - * @param string $widget ID/name of the widget being displayed. + * @param string $widget_id ID/name of the widget being displayed. * * @return string */ diff --git a/includes/core/gateways/class-wc-subscriptions-gateway-restrictions-manager.php b/includes/core/gateways/class-wc-subscriptions-gateway-restrictions-manager.php index 8e45dda..9435e14 100644 --- a/includes/core/gateways/class-wc-subscriptions-gateway-restrictions-manager.php +++ b/includes/core/gateways/class-wc-subscriptions-gateway-restrictions-manager.php @@ -51,8 +51,8 @@ class WC_Subscriptions_Gateway_Restrictions_Manager { * * @since 1.5.0 * - * @param false|float The minimum amount that can be processed in the given currency. - * @param string The currency. + * @param false|float $minimum_processable_amount The minimum amount that can be processed in the given currency. + * @param string $currency The currency. */ $minimum_processable_amount = apply_filters( 'woocommerce_subscriptions_minimum_processable_recurring_amount', false, get_woocommerce_currency() ); @@ -68,4 +68,3 @@ class WC_Subscriptions_Gateway_Restrictions_Manager { } } } - diff --git a/includes/core/gateways/paypal/class-wcs-paypal.php b/includes/core/gateways/paypal/class-wcs-paypal.php index 23b1e9d..3c86398 100644 --- a/includes/core/gateways/paypal/class-wcs-paypal.php +++ b/includes/core/gateways/paypal/class-wcs-paypal.php @@ -208,7 +208,7 @@ class WCS_PayPal { } // get token to retrieve checkout details with - $token = esc_attr( $_GET['token'] ); + $token = wc_clean( wp_unslash( $_GET['token'] ) ); try { @@ -284,7 +284,7 @@ class WCS_PayPal { wc_add_notice( __( 'An error occurred, please try again or try an alternate form of payment.', 'woocommerce-subscriptions' ), 'error' ); - wp_redirect( wc_get_cart_url() ); + wp_safe_redirect( wc_get_cart_url() ); } exit; diff --git a/includes/core/gateways/paypal/includes/abstracts/abstract-wcs-sv-api-base.php b/includes/core/gateways/paypal/includes/abstracts/abstract-wcs-sv-api-base.php index 84740c8..f4ef0c7 100644 --- a/includes/core/gateways/paypal/includes/abstracts/abstract-wcs-sv-api-base.php +++ b/includes/core/gateways/paypal/includes/abstracts/abstract-wcs-sv-api-base.php @@ -299,7 +299,7 @@ abstract class WCS_SV_API_Base { * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v4.1.0 * @param string $uri current request URI - * @param \WCS_SV_API_Base class instance + * @param \WCS_SV_API_Base $this class instance */ return apply_filters( 'wc_' . $this->get_api_id() . '_api_request_uri', $uri, $this ); } @@ -335,7 +335,7 @@ abstract class WCS_SV_API_Base { * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0 * @param array $args request arguments - * @param \WCS_SV_API_Base class instance + * @param \WCS_SV_API_Base $this class instance */ return apply_filters( 'wc_' . $this->get_api_id() . '_http_request_args', $args, $this ); } diff --git a/includes/core/gateways/paypal/includes/admin/class-wcs-paypal-admin.php b/includes/core/gateways/paypal/includes/admin/class-wcs-paypal-admin.php index 63a32f2..ed5a700 100644 --- a/includes/core/gateways/paypal/includes/admin/class-wcs-paypal-admin.php +++ b/includes/core/gateways/paypal/includes/admin/class-wcs-paypal-admin.php @@ -71,7 +71,7 @@ class WCS_PayPal_Admin { */ public static function maybe_check_account() { - if ( isset( $_GET['wcs_paypal'] ) && 'check_reference_transaction_support' === $_GET['wcs_paypal'] && wp_verify_nonce( $_GET['_wpnonce'], __CLASS__ ) ) { + if ( isset( $_GET['wcs_paypal'] ) && 'check_reference_transaction_support' === $_GET['wcs_paypal'] && wp_verify_nonce( wc_clean( wp_unslash( $_GET['_wpnonce'] ) ), __CLASS__ ) ) { $redirect_url = remove_query_arg( array( 'wcs_paypal', '_wpnonce' ) ); @@ -237,7 +237,7 @@ class WCS_PayPal_Admin { public static function maybe_update_credentials_error_flag() { // Check if the API credentials are being saved - we can't do this on the 'woocommerce_update_options_payment_gateways_paypal' hook because it is triggered after 'admin_notices' - if ( ! empty( $_REQUEST['_wpnonce'] ) && wp_verify_nonce( $_REQUEST['_wpnonce'], 'woocommerce-settings' ) && isset( $_POST['woocommerce_paypal_api_username'] ) || isset( $_POST['woocommerce_paypal_api_password'] ) || isset( $_POST['woocommerce_paypal_api_signature'] ) ) { + if ( ! empty( $_REQUEST['_wpnonce'] ) && wp_verify_nonce( wc_clean( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'woocommerce-settings' ) && ( isset( $_POST['woocommerce_paypal_api_username'] ) || isset( $_POST['woocommerce_paypal_api_password'] ) || isset( $_POST['woocommerce_paypal_api_signature'] ) ) ) { $credentials_updated = false; diff --git a/includes/core/gateways/paypal/includes/class-wcs-paypal-reference-transaction-api-request.php b/includes/core/gateways/paypal/includes/class-wcs-paypal-reference-transaction-api-request.php index 2c710fd..74a4fe8 100644 --- a/includes/core/gateways/paypal/includes/class-wcs-paypal-reference-transaction-api-request.php +++ b/includes/core/gateways/paypal/includes/class-wcs-paypal-reference-transaction-api-request.php @@ -675,7 +675,7 @@ class WCS_PayPal_Reference_Transaction_API_Request { * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v3.3.0 * @param bool $skip_line_items True if line items should be skipped, false otherwise - * @param WC_Order/null $order The WC_Order object or null. + * @param WC_Order|null $order The WC_Order object or null. */ return apply_filters( 'wcs_paypal_reference_transaction_skip_line_items', $skip_line_items, $order ); } diff --git a/includes/core/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php b/includes/core/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php index 982f669..7446b7b 100644 --- a/includes/core/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php +++ b/includes/core/gateways/paypal/includes/class-wcs-paypal-standard-ipn-handler.php @@ -678,8 +678,8 @@ class WCS_PayPal_Standard_IPN_Handler extends WC_Gateway_Paypal_IPN_Handler { * Used when switching payment methods with PayPal Standard to make sure that * the old subscription's profile ID is cancelled, not the new one. * - * @param WC_Subscription A subscription object - * @param string A PayPal Subscription Profile ID + * @param WC_Subscription $subscription A subscription object + * @param string $old_paypal_subscriber_id A PayPal Subscription Profile ID * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ protected static function cancel_subscription( $subscription, $old_paypal_subscriber_id ) { @@ -733,9 +733,9 @@ class WCS_PayPal_Standard_IPN_Handler extends WC_Gateway_Paypal_IPN_Handler { /** * Get an order associated with a subscription that has a specified transaction id. * - * @param WC_Subscription object $subscription + * @param WC_Subscription $subscription * @param int $transaction_id Id from transaction details as provided by PayPal - * @param array|string Order type we want. Defaults to any. + * @param array|string $order_types Order type we want. Defaults to any. * * @return WC_Order|null If order with that transaction id, WC_Order object, otherwise null * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.4.3 @@ -757,7 +757,7 @@ class WCS_PayPal_Standard_IPN_Handler extends WC_Gateway_Paypal_IPN_Handler { /** * Get a renewal order associated with a subscription that has a specified transaction id. * - * @param WC_Subscription object $subscription + * @param WC_Subscription $subscription * @param int $transaction_id Id from transaction details as provided by PayPal * @return WC_Order|null If order with that transaction id, WC_Order object, otherwise null * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.1 @@ -769,7 +769,7 @@ class WCS_PayPal_Standard_IPN_Handler extends WC_Gateway_Paypal_IPN_Handler { /** * Get a parent order associated with a subscription that has a specified transaction id. * - * @param WC_Subscription object $subscription + * @param WC_Subscription $subscription * @param int $transaction_id Id from transaction details as provided by PayPal * * @return WC_Order|null If order with that transaction id, WC_Order object, otherwise null diff --git a/includes/core/gateways/paypal/includes/class-wcs-paypal-standard-switcher.php b/includes/core/gateways/paypal/includes/class-wcs-paypal-standard-switcher.php index d7c9caa..0d757d1 100644 --- a/includes/core/gateways/paypal/includes/class-wcs-paypal-standard-switcher.php +++ b/includes/core/gateways/paypal/includes/class-wcs-paypal-standard-switcher.php @@ -226,7 +226,7 @@ class WCS_PayPal_Standard_Switcher { */ public static function get_available_payment_gateways( $available_gateways ) { - if ( ! is_wc_endpoint_url( 'order-pay' ) && ( wcs_cart_contains_switches() || ( isset( $_GET['order_id'] ) && wcs_order_contains_switch( $_GET['order_id'] ) ) ) ) { + if ( ! is_wc_endpoint_url( 'order-pay' ) && ( wcs_cart_contains_switches() || ( isset( $_GET['order_id'] ) && wcs_order_contains_switch( wc_clean( wp_unslash( $_GET['order_id'] ) ) ) ) ) ) { foreach ( $available_gateways as $gateway_id => $gateway ) { if ( 'paypal' == $gateway_id && false == WCS_PayPal::are_reference_transactions_enabled() ) { unset( $available_gateways[ $gateway_id ] ); diff --git a/includes/core/gateways/paypal/includes/deprecated/class-wc-paypal-standard-subscriptions.php b/includes/core/gateways/paypal/includes/deprecated/class-wc-paypal-standard-subscriptions.php index 55a7733..442c5ed 100644 --- a/includes/core/gateways/paypal/includes/deprecated/class-wc-paypal-standard-subscriptions.php +++ b/includes/core/gateways/paypal/includes/deprecated/class-wc-paypal-standard-subscriptions.php @@ -91,10 +91,11 @@ class WC_PayPal_Standard_Subscriptions { /** * Returns a PayPal Subscription ID/Recurring Payment Profile ID based on a user ID and subscription key * - * @param WC_Order|WC_Subscription A WC_Order object or child object (i.e. WC_Subscription) + * @param WC_Order|WC_Subscription $order_id A WC_Order object or child object (i.e. WC_Subscription) + * @param int $product_id The product ID. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.1 */ - public static function get_subscriptions_paypal_id( $order_id, $product_id = '' ) { + public static function get_subscriptions_paypal_id( $order_id, $product_id = 0 ) { _deprecated_function( __METHOD__, '2.0', 'wcs_get_paypal_id( $order_id )' ); return wcs_get_paypal_id( $order_id ); } @@ -176,9 +177,12 @@ class WC_PayPal_Standard_Subscriptions { /** * When a store manager or user cancels a subscription in the store, also cancel the subscription with PayPal. * + * @param WC_Order $order A WC_Order object. + * @param int $product_id The ID of the product. + * @param string $profile_id The ID of the profile. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.1 */ - public static function cancel_subscription_with_paypal( $order, $product_id = '', $profile_id = '' ) { + public static function cancel_subscription_with_paypal( $order, $product_id = 0, $profile_id = '' ) { _deprecated_function( __METHOD__, '2.0', 'WCS_PayPal_Status_Manager::cancel_subscription( $subscription )' ); foreach ( wcs_get_subscriptions_for_order( $order, array( 'order_type' => 'parent' ) ) as $subscription ) { self::change_subscription_status( $profile_id, 'Cancel', $subscription ); diff --git a/includes/core/gateways/paypal/includes/wcs-paypal-functions.php b/includes/core/gateways/paypal/includes/wcs-paypal-functions.php index 7f48c9b..bd1c96c 100644 --- a/includes/core/gateways/paypal/includes/wcs-paypal-functions.php +++ b/includes/core/gateways/paypal/includes/wcs-paypal-functions.php @@ -17,7 +17,7 @@ if ( ! defined( 'ABSPATH' ) ) { /** * Returns a PayPal Subscription ID or Billing Agreement ID use to process payment for a given subscription or order. * - * @param int The ID of a WC_Order or WC_Subscription object + * @param int|object $order The ID of a WC_Order or WC_Subscription object * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ function wcs_get_paypal_id( $order ) { @@ -32,8 +32,8 @@ function wcs_get_paypal_id( $order ) { /** * Stores a PayPal Standard Subscription ID or Billing Agreement ID in the post meta of a given order and the user meta of the order's user. * - * @param int|object A WC_Order or WC_Subscription object or the ID of a WC_Order or WC_Subscription object - * @param string A PayPal Standard Subscription ID or Express Checkout Billing Agreement ID + * @param int|WC_Order|WC_Subscription $order A WC_Order or WC_Subscription object or the ID of a WC_Order or WC_Subscription object + * @param string $paypal_subscription_id A PayPal Standard Subscription ID or Express Checkout Billing Agreement ID * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ function wcs_set_paypal_id( $order, $paypal_subscription_id ) { @@ -65,6 +65,8 @@ function wcs_set_paypal_id( $order, $paypal_subscription_id ) { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ function wcs_is_paypal_profile_a( $profile_id, $profile_type ) { + $profile_id = (string) $profile_id; + $profile_type = (string) $profile_type; if ( 'billing_agreement' === $profile_type && 'B-' == substr( $profile_id, 0, 2 ) ) { $is_a = true; @@ -160,9 +162,8 @@ function wcs_calculate_paypal_trial_periods_until( $future_timestamp ) { * like from WC_Subscriptions_Product::is_purchasable() and WC_Product_Subscription_Variation::is_purchasable(), * both of which are called within WC_Cart::get_cart_from_session(), which is run before query vars are setup. * - * @return 2.0.13 * @return bool **/ function wcs_is_paypal_api_page() { - return ( false !== strpos( $_SERVER['REQUEST_URI'], 'wc-api/wcs_paypal' ) ); + return ( false !== strpos( wc_clean( wp_unslash( $_SERVER['REQUEST_URI'] ) ), 'wc-api/wcs_paypal' ) ); } diff --git a/includes/core/legacy/class-wc-product-variable-subscription-legacy.php b/includes/core/legacy/class-wc-product-variable-subscription-legacy.php index 85eafb0..e26f899 100644 --- a/includes/core/legacy/class-wc-product-variable-subscription-legacy.php +++ b/includes/core/legacy/class-wc-product-variable-subscription-legacy.php @@ -210,10 +210,10 @@ class WC_Product_Variable_Subscription_Legacy extends WC_Product_Variable_Subscr /** * Sync variable product prices with the children lowest/highest prices. * - * @access public + * @param int $product_id The ID of the product. * @return void */ - public function variable_product_sync( $product_id = '' ) { + public function variable_product_sync( $product_id = 0 ) { WC_Product_Variable::variable_product_sync( $product_id ); diff --git a/includes/core/privacy/class-wcs-privacy-background-updater.php b/includes/core/privacy/class-wcs-privacy-background-updater.php index 39b6e23..e153e61 100644 --- a/includes/core/privacy/class-wcs-privacy-background-updater.php +++ b/includes/core/privacy/class-wcs-privacy-background-updater.php @@ -62,7 +62,7 @@ class WCS_Privacy_Background_Updater { * Schedule subscription related order anonymization, if it's not already scheduled. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.20 - * @param int The subscription ID. + * @param int $subscription_id The subscription ID. */ protected function schedule_subscription_orders_anonymization( $subscription_id ) { $action_args = array( 'subscription_id' => intval( $subscription_id ) ); @@ -76,7 +76,7 @@ class WCS_Privacy_Background_Updater { * Unschedule a specific subscription's related order anonymization hook. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.20 - * @param int The subscription ID. + * @param int $subscription_id The subscription ID. */ protected function unschedule_subscription_orders_anonymization( $subscription_id ) { as_unschedule_action( $this->subscription_orders_anonymization_hook, array( 'subscription_id' => intval( $subscription_id ) ) ); @@ -86,7 +86,7 @@ class WCS_Privacy_Background_Updater { * Schedule a specific order's anonymization action. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.20 - * @param int The order ID. + * @param int $order_id The order ID. */ protected function schedule_order_anonymization( $order_id ) { as_schedule_single_action( time(), $this->order_anonymization_hook, array( 'order_id' => intval( $order_id ) ) ); @@ -96,7 +96,7 @@ class WCS_Privacy_Background_Updater { * Check if an order has a scheduled anonymization action. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.20 - * @param int The order ID. + * @param int $order_id The order ID. * @return bool Whether the order has a scheduled anonymization action. */ protected function order_anonymization_is_scheduled( $order_id ) { @@ -209,7 +209,7 @@ class WCS_Privacy_Background_Updater { /** * Anonymize an order. * - * @param int The ID of the order to be anonymized. + * @param int $order_id The ID of the order to be anonymized. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.20 */ public function anonymize_order( $order_id ) { diff --git a/includes/core/privacy/class-wcs-privacy-erasers.php b/includes/core/privacy/class-wcs-privacy-erasers.php index d54886e..f8063b6 100644 --- a/includes/core/privacy/class-wcs-privacy-erasers.php +++ b/includes/core/privacy/class-wcs-privacy-erasers.php @@ -161,7 +161,7 @@ class WCS_Privacy_Erasers { * Expose a way to control the anonymized value of a prop via 3rd party code. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.20 - * @param bool $anonymized_data Value of this prop after anonymization. + * @param string $anonymized_data Value of this prop after anonymization. * @param string $prop Name of the prop being removed. * @param string $value Current value of the data. * @param string $data_type Type of data. diff --git a/includes/core/privacy/class-wcs-privacy.php b/includes/core/privacy/class-wcs-privacy.php index d20b381..8a3d8aa 100644 --- a/includes/core/privacy/class-wcs-privacy.php +++ b/includes/core/privacy/class-wcs-privacy.php @@ -69,10 +69,10 @@ class WCS_Privacy extends WC_Abstract_Privacy { add_filter( 'woocommerce_account_settings', array( __CLASS__, 'add_subscription_data_retention_settings' ) ); // Attach callbacks to prevent subscription related orders being trashed or anonymized - add_filter( 'woocommerce_trash_pending_orders_query_args', array( __CLASS__, 'remove_subscription_orders_from_anonymization_query' ), 10, 2 ); - add_filter( 'woocommerce_trash_failed_orders_query_args', array( __CLASS__, 'remove_subscription_orders_from_anonymization_query' ), 10, 2 ); - add_filter( 'woocommerce_trash_cancelled_orders_query_args', array( __CLASS__, 'remove_subscription_orders_from_anonymization_query' ), 10, 2 ); - add_filter( 'woocommerce_anonymize_completed_orders_query_args', array( __CLASS__, 'remove_subscription_orders_from_anonymization_query' ), 10, 2 ); + add_filter( 'woocommerce_trash_pending_orders_query_args', array( __CLASS__, 'remove_subscription_orders_from_anonymization_query' ) ); + add_filter( 'woocommerce_trash_failed_orders_query_args', array( __CLASS__, 'remove_subscription_orders_from_anonymization_query' ) ); + add_filter( 'woocommerce_trash_cancelled_orders_query_args', array( __CLASS__, 'remove_subscription_orders_from_anonymization_query' ) ); + add_filter( 'woocommerce_anonymize_completed_orders_query_args', array( __CLASS__, 'remove_subscription_orders_from_anonymization_query' ) ); add_action( 'woocommerce_cleanup_personal_data', array( $this, 'queue_cleanup_personal_data' ) ); diff --git a/includes/core/upgrades/class-wc-subscriptions-upgrader.php b/includes/core/upgrades/class-wc-subscriptions-upgrader.php index cfc9ff7..5881080 100644 --- a/includes/core/upgrades/class-wc-subscriptions-upgrader.php +++ b/includes/core/upgrades/class-wc-subscriptions-upgrader.php @@ -8,29 +8,58 @@ * the data be upgraded to the new schema without hassle. A hassle could easily occur if 100,000 orders were being * modified - memory exhaustion, script time out etc. * + * ⚠️ Since 7.7.0, when triggering migrations and upgrade routines we should reference self::$active_plugin_version + * (which corresponds with the actual plugin version) and not self::$active_core_library_version (which is the + * Subscriptions Core library version, and therefore a historic side-effect of a time when development was split + * across two repositories). + * * @author Prospress * @category Admin * @package WooCommerce Subscriptions/Admin/Upgrades * @version 1.0.0 - Migrated from WooCommerce Subscriptions v2.0.0 * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 + * @since 7.7.0 Added support for upgrades based on the plugin version number, as opposed to the core library version number. */ class WC_Subscriptions_Upgrader { + /** + * @var string The database-persisted version number of the Subscriptions Core framework. Retained to support historic migrations. + * @since 7.7.0 + */ + private static string $stored_core_library_version; /** - * @var string The database version of Subscriptions. + * @var string The database-persisted version number of WooCommerce Subscriptions. + * @since 7.7.0 */ - private static $active_version; + private static string $stored_plugin_version; + + /** + * Indicates if plugin upgrade routines should potentially be triggered. + * + * @var bool + * @since 7.7.0 + */ + private static bool $plugin_upgrades_may_be_needed = false; /** * @var string The minimum supported version that this class can upgrade from. */ private static $minimum_supported_version = '3.0'; + /** + * Indicates if core library upgrade routines should potentially be triggered. + * + * @var bool + * @since 7.7.0 + */ + private static bool $core_library_upgrades_may_be_needed = false; + /** * @var array An array of WCS_Background_Updater objects used to run upgrade scripts in the background. */ protected static $background_updaters = array(); + /** * Deprecated variables. * @@ -49,12 +78,14 @@ class WC_Subscriptions_Upgrader { * @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::$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(), '<' ); + self::$stored_core_library_version = (string) get_option( WC_Subscriptions_Admin::$option_prefix . '_active_version', '0' ); + self::$stored_plugin_version = (string) get_option( WC_Subscriptions::PLUGIN_VERSION_OPTION_NAME, '0' ); + self::$about_page_url = admin_url( 'admin.php?page=wc-admin' ); + self::$plugin_upgrades_may_be_needed = version_compare( self::$stored_plugin_version, WC_Subscriptions::$version, '<' ); + self::$core_library_upgrades_may_be_needed = version_compare( self::$stored_core_library_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, '<=' ) ) { + if ( '0' !== self::$stored_core_library_version && version_compare( self::$stored_core_library_version, self::$minimum_supported_version, '<=' ) ) { add_action( 'admin_notices', function () { @@ -64,7 +95,7 @@ class WC_Subscriptions_Upgrader { return; } - if ( @current_user_can( 'activate_plugins' ) && $version_out_of_date ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors + if ( @current_user_can( 'activate_plugins' ) && ( self::$plugin_upgrades_may_be_needed || self::$core_library_upgrades_may_be_needed ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors // Run upgrades as soon as admin hits site add_action( 'wp_loaded', [ __CLASS__, 'upgrade' ], 11 ); } @@ -78,16 +109,53 @@ class WC_Subscriptions_Upgrader { * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.2 */ public static function upgrade() { - global $wpdb; - - update_option( WC_Subscriptions_Admin::$option_prefix . '_previous_version', self::$active_version ); + update_option( WC_Subscriptions_Admin::$option_prefix . '_previous_version', self::$stored_core_library_version ); + update_option( WC_Subscriptions::PLUGIN_VERSION_OPTION_NAME . '_previous', self::$stored_plugin_version ); /** - * before upgrade hook. + * Runs before the plugin upgrade process begins. + * + * Note that, since 7.7.0, the plugin version and active plugin version are the values we are most intereted in. + * The core library and active core library version numbers are retained to support migrations from historic + * versions of the plugin. + * + * @since 7.7.0 Added $plugin_version and $active_plugin_version. + * + * @param string $core_library_version The current Subscriptions Core library version number. + * @param string $stored_core_library_version The database-persisted Subscriptions Core library version number (what we are updating from). + * @param string $plugin_version The current plugin version number. + * @param string $stored_plugin_version The database-persisted plugin version number (what we are updating from). */ - do_action( 'woocommerce_subscriptions_before_upgrade', WC_Subscriptions_Core_Plugin::instance()->get_library_version(), self::$active_version ); + do_action( + 'woocommerce_subscriptions_before_upgrade', + WC_Subscriptions_Core_Plugin::instance()->get_library_version(), + self::$stored_core_library_version, + WC_Subscriptions::$version, + self::$stored_plugin_version, + ); - if ( '0' === self::$active_version ) { + if ( self::$core_library_upgrades_may_be_needed ) { + self::legacy_core_library_upgrades(); + } + + self::plugin_upgrades(); + self::upgrade_complete(); + } + + /** + * This method contains migrations for changes introduced before WooCommerce Subscriptions 7.7.0. + * + * The version numbers tested here are core library version numbers. For any new migrations or upgrade routines, + * we should use the actual plugin version number. This boils down to one simple rule: + * + * ⚠️ New migrations/upgrade routines should not be added to this method. + * + * @return void + */ + private static function legacy_core_library_upgrades(): void { + global $wpdb; + + if ( '0' === self::$stored_core_library_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' ); @@ -106,39 +174,53 @@ class WC_Subscriptions_Upgrader { } // Delete old subscription period string ranges transients. - if ( version_compare( self::$active_version, '3.0.10', '<' ) ) { + if ( version_compare( self::$stored_core_library_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-%'" ); } // When upgrading from version 3.0.12, delete `switch_totals_calc_base_length` meta from product post meta as it was saved rather than set in memory. - if ( version_compare( self::$active_version, '3.0.12', '==' ) ) { + if ( version_compare( self::$stored_core_library_version, '3.0.12', '==' ) ) { $deleted_rows = $wpdb->query( "DELETE FROM {$wpdb->postmeta} WHERE `meta_key` = '_switch_totals_calc_base_length'" ); } - if ( version_compare( self::$active_version, '3.1.0', '<' ) ) { + if ( version_compare( self::$stored_core_library_version, '3.1.0', '<' ) ) { // Upon upgrading to 3.1.0 from a version after 3.0.10, repair subscriptions _subtracted_base_location_tax line item meta. - if ( version_compare( self::$active_version, '3.0.10', '>=' ) ) { + if ( version_compare( self::$stored_core_library_version, '3.0.10', '>=' ) ) { self::$background_updaters['3.1']['subtracted_base_tax_repair']->schedule_repair(); } WCS_Upgrade_3_1_0::migrate_subscription_webhooks_using_api_version_3(); } - if ( version_compare( self::$active_version, '6.8.0', '<' ) ) { + if ( version_compare( self::$stored_core_library_version, '6.8.0', '<' ) ) { // Upon upgrading to 6.8.0 delete the 'wcs_cleanup_big_logs' WP Cron job that is no longer used. wp_unschedule_hook( 'wcs_cleanup_big_logs' ); } - if ( version_compare( self::$active_version, '8.0.0', '<' ) ) { + if ( version_compare( self::$stored_core_library_version, '8.0.0', '<' ) ) { // As of Subscriptions 7.2.0 (Core 8.0.0), admin notices are stored one transient per-user. delete_transient( '_wcs_admin_notices' ); } - if ( version_compare( self::$active_version, '8.3.0', '<' ) ) { + if ( version_compare( self::$stored_core_library_version, '8.3.0', '<' ) ) { WCS_Upgrade_8_3_0::migrate_subscription_email_templates(); } + } - self::upgrade_complete(); + /** + * Plugin upgrades should be triggered from this method. + * + * Here is an example of doing some work when the user updates to 8.0.0: + * + * if ( version_compare( self::$stored_plugin_version, '8.0.0', '<' ) ) { + * self::do_something_when_updating_to_8_0_0(); + * } + * + * @return void + */ + private static function plugin_upgrades(): void { + // This method is currently just a placeholder. Once we're ready to add a migration in a future release, this + // comment can of course be deleted. } /** @@ -148,7 +230,29 @@ class WC_Subscriptions_Upgrader { */ 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 ); + update_option( WC_Subscriptions::PLUGIN_VERSION_OPTION_NAME, WC_Subscriptions::$version ); + + /** + * Runs after plugin upgrades have been set in motion. + * + * Note that, since 7.7.0, the plugin version and active plugin version are the values we are most interested in. + * The core library and active core library version numbers are retained only to support migrations from historic + * versions of the plugin. + * + * @since 7.7.0 Added $plugin_version and $active_plugin_version. + * + * @param string $core_library_version The current Subscriptions Core library version number. + * @param string $stored_core_library_version The database-persisted Subscriptions Core library version number (what we are updating from). + * @param string $plugin_version The current plugin version number. + * @param string $stored_plugin_version The database-persisted plugin version number (what we are updating from). + */ + do_action( + 'woocommerce_subscriptions_upgraded', + WC_Subscriptions_Core_Plugin::instance()->get_library_version(), + self::$stored_core_library_version, + WC_Subscriptions::$version, + self::$stored_plugin_version, + ); } /** @@ -216,7 +320,7 @@ class WC_Subscriptions_Upgrader { // 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', '<' ) ) { + } elseif ( version_compare( self::$stored_core_library_version, '2.4.0', '<' ) ) { self::$background_updaters['2.4']['subscription_post_author']->schedule_repair(); } } @@ -244,7 +348,7 @@ class WC_Subscriptions_Upgrader { 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() ), '>' ) ) { + if ( ! version_compare( wcs_get_minor_version_string( self::$stored_core_library_version ), wcs_get_minor_version_string( WC_Subscriptions_Core_Plugin::instance()->get_library_version() ), '>' ) ) { return; } @@ -255,7 +359,7 @@ class WC_Subscriptions_Upgrader { 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 . '', + '' . self::$stored_core_library_version . '', '' . WC_Subscriptions_Core_Plugin::instance()->get_library_version() . '', '', '', @@ -338,7 +442,7 @@ class WC_Subscriptions_Upgrader { 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']; + $_GET['wcs_upgrade_step'] = ( ! isset( $_GET['wcs_upgrade_step'] ) ) ? 0 : wc_clean( wp_unslash( $_GET['wcs_upgrade_step'] ) ); switch ( (int) $_GET['wcs_upgrade_step'] ) { case 1: @@ -375,7 +479,7 @@ class WC_Subscriptions_Upgrader { self::set_upgrade_limits(); - WCS_Upgrade_Logger::add( sprintf( 'Starting upgrade step: %s', $_POST['upgrade_step'] ) ); + WCS_Upgrade_Logger::add( sprintf( 'Starting upgrade step: %s', wc_clean( wp_unslash( $_POST['upgrade_step'] ) ) ) ); if ( ini_get( 'max_execution_time' ) < 600 ) { @set_time_limit( 600 ); @@ -428,7 +532,7 @@ class WC_Subscriptions_Upgrader { } catch ( Exception $e ) { - WCS_Upgrade_Logger::add( sprintf( 'Error on upgrade step: %s. Error: %s', $_POST['upgrade_step'], $e->getMessage() ) ); + WCS_Upgrade_Logger::add( sprintf( 'Error on upgrade step: %s. Error: %s', wc_clean( wp_unslash( $_POST['upgrade_step'] ) ), $e->getMessage() ) ); $results = array( 'upgraded_count' => 0, @@ -474,7 +578,7 @@ class WC_Subscriptions_Upgrader { } catch ( Exception $e ) { - WCS_Upgrade_Logger::add( sprintf( 'Error on upgrade step: %s. Error: %s', $_POST['upgrade_step'], $e->getMessage() ) ); + WCS_Upgrade_Logger::add( sprintf( 'Error on upgrade step: %s. Error: %s', wc_clean( wp_unslash( $_POST['upgrade_step'] ) ), $e->getMessage() ) ); $results = array( 'repaired_count' => 0, @@ -501,9 +605,10 @@ class WC_Subscriptions_Upgrader { } } - WCS_Upgrade_Logger::add( sprintf( 'Completed upgrade step: %s', $_POST['upgrade_step'] ) ); + WCS_Upgrade_Logger::add( sprintf( 'Completed upgrade step: %s', wc_clean( wp_unslash( $_POST['upgrade_step'] ) ) ) ); header( 'Content-Type: application/json; charset=utf-8' ); + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo wcs_json_encode( $results ); exit(); } @@ -517,7 +622,7 @@ class WC_Subscriptions_Upgrader { 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', '<' ) ) { + if ( '0' !== self::$stored_core_library_version && version_compare( self::$stored_core_library_version, '1.2', '<' ) ) { WCS_Upgrade_1_2::init(); self::generate_renewal_orders(); update_option( WC_Subscriptions_Admin::$option_prefix . '_active_version', '1.2' ); @@ -525,14 +630,14 @@ class WC_Subscriptions_Upgrader { } // Add Variable Subscription product type term - if ( '0' != self::$active_version && version_compare( self::$active_version, '1.3', '<' ) ) { + if ( '0' !== self::$stored_core_library_version && version_compare( self::$stored_core_library_version, '1.3', '<' ) ) { WCS_Upgrade_1_3::init(); update_option( WC_Subscriptions_Admin::$option_prefix . '_active_version', '1.3' ); $upgraded_versions .= '1.3 & '; } // Moving subscription meta out of user meta and into item meta - if ( '0' != self::$active_version && version_compare( self::$active_version, '1.4', '<' ) ) { + if ( '0' !== self::$stored_core_library_version && version_compare( self::$stored_core_library_version, '1.4', '<' ) ) { WCS_Upgrade_1_4::init(); update_option( WC_Subscriptions_Admin::$option_prefix . '_active_version', '1.4' ); $upgraded_versions .= '1.4.'; @@ -700,7 +805,7 @@ class WC_Subscriptions_Upgrader { 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 ); + wp_enqueue_style( 'woocommerce-subscriptions-about', WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory_url( 'assets/css/about.css' ), array(), self::$stored_core_library_version ); } /** @@ -719,7 +824,7 @@ class WC_Subscriptions_Upgrader { public static function about_screen() { wcs_deprecated_function( __METHOD__, 'subscriptions-core 7.7.0' ); - $active_version = self::$active_version; + $active_version = self::$stored_core_library_version; $settings_page = admin_url( 'admin.php?page=wc-settings&tab=subscriptions' ); include_once( dirname( __FILE__ ) . '/templates/wcs-about.php' ); @@ -765,12 +870,12 @@ class WC_Subscriptions_Upgrader { $query = self::get_subscription_query(); + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $wpdb->get_results( $query ); return $wpdb->num_rows; } - /** * Single source of truth for the query * @param integer $limit the number of subscriptions to get @@ -782,30 +887,34 @@ class WC_Subscriptions_Upgrader { if ( null === $batch_size ) { $select = 'SELECT DISTINCT items.order_item_id'; - $limit = ''; + $limit = ''; } else { $select = 'SELECT meta.*, items.*'; - $limit = sprintf( ' LIMIT 0, %d', $batch_size ); + $limit = sprintf( ' LIMIT 0, %d', $batch_size ); } - $query = sprintf( "%s FROM `{$wpdb->prefix}woocommerce_order_itemmeta` AS meta - LEFT JOIN `{$wpdb->prefix}woocommerce_order_items` AS items USING (order_item_id) - LEFT JOIN ( - SELECT a.order_item_id FROM `{$wpdb->prefix}woocommerce_order_itemmeta` AS a + $query = sprintf( + "%s FROM `{$wpdb->prefix}woocommerce_order_itemmeta` AS meta + LEFT JOIN `{$wpdb->prefix}woocommerce_order_items` AS items USING (order_item_id) LEFT JOIN ( - SELECT `{$wpdb->prefix}woocommerce_order_itemmeta`.order_item_id FROM `{$wpdb->prefix}woocommerce_order_itemmeta` - WHERE `{$wpdb->prefix}woocommerce_order_itemmeta`.meta_key = '_subscription_status' - ) AS s - USING (order_item_id) - WHERE 1=1 - AND a.order_item_id = s.order_item_id - AND a.meta_key = '_subscription_start_date' - ORDER BY CASE WHEN CAST(a.meta_value AS DATETIME) IS NULL THEN 1 ELSE 0 END, CAST(a.meta_value AS DATETIME) ASC - %s - ) AS a3 USING (order_item_id) - WHERE meta.meta_key REGEXP '_subscription_(.*)|_product_id|_variation_id' - AND meta.order_item_id = a3.order_item_id - AND items.order_item_id IS NOT NULL", $select, $limit ); + SELECT a.order_item_id FROM `{$wpdb->prefix}woocommerce_order_itemmeta` AS a + LEFT JOIN ( + SELECT `{$wpdb->prefix}woocommerce_order_itemmeta`.order_item_id FROM `{$wpdb->prefix}woocommerce_order_itemmeta` + WHERE `{$wpdb->prefix}woocommerce_order_itemmeta`.meta_key = '_subscription_status' + ) AS s + USING (order_item_id) + WHERE 1=1 + AND a.order_item_id = s.order_item_id + AND a.meta_key = '_subscription_start_date' + ORDER BY CASE WHEN CAST(a.meta_value AS DATETIME) IS NULL THEN 1 ELSE 0 END, CAST(a.meta_value AS DATETIME) ASC + %s + ) AS a3 USING (order_item_id) + WHERE meta.meta_key REGEXP '_subscription_(.*)|_product_id|_variation_id' + AND meta.order_item_id = a3.order_item_id + AND items.order_item_id IS NOT NULL", + $select, + $limit + ); return $query; } @@ -908,7 +1017,7 @@ class WC_Subscriptions_Upgrader { $action = 'wcs_external_cache_warning'; // First, check if the notice is being dismissed. - if ( isset( $_GET[ $action ], $_GET[ $nonce ] ) && wp_verify_nonce( $_GET[ $nonce ], $action ) ) { + if ( isset( $_GET[ $action ], $_GET[ $nonce ] ) && wp_verify_nonce( wc_clean( wp_unslash( $_GET[ $nonce ] ) ), $action ) ) { delete_option( $option_name ); return; } diff --git a/includes/core/upgrades/class-wcs-repair-subtracted-base-tax-line-item-meta.php b/includes/core/upgrades/class-wcs-repair-subtracted-base-tax-line-item-meta.php index 535faf2..d86656d 100644 --- a/includes/core/upgrades/class-wcs-repair-subtracted-base-tax-line-item-meta.php +++ b/includes/core/upgrades/class-wcs-repair-subtracted-base-tax-line-item-meta.php @@ -56,10 +56,14 @@ class WCS_Repair_Subtracted_Base_Tax_Line_Item_Meta extends WCS_Background_Repai $offset = ( $page - 1 ) * $limit; return $wpdb->get_col( - "SELECT DISTINCT order_item_id - FROM {$wpdb->prefix}woocommerce_order_itemmeta - WHERE `meta_key` = '_subtracted_base_location_tax' - LIMIT {$offset}, {$limit}" + $wpdb->prepare( + "SELECT DISTINCT order_item_id + FROM {$wpdb->prefix}woocommerce_order_itemmeta + WHERE `meta_key` = '_subtracted_base_location_tax' + LIMIT %d, %d", + $offset, + $limit + ) ); } diff --git a/includes/core/upgrades/class-wcs-upgrade-1-4.php b/includes/core/upgrades/class-wcs-upgrade-1-4.php index dbdfcce..5d1c491 100644 --- a/includes/core/upgrades/class-wcs-upgrade-1-4.php +++ b/includes/core/upgrades/class-wcs-upgrade-1-4.php @@ -26,8 +26,6 @@ class WCS_Upgrade_1_4 { global $wpdb; $subscriptions_meta_key = $wpdb->get_blog_prefix() . 'woocommerce_subscriptions'; - $order_items_table = $wpdb->get_blog_prefix() . 'woocommerce_order_items'; - $order_item_meta_table = $wpdb->get_blog_prefix() . 'woocommerce_order_itemmeta'; // Get the IDs of all users who have a subscription $users_to_upgrade = get_users( @@ -69,7 +67,7 @@ class WCS_Upgrade_1_4 { $wpdb->query( $wpdb->prepare( - "INSERT INTO $order_item_meta_table (order_item_id, meta_key, meta_value) + "INSERT INTO {$wpdb->prefix}woocommerce_order_itemmeta (order_item_id, meta_key, meta_value) VALUES (%d,%s,%s), (%d,%s,%s), @@ -128,9 +126,9 @@ class WCS_Upgrade_1_4 { // Get the ID of all orders for a subscription with a free trial and no sign-up fee $order_ids = $wpdb->get_col( - "SELECT order_items.order_id FROM $order_items_table AS order_items - LEFT JOIN $order_item_meta_table AS itemmeta USING (order_item_id) - LEFT JOIN $order_item_meta_table AS itemmeta2 USING (order_item_id) + "SELECT order_items.order_id FROM {$wpdb->prefix}woocommerce_order_itemmeta AS order_items + LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS itemmeta USING (order_item_id) + LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS itemmeta2 USING (order_item_id) WHERE itemmeta.meta_key = '_subscription_trial_length' AND itemmeta.meta_value > 0 AND itemmeta2.meta_key = '_subscription_sign_up_fee' @@ -145,19 +143,18 @@ class WCS_Upgrade_1_4 { "UPDATE $wpdb->postmeta SET `meta_value` = 0 WHERE `meta_key` IN ( '_order_total', '_order_tax', '_order_shipping_tax', '_order_shipping', '_order_discount', '_cart_discount' ) - AND `post_id` IN ( $order_ids )" + AND `post_id` IN ( $order_ids )" // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared ); // Now set the line totals to $0 $wpdb->query( - "UPDATE $order_item_meta_table + "UPDATE {$wpdb->prefix}woocommerce_order_itemmeta SET `meta_value` = 0 WHERE `meta_key` IN ( '_line_subtotal', '_line_subtotal_tax', '_line_total', '_line_tax', 'tax_amount', 'shipping_tax_amount' ) AND `order_item_id` IN ( - SELECT `order_item_id` FROM $order_items_table + SELECT `order_item_id` FROM {$wpdb->prefix}woocommerce_order_items WHERE `order_item_type` IN ('tax','line_item') - AND `order_id` IN ( $order_ids ) - )" + AND `order_id` IN ( $order_ids ) )" // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared ); } diff --git a/includes/core/upgrades/class-wcs-upgrade-1-5.php b/includes/core/upgrades/class-wcs-upgrade-1-5.php index 06a42f0..13b4ee9 100644 --- a/includes/core/upgrades/class-wcs-upgrade-1-5.php +++ b/includes/core/upgrades/class-wcs-upgrade-1-5.php @@ -26,16 +26,20 @@ class WCS_Upgrade_1_5 { public static function upgrade_products() { global $wpdb; - $sql = "SELECT DISTINCT ID FROM {$wpdb->posts} as posts - JOIN {$wpdb->postmeta} as postmeta - ON posts.ID = postmeta.post_id - AND (postmeta.meta_key LIKE '_subscription%') - JOIN {$wpdb->postmeta} AS soldindividually - ON posts.ID = soldindividually.post_id - AND ( soldindividually.meta_key LIKE '_sold_individually' AND soldindividually.meta_value != 'yes' ) - WHERE posts.post_type = 'product'"; - - $subscription_product_ids = $wpdb->get_results( $sql ); + $subscription_product_ids = $wpdb->get_results( + $wpdb->prepare( + "SELECT DISTINCT ID FROM {$wpdb->posts} as posts + JOIN {$wpdb->postmeta} as postmeta + ON posts.ID = postmeta.post_id + AND (postmeta.meta_key LIKE %s) + JOIN {$wpdb->postmeta} AS soldindividually + ON posts.ID = soldindividually.post_id + AND ( soldindividually.meta_key LIKE %s AND soldindividually.meta_value != 'yes' ) + WHERE posts.post_type = 'product'", + $wpdb->esc_like( '_subscription' ) . '%', + $wpdb->esc_like( '_sold_individually' ) + ) + ); foreach ( $subscription_product_ids as $product_id ) { update_post_meta( $product_id->ID, '_sold_individually', 'yes' ); diff --git a/includes/core/upgrades/class-wcs-upgrade-2-0.php b/includes/core/upgrades/class-wcs-upgrade-2-0.php index 0d32b69..034e2ae 100644 --- a/includes/core/upgrades/class-wcs-upgrade-2-0.php +++ b/includes/core/upgrades/class-wcs-upgrade-2-0.php @@ -205,6 +205,7 @@ class WCS_Upgrade_2_0 { $wpdb->query( 'SET SQL_BIG_SELECTS = 1;' ); + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $raw_subscriptions = $wpdb->get_results( $query ); $subscriptions = array(); @@ -427,11 +428,13 @@ class WCS_Upgrade_2_0 { // Now that we've copied over the old data, prefix some the subscription meta keys with _wcs_migrated to deprecate it without deleting it (yet) $subscription_item_meta_key_string = implode( "','", esc_sql( self::$subscription_item_meta_keys ) ); - $rows_affected = $wpdb->query( $wpdb->prepare( - "UPDATE `{$wpdb->prefix}woocommerce_order_itemmeta` SET `meta_key` = concat( '_wcs_migrated', `meta_key` ) - WHERE `order_item_id` = %d AND `meta_key` IN ('{$subscription_item_meta_key_string}')", - $order_item_id - ) ); + $rows_affected = $wpdb->query( + $wpdb->prepare( + "UPDATE `{$wpdb->prefix}woocommerce_order_itemmeta` SET `meta_key` = concat( '_wcs_migrated', `meta_key` ) + WHERE `order_item_id` = %d AND `meta_key` IN ('{$subscription_item_meta_key_string}')", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $order_item_id + ) + ); return $rows_affected; } @@ -662,11 +665,13 @@ class WCS_Upgrade_2_0 { // Do a single bulk insert instead of using update_post_meta() to massively reduce query time if ( ! empty( $query_meta_values ) ) { - $rows_affected = $wpdb->query( $wpdb->prepare( - "INSERT INTO {$wpdb->postmeta} (post_id, meta_key, meta_value) - VALUES " . implode( ', ', $query_placeholders ), - $query_meta_values - ) ); + $rows_affected = $wpdb->query( + $wpdb->prepare( + "INSERT INTO {$wpdb->postmeta} (post_id, meta_key, meta_value) + VALUES " . implode( ', ', $query_placeholders ), // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQL.NotPrepared + $query_meta_values + ) + ); WCS_Upgrade_Logger::add( sprintf( 'For subscription %d: %d rows of post meta added', $subscription_id, $rows_affected ) ); } @@ -710,11 +715,13 @@ class WCS_Upgrade_2_0 { $post_meta_to_deprecate = implode( "','", esc_sql( $post_meta_to_deprecate ) ); - $rows_affected = $wpdb->query( $wpdb->prepare( - "UPDATE {$wpdb->postmeta} SET `meta_key` = concat( '_wcs_migrated', `meta_key` ) - WHERE `post_id` = %d AND `meta_key` IN ('{$post_meta_to_deprecate}')", - $order_id - ) ); + $rows_affected = $wpdb->query( + $wpdb->prepare( + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + "UPDATE {$wpdb->postmeta} SET `meta_key` = concat( '_wcs_migrated', `meta_key` ) WHERE `post_id` = %d AND `meta_key` IN ('{$post_meta_to_deprecate}')", + $order_id + ) + ); return $rows_affected; } @@ -735,12 +742,17 @@ class WCS_Upgrade_2_0 { "UPDATE {$wpdb->comments} SET `comment_post_ID` = %d WHERE `comment_post_id` = %d AND ( - `comment_content` LIKE '%%subscription%%' - OR `comment_content` LIKE '%%Recurring%%' - OR `comment_content` LIKE '%%Renewal%%' - OR `comment_content` LIKE '%%Simplify payment error%%' + `comment_content` LIKE %s + OR `comment_content` LIKE %s + OR `comment_content` LIKE %s + OR `comment_content` LIKE %s )", - $subscription_id, $order_id + $subscription_id, + $order_id, + '%' . $wpdb->esc_like( 'subscription' ) . '%', + '%' . $wpdb->esc_like( 'Recurring' ) . '%', + '%' . $wpdb->esc_like( 'Renewal' ) . '%', + '%' . $wpdb->esc_like( 'Simplify payment error' ) . '%' ) ); WCS_Upgrade_Logger::add( sprintf( 'For subscription %d: migrated %d order notes', $subscription_id, $rows_affected ) ); diff --git a/includes/core/upgrades/class-wcs-upgrade-2-2-7.php b/includes/core/upgrades/class-wcs-upgrade-2-2-7.php index dc54fdd..432c0fb 100644 --- a/includes/core/upgrades/class-wcs-upgrade-2-2-7.php +++ b/includes/core/upgrades/class-wcs-upgrade-2-2-7.php @@ -120,7 +120,7 @@ class WCS_Upgrade_2_2_7 { /** * Add a message to the wcs-upgrade-end-of-prepaid-term-repair log * - * @param string The message to be logged + * @param string $message The message to be logged * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.7 */ protected static function log( $message ) { diff --git a/includes/core/upgrades/class-wcs-upgrade-2-2-9.php b/includes/core/upgrades/class-wcs-upgrade-2-2-9.php index c67b5e1..5ad824f 100644 --- a/includes/core/upgrades/class-wcs-upgrade-2-2-9.php +++ b/includes/core/upgrades/class-wcs-upgrade-2-2-9.php @@ -117,7 +117,7 @@ class WCS_Upgrade_2_2_9 { /** * Add a message to the wcs-upgrade-subscriptions-containing-synced-variations log * - * @param string The message to be logged + * @param string $message The message to be logged * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.9 */ protected static function log( $message ) { diff --git a/includes/core/upgrades/class-wcs-upgrade-logger.php b/includes/core/upgrades/class-wcs-upgrade-logger.php index e8988a5..5d5c64c 100644 --- a/includes/core/upgrades/class-wcs-upgrade-logger.php +++ b/includes/core/upgrades/class-wcs-upgrade-logger.php @@ -25,8 +25,8 @@ class WCS_Upgrade_Logger { public static function init() { - add_action( 'woocommerce_subscriptions_upgraded', array( __CLASS__, 'schedule_cleanup' ), 10, 2 ); - add_action( 'woocommerce_subscriptions_upgraded', array( __CLASS__, 'add_more_info' ), 10 ); + add_action( 'woocommerce_subscriptions_upgraded', array( __CLASS__, 'schedule_cleanup' ), 10, 4 ); + add_action( 'woocommerce_subscriptions_upgraded', array( __CLASS__, 'add_more_info' ) ); } /** @@ -86,6 +86,13 @@ class WCS_Upgrade_Logger { self::add( sprintf( 'Active Plugins:' ) ); foreach ( $active_plugins as $plugin ) { + // In unusual cases, the stored list of active plugins may be out-of-sync with the set of installed plugins. + // This would be true if a plugin was manually deleted without WordPress's knowledge in the current request, + // for example. + if ( ! isset( $all_plugins[ $plugin ] ) ) { + continue; + } + $author = empty( $all_plugins[ $plugin ]['Author'] ) ? 'Unknown' : $all_plugins[ $plugin ]['Author']; $version = empty( $all_plugins[ $plugin ]['Version'] ) ? 'Unknown version' : $all_plugins[ $plugin ]['Version']; self::add( sprintf( ' %s by %s – %s', $all_plugins[ $plugin ]['Name'], $author, $version ) ); @@ -94,10 +101,25 @@ class WCS_Upgrade_Logger { /** * Schedule a hook to automatically clear the log after 8 weeks + * + * @since 7.7.0 Updated to reference the plugin version, rather than the legacy core library version. + * + * @param string $current_library_version Disused. + * @param string $old_library_version Disused. + * @param string $current_version Current version of WooCommerce Subscriptions. + * @param string $old_version Old version of WooCommerce Subscriptions. */ - public static function schedule_cleanup( $current_version, $old_version ) { + public static function schedule_cleanup( string $current_library_version, string $old_library_version, string $current_version, string $old_version ): void { $wc_version = defined( 'WC_VERSION' ) ? WC_VERSION : 'undefined'; - self::add( sprintf( '%s upgrade complete from Subscriptions v%s while WooCommerce WC_VERSION %s and database version %s was active.', $current_version, $old_version, $wc_version, get_option( 'woocommerce_db_version' ) ) ); + self::add( + sprintf( + 'WooCommerce Subscriptions completed its upgrade to %1$s from %2$s while WooCommerce WC_VERSION %3$s and database version %4$s was active.', + $current_version, + $old_version, + $wc_version, + get_option( 'woocommerce_db_version' ) + ) + ); } } diff --git a/includes/core/wcs-cart-functions.php b/includes/core/wcs-cart-functions.php index 45dbf3f..22e23f7 100644 --- a/includes/core/wcs-cart-functions.php +++ b/includes/core/wcs-cart-functions.php @@ -18,9 +18,7 @@ if ( ! defined( 'ABSPATH' ) ) { /** * Display a recurring cart's subtotal * - * @access public * @param WC_Cart $cart The cart do print the subtotal html for. - * @return string */ function wcs_cart_totals_subtotal_html( $cart ) { $subtotal_html = wcs_cart_price_string( wc_price( $cart->get_displayed_subtotal() ), $cart ); @@ -38,8 +36,6 @@ function wcs_cart_totals_subtotal_html( $cart ) { /** * Get recurring shipping methods. - * - * @access public */ function wcs_cart_totals_shipping_html() { $initial_packages = WC()->shipping->get_packages(); @@ -164,7 +160,6 @@ function wcs_cart_totals_shipping_html() { * @param object $shipping_method * @param string $chosen_method * @param string $input_type - * @return null */ function wcs_cart_print_shipping_input( $shipping_method_index, $shipping_method, $chosen_method = '', $input_type = 'hidden' ) { @@ -332,7 +327,7 @@ function wcs_cart_totals_coupon_html( $coupon, $cart ) { /** * Gets recurring total html including inc tax if needed. * - * @param WC_Cart The cart to display the total for. + * @param WC_Cart $cart The cart to display the total for. */ function wcs_cart_totals_order_total_html( $cart ) { $order_total_html = '' . $cart->get_total() . ' '; diff --git a/includes/core/wcs-compatibility-functions.php b/includes/core/wcs-compatibility-functions.php index 1edebe7..5a50792 100644 --- a/includes/core/wcs-compatibility-functions.php +++ b/includes/core/wcs-compatibility-functions.php @@ -140,7 +140,7 @@ function wcs_get_objects_property( $object, $property, $single = 'single', $defa * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0 * @return mixed */ -function wcs_set_objects_property( &$object, $key, $value, $save = 'save', $meta_id = '', $prefix_meta_key = 'prefix_meta_key' ) { +function wcs_set_objects_property( &$object, $key, $value, $save = 'save', $meta_id = 0, $prefix_meta_key = 'prefix_meta_key' ) { $prefixed_key = wcs_maybe_prefix_key( $key ); @@ -194,13 +194,13 @@ function wcs_set_objects_property( &$object, $key, $value, $save = 'save', $meta * * @param WC_Order|WC_Product|WC_Subscription $object The object whose property we want to access. * @param string $key The meta key name without '_' prefix - * @param mixed $value The data to set as the value of the meta * @param string $save Whether to save the data or not, 'save' to save the data, otherwise it won't be saved. + * @param int $meta_id The meta ID. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0 * @deprecated 2.4.0 Use of this compatibility function is no longer required, setters should be used on the objects instead. * @return mixed */ -function wcs_delete_objects_property( &$object, $key, $save = 'save', $meta_id = '' ) { +function wcs_delete_objects_property( &$object, $key, $save = 'save', $meta_id = 0 ) { $prefixed_key = wcs_maybe_prefix_key( $key ); @@ -477,7 +477,8 @@ function wcs_is_rest_api_request() { return false; } - $rest_prefix = trailingslashit( rest_get_url_prefix() ); + $rest_prefix = trailingslashit( rest_get_url_prefix() ); + // @phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash $is_rest_api_request = ( false !== strpos( $_SERVER['REQUEST_URI'], $rest_prefix ) ); return apply_filters( 'woocommerce_is_rest_api_request', $is_rest_api_request ); @@ -567,7 +568,7 @@ function wcs_add_woocommerce_dependent_action( $tag, $function, $woocommerce_ver * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v4.0.0 * - * @param string The version string to check in a version_compare() compatible format. + * @param string $version The version string to check in a version_compare() compatible format. * @return bool Whether the installed version of WC is prior to the given version string. */ function wcs_is_woocommerce_pre( $version ) { diff --git a/includes/core/wcs-conditional-functions.php b/includes/core/wcs-conditional-functions.php index ab540ad..8f166a5 100644 --- a/includes/core/wcs-conditional-functions.php +++ b/includes/core/wcs-conditional-functions.php @@ -21,10 +21,11 @@ if ( ! defined( 'ABSPATH' ) ) { * in some cases, like WC_Subscriptions_Product::is_purchasable() and WC_Product_Subscription_Variation::is_purchasable(), both * called within WC_Cart::get_cart_from_session(), which is run before query vars are setup. * - * @return 2.0.13 * @return bool **/ function wcs_is_order_received_page() { - return ( false !== strpos( $_SERVER['REQUEST_URI'], 'order-received' ) ); -} + // @phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash + $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( $_SERVER['REQUEST_URI'] ) : ''; + return ( false !== strpos( $request_uri, 'order-received' ) ); +} diff --git a/includes/core/wcs-deprecated-functions.php b/includes/core/wcs-deprecated-functions.php index a7310cc..9c6eef2 100644 --- a/includes/core/wcs-deprecated-functions.php +++ b/includes/core/wcs-deprecated-functions.php @@ -21,8 +21,8 @@ if ( ! defined( 'ABSPATH' ) ) { * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0 * @param string $function + * @param string $message * @param string $version - * @param string $replacement */ function wcs_doing_it_wrong( $function, $message, $version ) { @@ -70,7 +70,7 @@ function wcs_deprecated_function( $function, $version, $replacement = null ) { * Reimplement similar logic to wc_deprecated_argument() without the first parameter confusion. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0 - * @param string $argument + * @param string $function * @param string $version * @param string $message */ @@ -174,6 +174,7 @@ function wcs_get_subscription_from_key( $subscription_key ) { $subscription = wcs_get_subscription( $subscription_id ); } + // @phpstan-ignore variable.undefined if ( ! is_object( $subscription ) ) { // translators: placeholder is either subscription key or a subscription id, or, failing that, empty (e.g. "145_21" or "145") throw new InvalidArgumentException( sprintf( __( 'Could not get subscription. Most likely the subscription key does not refer to a subscription. The key was: "%s".', 'woocommerce-subscriptions' ), $subscription_key ) ); @@ -282,7 +283,7 @@ function wcs_deprecated_hook( $hook, $version, $replacement = null, $message = n error_log( $log_string . $message ); } else { - _deprecated_hook( $hook, $version, $replacement, $message ); + wc_deprecated_hook( $hook, $version, $replacement, $message ); } } } diff --git a/includes/core/wcs-formatting-functions.php b/includes/core/wcs-formatting-functions.php index 1bcf3eb..69b753f 100644 --- a/includes/core/wcs-formatting-functions.php +++ b/includes/core/wcs-formatting-functions.php @@ -285,7 +285,7 @@ function wp_kses_allow_underscores( $content, $allowed_html ) { * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v4.0.0 * - * @param string The number to append the ordinal suffix to. + * @param string $number The number to append the ordinal suffix to. * @return string */ function wcs_append_numeral_suffix( $number ) { diff --git a/includes/core/wcs-functions.php b/includes/core/wcs-functions.php index c08dbdb..aa5b242 100644 --- a/includes/core/wcs-functions.php +++ b/includes/core/wcs-functions.php @@ -301,8 +301,8 @@ function wcs_get_subscription_date_types() { /** * Find whether to display a specific date type in the admin area * - * @param string A subscription date type key. One of the array key values returned by @see wcs_get_subscription_date_types(). - * @param WC_Subscription + * @param string $date_type A subscription date type key. One of the array key values returned by @see wcs_get_subscription_date_types(). + * @param WC_Subscription $subscription * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.1 * @return bool */ @@ -472,9 +472,6 @@ function wcs_get_subscriptions( $args ) { 'meta_query' => isset( $working_args['meta_query'] ) ? $working_args['meta_query'] : array(), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query ); - // Remove Subscriptions-specific args which exist as aliases of regular order query args. - unset( $query_args['subscription_status'], $query_args['subscriptions_per_page'] ); - // Maybe only get subscriptions created by a certain order if ( 0 !== $working_args['order_id'] && is_numeric( $working_args['order_id'] ) ) { $query_args['parent'] = $working_args['order_id']; @@ -646,12 +643,12 @@ function wcs_get_subscriptions_for_product( $product_ids, $fields = 'ids', $args /** * Get all subscription items which have a trial. * - * @param mixed WC_Subscription|post_id + * @param mixed $subscription_id WC_Subscription|post_id * @return array * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ function wcs_get_line_items_with_a_trial( $subscription_id ) { - + /** @var WC_Subscription $subscription */ $subscription = ( is_object( $subscription_id ) ) ? $subscription_id : wcs_get_subscription( $subscription_id ); $trial_items = array(); @@ -684,7 +681,7 @@ function wcs_can_items_be_removed( $subscription ) { /** * Checks if the user can be granted the permission to remove a particular line item from the subscription. * - * @param WC_Order_item $item An instance of a WC_Order_item object + * @param WC_Order_Item $item An instance of a WC_Order_item object * @param WC_Subscription $subscription An instance of a WC_Subscription object * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.15 */ @@ -696,7 +693,7 @@ function wcs_can_item_be_removed( $item, $subscription ) { * Get the Product ID for an order's line item (only the product ID, not the variation ID, even if the order item * is for a variation). * - * @param int An order item ID + * @param int $item_id An order item ID * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ function wcs_get_order_items_product_id( $item_id ) { @@ -719,7 +716,7 @@ function wcs_get_order_items_product_id( $item_id ) { * items representing a variation, that means the 'variation_id' value, if the item is not a variation, that means * the 'product_id value. This function helps save keystrokes on the idiom to check if an item is to a variation or not. * - * @param array or object $item Either a cart item, order/subscription line item, or a product. + * @param array|object $item_or_product Either a cart item, order/subscription line item, or a product. */ function wcs_get_canonical_product_id( $item_or_product ) { @@ -817,22 +814,24 @@ function wcs_subscription_search( $term ) { FROM {$wpdb->postmeta} p1 INNER JOIN {$wpdb->postmeta} p2 ON p1.post_id = p2.post_id WHERE - ( p1.meta_key = '_billing_first_name' AND p2.meta_key = '_billing_last_name' AND CONCAT(p1.meta_value, ' ', p2.meta_value) LIKE '%%%s%%' ) + ( p1.meta_key = '_billing_first_name' AND p2.meta_key = '_billing_last_name' AND CONCAT(p1.meta_value, ' ', p2.meta_value) LIKE %s ) OR - ( p1.meta_key = '_shipping_first_name' AND p2.meta_key = '_shipping_last_name' AND CONCAT(p1.meta_value, ' ', p2.meta_value) LIKE '%%%s%%' ) + ( p1.meta_key = '_shipping_first_name' AND p2.meta_key = '_shipping_last_name' AND CONCAT(p1.meta_value, ' ', p2.meta_value) LIKE %s ) OR - ( p1.meta_key IN ('" . implode( "','", esc_sql( $search_fields ) ) . "') AND p1.meta_value LIKE '%%%s%%' ) + ( p1.meta_key IN ('" . implode( "','", esc_sql( $search_fields ) ) . "') AND p1.meta_value LIKE %s ) ", - esc_attr( $term ), esc_attr( $term ), esc_attr( $term ) + '%' . $wpdb->esc_like( esc_attr( $term ) ) . '%', + '%' . $wpdb->esc_like( esc_attr( $term ) ) . '%', + '%' . $wpdb->esc_like( esc_attr( $term ) ) . '%' ) ), $wpdb->get_col( $wpdb->prepare( " SELECT order_id FROM {$wpdb->prefix}woocommerce_order_items as order_items - WHERE order_item_name LIKE '%%%s%%' + WHERE order_item_name LIKE %s ", - esc_attr( $term ) + '%' . $wpdb->esc_like( esc_attr( $term ) ) . '%' ) ), $wpdb->get_col( @@ -841,11 +840,11 @@ function wcs_subscription_search( $term ) { FROM {$wpdb->posts} p1 INNER JOIN {$wpdb->postmeta} p2 ON p1.ID = p2.post_id INNER JOIN {$wpdb->users} u ON p2.meta_value = u.ID - WHERE u.user_email LIKE '%%%s%%' + WHERE u.user_email LIKE %s AND p2.meta_key = '_customer_user' AND p1.post_type = 'shop_subscription' ", - esc_attr( $term ) + '%' . $wpdb->esc_like( esc_attr( $term ) ) . '%' ) ), array( $search_order_id ) @@ -897,7 +896,7 @@ function wcs_set_payment_meta( $subscription, $payment_meta ) { * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.6.0 * - * @param WC_Order|WC_Subscription $subscription Order or subscription object. + * @param WC_Order|WC_Subscription $order Order or subscription object. * @param WC_Product $product The product to get the total quantity of. * @param string $product_match_method The way to find matching products. Optional. Default is 'stock_managed' Can be: * 'stock_managed' - Products with matching stock managed IDs are grouped. Helpful for getting the total quantity of variation parents if they are managed on the product level, not on the variation level - @see WC_Product::get_stock_managed_by_id(). diff --git a/includes/core/wcs-helper-functions.php b/includes/core/wcs-helper-functions.php index c6a742f..83813b2 100644 --- a/includes/core/wcs-helper-functions.php +++ b/includes/core/wcs-helper-functions.php @@ -15,7 +15,7 @@ if ( ! defined( 'ABSPATH' ) ) { /** * Display date/time input fields * - * @param int (optional) A timestamp for a certain date in the site's timezome. If left empty, or 0, it will be set to today's date. + * @param int|null $timestamp (optional) A timestamp for a certain date in the site's timezome. If left empty, or 0, it will be set to today's date. * @param array $args A set of name => value pairs to customise the input fields * 'id_attr': (string) the date to display in the selector in MySQL format ('Y-m-d H:i:s'). Required. * 'date': (string) the date to display in the selector in MySQL format ('Y-m-d H:i:s'). Required. @@ -121,7 +121,7 @@ function wcs_json_encode( $data ) { * @param $haystack An array to insert the element into * @param $new_key The key to insert * @param $new_value An value to insert - * @return The new array if the $needle key exists, otherwise an unmodified $haystack + * @return array The new array if the $needle key exists, otherwise an unmodified $haystack */ function wcs_array_insert_after( $needle, $haystack, $new_key, $new_value ) { @@ -314,7 +314,7 @@ function wcs_trial_has_passed( $subscription ) { * @param array $array The array of items to check. * @param array $property The name of object's property to check. Optional. Default is '' - the array element value as a boolean will be used, the same as array_filter(). */ -function wcs_apply_array_filter( $filter, $array, $property = '' ) { +function wcs_apply_array_filter( $filter, $array, $property = array() ) { foreach ( $array as $index => $element ) { $value = empty( $property ) ? $element : $element->{$property}; diff --git a/includes/core/wcs-order-functions.php b/includes/core/wcs-order-functions.php index 7b1fbe5..37af67f 100644 --- a/includes/core/wcs-order-functions.php +++ b/includes/core/wcs-order-functions.php @@ -255,6 +255,7 @@ function wcs_create_order_from_subscription( $subscription, $type ) { // If the line item we're adding is a product line item and that product still exists, set any applicable backorder meta. if ( $item->is_type( 'line_item' ) && $item->get_product() ) { + // @phpstan-ignore-next-line $order_item->set_backorder_meta(); $order_item->save(); } @@ -631,7 +632,8 @@ function wcs_update_order_item_type( $item_id, $new_type, $order_or_subscription /** * Get an instance of WC_Order_Item_Meta for an order item * - * @param array + * @param WC_Order_Item $item + * @param WC_Product $product * @return WC_Order_Item_Meta * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ @@ -706,7 +708,7 @@ function wcs_get_order_item_name( $order_item, $include = array() ) { * Get the full name for a order/subscription line item, including the items non hidden meta * (i.e. attributes), as a flat string. * - * @param array + * @param array $line_item * @return string */ function wcs_get_line_item_name( $line_item ) { @@ -762,7 +764,7 @@ function wcs_get_line_item_name( $line_item ) { * Display item meta data in a version compatible way. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0 - * @param WC_Item $item + * @param WC_Order_Item $item * @param WC_Order $order * @return void */ @@ -778,7 +780,7 @@ function wcs_display_item_meta( $item, $order ) { * Display item download links in a version compatible way. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0 - * @param WC_Item $item + * @param WC_Order_Item $item * @param WC_Order $order * @return void */ @@ -828,6 +830,7 @@ function wcs_copy_order_item( $from_item, &$to_item ) { ) ); break; case 'shipping': + /** @var WC_Order_Item_Shipping $to_item */ /** @var WC_Order_Item_Shipping $from_item */ $to_item->set_props( array( 'method_id' => $from_item->get_method_id(), @@ -966,6 +969,8 @@ function wcs_seconds_since_order_created( $order ) { function wcs_find_matching_line_item( $order, $subscription_item, $match_type = 'match_product_ids' ) { $matching_item = false; + $subscription_item_attributes = array(); + if ( 'match_attributes' === $match_type ) { $subscription_item_attributes = wp_list_pluck( $subscription_item->get_formatted_meta_data( '_', true ), 'value', 'key' ); } @@ -1051,7 +1056,7 @@ function wcs_order_contains_early_renewal( $order ) { * * @return string The item's subscription grouping key. */ -function wcs_get_subscription_item_grouping_key( $item, $renewal_time = '' ) { +function wcs_get_subscription_item_grouping_key( $item, $renewal_time = 0 ) { return apply_filters( 'woocommerce_subscriptions_item_grouping_key', wcs_get_subscription_grouping_key( $item->get_product(), $renewal_time ), $item ); } @@ -1063,7 +1068,7 @@ function wcs_get_subscription_item_grouping_key( $item, $renewal_time = '' ) { * * Note: If the line item has a custom total that doesn't match the expected price, don't override it. * - * @param WC_Order_Item $item Subscription line item. + * @param WC_Order_Item_Product $item Subscription line item. */ function wcs_set_recurring_item_total( &$item ) { $product = $item->get_product(); diff --git a/includes/core/wcs-product-functions.php b/includes/core/wcs-product-functions.php index 8156b08..daa5976 100644 --- a/includes/core/wcs-product-functions.php +++ b/includes/core/wcs-product-functions.php @@ -144,7 +144,7 @@ function wcs_get_min_max_variation_data( $variable_product, $child_variation_ids * Determine the minimum and maximum values for a set of structured subscription * price data in a form created by @see wcs_get_min_max_variation_data() * - * @param array $child_variation_ids the IDs of product variation children ids + * @param array $variations_data the IDs of product variation children ids * @return array * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0 */ diff --git a/includes/core/wcs-resubscribe-functions.php b/includes/core/wcs-resubscribe-functions.php index a1c3dd2..6a8fe7f 100644 --- a/includes/core/wcs-resubscribe-functions.php +++ b/includes/core/wcs-resubscribe-functions.php @@ -105,11 +105,11 @@ function wcs_get_users_resubscribe_link_for_product( $product_id ) { /** * Checks the cart to see if it contains a subscription product renewal. * - * @param bool | Array The cart item containing the renewal, else false. + * @param bool|array $cart The cart item containing the renewal, else false. * @return string * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ -function wcs_cart_contains_resubscribe( $cart = '' ) { +function wcs_cart_contains_resubscribe( $cart = null ) { $contains_resubscribe = false; @@ -153,12 +153,12 @@ function wcs_get_subscriptions_for_resubscribe_order( $order ) { * 5. have a recurring amount greater than $0, to avoid allowing resubscribes to subscriptions * where the entire cost is charged in a sign-up fee * - * @param int | WC_Subscription $subscription Post ID of a 'shop_subscription' post, or instance of a WC_Subscription object - * @param int The ID of a user + * @param int|WC_Subscription $subscription Post ID of a 'shop_subscription' post, or instance of a WC_Subscription object + * @param int $user_id The ID of a user * @return bool * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ -function wcs_can_user_resubscribe_to( $subscription, $user_id = '' ) { +function wcs_can_user_resubscribe_to( $subscription, $user_id = 0 ) { if ( ! is_object( $subscription ) ) { $subscription = wcs_get_subscription( $subscription ); diff --git a/includes/core/wcs-switch-functions.php b/includes/core/wcs-switch-functions.php index 3ed0d7f..7d6aa9b 100644 --- a/includes/core/wcs-switch-functions.php +++ b/includes/core/wcs-switch-functions.php @@ -36,7 +36,7 @@ function wcs_order_contains_switch( $order ) { /** * Get the subscriptions that had an item switch for a given order (if any). * - * @param int|WC_Order $order_id The post_id of a shop_order post or an instance of a WC_Order object + * @param int|WC_Order $order The post_id of a shop_order post or an instance of a WC_Order object * @return array Subscription details in post_id => WC_Subscription form. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ diff --git a/includes/core/wcs-time-functions.php b/includes/core/wcs-time-functions.php index 45e6785..0bd607f 100644 --- a/includes/core/wcs-time-functions.php +++ b/includes/core/wcs-time-functions.php @@ -18,8 +18,8 @@ if ( ! defined( 'ABSPATH' ) ) { /** * Return an i18n'ified associative array of all possible subscription periods. * - * @param int (optional) An interval in the range 1-6 - * @param string (optional) One of day, week, month or year. If empty, all subscription ranges are returned. + * @param int|null $number (optional) An interval in the range 1-6 + * @param string|null $period (optional) One of day, week, month or year. If empty, all subscription ranges are returned. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ function wcs_get_subscription_period_strings( $number = 1, $period = '' ) { @@ -46,8 +46,8 @@ function wcs_get_subscription_period_strings( $number = 1, $period = '' ) { /** * Return an i18n'ified associative array of all possible subscription trial periods. * - * @param int (optional) An interval in the range 1-6 - * @param string (optional) One of day, week, month or year. If empty, all subscription ranges are returned. + * @param int|null $number (optional) An interval in the range 1-6 + * @param string|null $period (optional) One of day, week, month or year. If empty, all subscription ranges are returned. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ function wcs_get_subscription_trial_period_strings( $number = 1, $period = '' ) { @@ -123,10 +123,10 @@ function wcs_get_non_cached_subscription_ranges() { /** * Retaining the API, it makes use of the transient functionality. * - * @param string $period + * @param string $subscription_period * @return bool|mixed */ -function wcs_get_subscription_ranges( $subscription_period = '' ) { +function wcs_get_subscription_ranges( $subscription_period = null ) { static $subscription_locale_ranges = array(); if ( ! is_string( $subscription_period ) ) { @@ -151,10 +151,10 @@ function wcs_get_subscription_ranges( $subscription_period = '' ) { /** * Return an i18n'ified associative array of all possible subscription periods. * - * @param int (optional) An interval in the range 1-6 + * @param int|null $interval (optional) An interval in the range 1-6 * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ -function wcs_get_subscription_period_interval_strings( $interval = '' ) { +function wcs_get_subscription_period_interval_strings( $interval = null ) { $intervals = array( 1 => _x( 'every', 'period interval (eg "$10 _every_ 2 weeks")', 'woocommerce-subscriptions' ) ); @@ -175,7 +175,7 @@ function wcs_get_subscription_period_interval_strings( $interval = '' ) { /** * Return an i18n'ified associative array of all time periods allowed for subscriptions. * - * @param string (Optional) Either 'singular' for singular trial periods or 'plural'. + * @param string|null $form (Optional) Either 'singular' for singular trial periods or 'plural'. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ function wcs_get_available_time_periods( $form = 'singular' ) { @@ -199,7 +199,7 @@ function wcs_get_available_time_periods( $form = 'singular' ) { /** * Returns an array of allowed trial period lengths. * - * @param string (optional) One of day, week, month or year. If empty, all subscription trial period lengths are returned. + * @param string|null $subscription_period (optional) One of day, week, month or year. If empty, all subscription trial period lengths are returned. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ function wcs_get_subscription_trial_lengths( $subscription_period = '' ) { @@ -264,6 +264,7 @@ function wcs_add_months( $from_timestamp, $months_to_add, $timezone_behaviour = $first_day_of_month = gmdate( 'Y-m', $from_timestamp ) . '-1'; $days_in_next_month = gmdate( 't', wcs_strtotime_dark_knight( "+ {$months_to_add} month", wcs_date_to_time( $first_day_of_month ) ) ); + $next_timestamp = 0; // Payment is on the last day of the month OR number of days in next billing month is less than the the day of this month (i.e. current billing date is 30th January, next billing date can't be 30th February) if ( gmdate( 'd m Y', $from_timestamp ) === gmdate( 't m Y', $from_timestamp ) || gmdate( 'd', $from_timestamp ) > $days_in_next_month ) { @@ -316,6 +317,8 @@ function wcs_estimate_periods_between( $start_timestamp, $end_timestamp, $unit_o $seconds_until_timestamp = $end_timestamp - $start_timestamp; + $denominator = 0; + switch ( $unit_of_time ) { case 'day': @@ -333,6 +336,7 @@ function wcs_estimate_periods_between( $start_timestamp, $end_timestamp, $unit_o break; } + // @phpstan-ignore-next-line $periods_until = ( 'ceil' == $rounding_method ) ? ceil( $seconds_until_timestamp / $denominator ) : floor( $seconds_until_timestamp / $denominator ); } @@ -629,7 +633,7 @@ function wcs_is_datetime_mysql_format( $time ) { return false; } - $format = 'Y-m-d H:i:s'; + $format = wcs_get_db_datetime_format(); $date_object = DateTime::createFromFormat( $format, $time ); @@ -642,6 +646,26 @@ function wcs_is_datetime_mysql_format( $time ) { && (int) $date_object->format( 'Y' ) >= 1900; } +/** + * Check if a value is a valid timestamp. + * + * @param int|string $timestamp The value to check. Only integers and strings are allowed. + * @return bool True if the value is a valid timestamp, false otherwise. + */ +function wcs_is_timestamp( $timestamp ) { + // Only accept integers and strings + if ( ! is_int( $timestamp ) && ! is_string( $timestamp ) ) { + return false; + } + + $str = (string) $timestamp; + + // Match valid timestamp patterns: integers, negative integers, or their string equivalents + // Allows: 123, -123, '123', '-123', 0, '0' + // Rejects: +123, 0123, 12.34, 1e10, ' 123', '123 ', scientific notation, newlines, etc. + return (bool) preg_match( '/\A-?(?:0|[1-9]\d*)\z/', $str ); +} + /** * Convert a date string into a timestamp without ever adding or deducting time. * @@ -656,8 +680,8 @@ function wcs_is_datetime_mysql_format( $time ) { * * This makes sure the date is never converted. * - * @param string $date_string A date string formatted in MySQl or similar format that will map correctly when instantiating an instance of DateTime() - * @return int Unix timestamp representation of the timestamp passed in without any changes for timezones + * @param string $date_string A date string acceptable by DateTime() or a timestamp. + * @return int|null Unix timestamp representation of the timestamp passed in without any changes for timezones, or null if the date string is invalid. */ function wcs_date_to_time( $date_string ) { @@ -665,7 +689,16 @@ function wcs_date_to_time( $date_string ) { return 0; } - $date_time = new WC_DateTime( $date_string, new DateTimeZone( 'UTC' ) ); + try { + if ( is_numeric( $date_string ) ) { + $date_time = new WC_DateTime( 'now', new DateTimeZone( 'UTC' ) ); + $date_time->setTimestamp( (int) $date_string ); + } else { + $date_time = new WC_DateTime( $date_string, new DateTimeZone( 'UTC' ) ); + } + } catch ( \Throwable $e ) { + return null; + } return intval( $date_time->getTimestamp() ); } @@ -706,6 +739,7 @@ function wcs_strtotime_dark_knight( $time_string, $from_timestamp = null ) { * @return int the number of days in that billing cycle */ function wcs_get_days_in_cycle( $period, $interval ) { + $days_in_cycle = 0; switch ( $period ) { case 'day': @@ -805,7 +839,6 @@ function wcs_get_sites_timezone() { * M – for months; allowable range is 1 to 24 * Y – for years; allowable range is 1 to 5 * - * @param string (optional) One of day, week, month or year. If empty, all subscription ranges are returned. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ function wcs_get_subscription_ranges_tlc() { @@ -819,7 +852,7 @@ function wcs_get_subscription_ranges_tlc() { * a WC_Datetime object when WC > 3.0 is active) and create a WC_DateTime object. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0 - * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if their is no date. + * @param string|integer|null $variable_date_type UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if their is no date. * @return null|WC_DateTime in site's timezone */ function wcs_get_datetime_from( $variable_date_type ) { @@ -846,13 +879,22 @@ function wcs_get_datetime_from( $variable_date_type ) { * Get a MySQL date/time string in UTC timezone from a WC_Datetime object. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0 - * @param WC_DateTime + * @param WC_DateTime $datetime * @return string MySQL date/time string representation of the DateTime object in UTC timezone */ function wcs_get_datetime_utc_string( $datetime ) { $date = clone $datetime; // Don't change the original date object's timezone $date->setTimezone( new DateTimeZone( 'UTC' ) ); - return $date->format( 'Y-m-d H:i:s' ); + return $date->format( wcs_get_db_datetime_format() ); +} + +/** + * Get the datetime format used in the database. + * + * @return string The datetime format used in the database. Default: Y-m-d H:i:s. + */ +function wcs_get_db_datetime_format() { + return 'Y-m-d H:i:s'; } /** diff --git a/includes/core/wcs-user-functions.php b/includes/core/wcs-user-functions.php index 5abe6cd..38f550e 100644 --- a/includes/core/wcs-user-functions.php +++ b/includes/core/wcs-user-functions.php @@ -46,7 +46,7 @@ function wcs_maybe_make_user_inactive( $user_id ) { * Handy for hooks that pass a subscription object. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.9 - * @param WC_Subscription|WC_Order + * @param WC_Subscription|WC_Order $subscription */ function wcs_maybe_make_user_inactive_for( $subscription ) { wcs_maybe_make_user_inactive( $subscription->get_user_id() ); @@ -130,7 +130,7 @@ function wcs_get_new_user_role_names( $role_new ) { * * @return bool */ -function wcs_user_has_subscription( $user_id = 0, $product_id = '', $status = 'any' ) { +function wcs_user_has_subscription( $user_id = 0, $product_id = 0, $status = 'any' ) { $subscriptions = wcs_get_users_subscriptions( $user_id ); @@ -170,7 +170,7 @@ function wcs_user_has_subscription( $user_id = 0, $product_id = '', $status = 'a * @return WC_Subscription[] */ function wcs_get_users_subscriptions( $user_id = 0 ) { - if ( 0 === $user_id || empty( $user_id ) ) { + if ( 0 === $user_id ) { $user_id = get_current_user_id(); } @@ -185,7 +185,7 @@ function wcs_get_users_subscriptions( $user_id = 0 ) { } } - if ( empty( $subscriptions ) && 0 !== $user_id && ! empty( $user_id ) ) { + if ( empty( $subscriptions ) && 0 !== $user_id ) { $subscription_ids = WCS_Customer_Store::instance()->get_users_subscription_ids( $user_id ); foreach ( $subscription_ids as $subscription_id ) { diff --git a/includes/payment-retry/class-wcs-retry-table-maker.php b/includes/payment-retry/class-wcs-retry-table-maker.php index fe37006..0dcfa7b 100644 --- a/includes/payment-retry/class-wcs-retry-table-maker.php +++ b/includes/payment-retry/class-wcs-retry-table-maker.php @@ -36,6 +36,7 @@ class WCS_Retry_Table_Maker extends WCS_Table_Maker { */ protected function get_table_definition( $table ) { global $wpdb; + // phpcs:disable QITStandard.DB.DynamicWpdbMethodCall.DynamicMethod $table_name = $wpdb->$table; $charset_collate = $wpdb->get_charset_collate(); diff --git a/includes/switching/class-wcs-switch-cart-item.php b/includes/switching/class-wcs-switch-cart-item.php index d001ba7..cb3f5db 100644 --- a/includes/switching/class-wcs-switch-cart-item.php +++ b/includes/switching/class-wcs-switch-cart-item.php @@ -112,6 +112,12 @@ class WCS_Switch_Cart_Item { */ public $is_switch_after_fully_reduced_prepaid_term; + /** + * The last switch order for this subscription. + * @var WC_Order|null + */ + private $switch_order = null; + /** * Constructor. * @@ -263,12 +269,19 @@ class WCS_Switch_Cart_Item { public function get_total_paid_for_current_period() { if ( ! isset( $this->total_paid_for_current_period ) ) { + $orders_to_include = array(); + // If the last order was a switch with a fully reduced pre-paid term, the amount the customer has paid is just the total in that order. if ( $this->is_switch_after_fully_reduced_prepaid_term() ) { - $this->total_paid_for_current_period = WC_Subscriptions_Switcher::calculate_total_paid_since_last_order( $this->subscription, $this->existing_item, 'exclude_sign_up_fees', array( $this->get_last_switch_order() ) ); - } else { - $this->total_paid_for_current_period = WC_Subscriptions_Switcher::calculate_total_paid_since_last_order( $this->subscription, $this->existing_item, 'exclude_sign_up_fees' ); + $orders_to_include[] = $this->get_last_switch_order(); } + + $this->total_paid_for_current_period = WC_Subscriptions_Switcher::calculate_total_paid_since_last_order( + $this->subscription, + $this->existing_item, + 'exclude_sign_up_fees', + $orders_to_include + ); } return apply_filters( 'wcs_switch_total_paid_for_current_period', $this->total_paid_for_current_period, $this->subscription, $this->existing_item ); @@ -443,13 +456,11 @@ class WCS_Switch_Cart_Item { * @return WC_Order|Null The last switch order or null if one doesn't exist. */ protected function get_last_switch_order() { - static $switch_order = null; - - if ( ! $switch_order ) { - $switch_order = $this->subscription->get_last_order( 'all', 'switch', [ 'checkout-draft' ] ); + if ( ! $this->switch_order ) { + $this->switch_order = $this->subscription->get_last_order( 'all', 'switch', array( 'checkout-draft' ) ); } - return $switch_order; + return $this->switch_order; } /** @@ -473,34 +484,47 @@ class WCS_Switch_Cart_Item { protected function is_switch_after_fully_reduced_prepaid_term() { if ( ! isset( $this->is_switch_after_fully_reduced_prepaid_term ) ) { - $last_switch_order = $this->get_last_switch_order(); - - if ( empty( $last_switch_order ) || ! $last_switch_order->get_date_paid() ) { - $this->is_switch_after_fully_reduced_prepaid_term = false; - return false; - } - - $switch_paid_date = $last_switch_order->get_date_paid(); - - // If the last switch order occurred before the last payment order (parent or renewal), then the last order wasn't a switch. - if ( $switch_paid_date->getTimestamp() < $this->get_last_order_paid_time() ) { - $this->is_switch_after_fully_reduced_prepaid_term = false; - return false; - } - - /** - * If the last switch resulted in the customer being charged the pull cost upfront, the customer must have been entitled to fewer days than had already elapsed - see reduce_prepaid_term(). - * This means the subscription billing term would have started from that switch order's date, not the last order (parent/renewal) date. - */ - $first_payment_after_switch = WC_Subscriptions_Product::get_first_renewal_payment_time( $this->existing_item->get_product(), gmdate( 'Y-m-d H:i:s', $switch_paid_date->format( 'U' ) ) ); - - // If the first payment date after the last switch is roughly equal (+- 1 hour) to the next payment date, then it was a fully reduced pre-paid term switch. - $this->is_switch_after_fully_reduced_prepaid_term = ( $this->next_payment_timestamp - HOUR_IN_SECONDS <= $first_payment_after_switch ) && ( $first_payment_after_switch <= $this->next_payment_timestamp + HOUR_IN_SECONDS ); + $this->is_switch_after_fully_reduced_prepaid_term = $this->calculate_is_switch_after_fully_reduced_prepaid_term(); } return $this->is_switch_after_fully_reduced_prepaid_term; } + /** + * Calculates whether the last order was a switch and it fully reduced the prepaid term. + * + * @since 7.6.0 + * @return bool + */ + public function calculate_is_switch_after_fully_reduced_prepaid_term() { + $last_switch_order = $this->get_last_switch_order(); + + // If there is no last switch order or it hasn't been paid for, the customer hasn't switched before. + // Therefore, this can't be a switch after a fully reduced prepaid term. + if ( empty( $last_switch_order ) || ! $last_switch_order->get_date_paid() ) { + return false; + } + + $switch_paid_date = $last_switch_order->get_date_paid(); + + // If the last switch order occurred before the last payment order (parent or renewal), then the last order wasn't a switch. + if ( $switch_paid_date->getTimestamp() < $this->get_last_order_paid_time() ) { + return false; + } + + /** + * If the last switch resulted in the customer being charged the pull cost upfront, the customer must have been entitled to fewer days than had already elapsed - see reduce_prepaid_term(). + * This means the subscription billing term would have started from that switch order's date, not the last order (parent/renewal) date. + */ + $first_payment_after_switch = WC_Subscriptions_Product::get_first_renewal_payment_time( + $this->existing_item->get_product(), + gmdate( 'Y-m-d H:i:s', $switch_paid_date->format( 'U' ) ) + ); + + // Check if the first payment after switch is within 1 hour of the next payment timestamp. + return abs( $first_payment_after_switch - $this->next_payment_timestamp ) <= HOUR_IN_SECONDS; + } + /** * Determines whether the customer is switching to a subscription with a length of 1 - one off payment. * diff --git a/languages/woocommerce-subscriptions.pot b/languages/woocommerce-subscriptions.pot index 9e9fac4..17a3866 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 7.6.0\n" +"Project-Id-Version: WooCommerce Subscriptions 7.7.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: 2025-06-11T02:20:05+00:00\n" +"POT-Creation-Date: 2025-07-09T20:16:57+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.12.0\n" "X-Domain: woocommerce-subscriptions\n" @@ -33,7 +33,7 @@ msgstr "" #. Author of the plugin #: woocommerce-subscriptions.php #: includes/admin/class-wcs-admin-reports.php:136 -#: includes/admin/reports/class-wcs-report-cache-manager.php:269 +#: includes/admin/reports/class-wcs-report-cache-manager.php:278 msgid "WooCommerce" msgstr "" @@ -56,14 +56,14 @@ msgstr "" #: includes/admin/class-wcs-admin-reports.php:81 #: includes/admin/reports/class-wcs-report-subscription-events-by-date.php:938 #: includes/api/class-wc-rest-subscriptions-settings.php:29 -#: includes/core/admin/class-wc-subscriptions-admin.php:1030 -#: includes/core/admin/class-wc-subscriptions-admin.php:1182 +#: includes/core/admin/class-wc-subscriptions-admin.php:1025 +#: includes/core/admin/class-wc-subscriptions-admin.php:1178 #: includes/core/admin/class-wcs-admin-system-status.php:100 #: includes/core/admin/class-wcs-wc-admin-manager.php:41 #: includes/core/admin/class-wcs-wc-admin-manager.php:51 #: includes/core/admin/class-wcs-wc-admin-manager.php:93 -#: includes/core/class-wc-subscriptions-core-plugin.php:393 -#: includes/core/class-wc-subscriptions-core-plugin.php:406 +#: includes/core/class-wc-subscriptions-core-plugin.php:396 +#: includes/core/class-wc-subscriptions-core-plugin.php:409 #: includes/core/class-wcs-query.php:108 #: includes/core/class-wcs-query.php:133 #: includes/core/class-wcs-query.php:289 @@ -95,34 +95,34 @@ msgstr "" msgid "Failed Payment Retries" msgstr "" -#: includes/admin/reports/class-wcs-report-cache-manager.php:272 +#: includes/admin/reports/class-wcs-report-cache-manager.php:281 msgid "Please note: data for this report is cached. The data displayed may be out of date by up to 24 hours. The cache is updated each morning at 4am in your site's timezone." msgstr "" -#: includes/admin/reports/class-wcs-report-cache-manager.php:321 +#: includes/admin/reports/class-wcs-report-cache-manager.php:330 msgctxt "Whether the Report Cache has been enabled" msgid "Report Cache Enabled" msgstr "" -#: includes/admin/reports/class-wcs-report-cache-manager.php:323 -#: includes/core/admin/class-wc-subscriptions-admin.php:1724 -#: includes/core/admin/class-wc-subscriptions-admin.php:1793 +#: includes/admin/reports/class-wcs-report-cache-manager.php:332 +#: includes/core/admin/class-wc-subscriptions-admin.php:1721 +#: includes/core/admin/class-wc-subscriptions-admin.php:1790 #: includes/core/admin/class-wcs-admin-system-status.php:139 msgid "Yes" msgstr "" -#: includes/admin/reports/class-wcs-report-cache-manager.php:323 -#: includes/core/admin/class-wc-subscriptions-admin.php:1724 +#: includes/admin/reports/class-wcs-report-cache-manager.php:332 +#: includes/core/admin/class-wc-subscriptions-admin.php:1721 #: includes/core/admin/class-wcs-admin-system-status.php:139 msgid "No" msgstr "" -#: includes/admin/reports/class-wcs-report-cache-manager.php:327 +#: includes/admin/reports/class-wcs-report-cache-manager.php:336 msgid "Cache Update Failures" msgstr "" #. translators: %d refers to the number of times we have detected cache update failures -#: includes/admin/reports/class-wcs-report-cache-manager.php:330 +#: includes/admin/reports/class-wcs-report-cache-manager.php:339 #, php-format msgid "%d failures" msgid_plural "%d failure" @@ -778,8 +778,8 @@ msgstr "" #: includes/api/legacy/class-wc-rest-subscriptions-controller.php:349 #: includes/api/v1/class-wc-rest-subscriptions-v1-controller.php:504 #: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:396 -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:179 -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:453 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:186 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:460 msgid "The number of billing periods between subscription renewals." msgstr "" @@ -787,8 +787,8 @@ msgstr "" #: includes/api/legacy/class-wc-rest-subscriptions-controller.php:354 #: includes/api/v1/class-wc-rest-subscriptions-v1-controller.php:509 #: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:401 -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:172 -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:446 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:179 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:453 msgid "Billing period for the subscription." msgstr "" @@ -974,7 +974,7 @@ msgstr "" #: includes/api/legacy/class-wc-rest-subscriptions-controller.php:382 #: includes/api/v1/class-wc-rest-subscriptions-v1-controller.php:537 -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:440 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:447 msgid "The subscription's next payment date." msgstr "" @@ -1128,7 +1128,7 @@ msgid "Add a Subscription Product" msgstr "" #: includes/class-wc-subscriptions-plugin.php:227 -#: includes/core/class-wc-subscriptions-core-plugin.php:574 +#: includes/core/class-wc-subscriptions-core-plugin.php:577 #: includes/core/upgrades/templates/wcs-about-2-0.php:35 #: includes/core/upgrades/templates/wcs-about.php:34 msgid "Settings" @@ -1162,9 +1162,9 @@ msgstr "" #: includes/class-wcs-call-to-action-button-text-manager.php:47 #: includes/class-wcs-call-to-action-button-text-manager.php:55 #: includes/class-wcs-call-to-action-button-text-manager.php:58 -#: includes/core/class-wc-subscriptions-checkout.php:657 -#: includes/core/class-wc-subscriptions-product.php:1204 -#: includes/core/class-wc-subscriptions-product.php:1236 +#: includes/core/class-wc-subscriptions-checkout.php:658 +#: includes/core/class-wc-subscriptions-product.php:1205 +#: includes/core/class-wc-subscriptions-product.php:1237 msgid "Sign up now" msgstr "" @@ -1190,7 +1190,7 @@ msgid "Set a maximum number of times a customer can suspend their account for ea msgstr "" #: includes/class-wcs-customer-suspension-manager.php:111 -#: includes/core/admin/class-wcs-admin-post-types.php:1387 +#: includes/core/admin/class-wcs-admin-post-types.php:1386 msgid "Suspend" msgstr "" @@ -1242,7 +1242,7 @@ msgid "Active for unlimited payments" msgstr "" #: includes/class-wcs-limited-recurring-coupon-manager.php:388 -#: includes/core/class-wc-subscriptions-coupon.php:1148 +#: includes/core/class-wc-subscriptions-coupon.php:1153 msgid "Sorry, it seems there are no available payment methods which support the recurring coupon you are using. Please contact us if you require assistance or wish to make alternate arrangements." msgstr "" @@ -1333,7 +1333,7 @@ msgid "Welcome to WooCommerce Subscriptions %s!" msgstr "" #: includes/class-wcs-upgrade-notice-manager.php:112 -#: includes/core/class-wcs-failed-scheduled-action-manager.php:232 +#: includes/core/class-wcs-failed-scheduled-action-manager.php:233 msgid "Learn more" msgstr "" @@ -1366,34 +1366,34 @@ msgid "Allow a subscription product with a $0 initial payment to be purchased wi msgstr "" #. translators: 1: relation type, 2: list of valid relation types. -#: includes/core/abstracts/abstract-wcs-related-order-store.php:148 +#: includes/core/abstracts/abstract-wcs-related-order-store.php:147 #, php-format msgid "Invalid relation type: %1$s. Order relationship type must be one of: %2$s." msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:211 +#: includes/core/admin/class-wc-subscriptions-admin.php:209 msgid "Simple subscription" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:212 +#: includes/core/admin/class-wc-subscriptions-admin.php:210 msgid "Variable subscription" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:233 +#: includes/core/admin/class-wc-subscriptions-admin.php:231 msgid "Downloadable" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:234 +#: includes/core/admin/class-wc-subscriptions-admin.php:232 msgid "Virtual" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:298 -#: templates/admin/html-variation-price.php:25 +#: includes/core/admin/class-wc-subscriptions-admin.php:296 +#: templates/admin/html-variation-price.php:27 msgid "Choose the subscription price, billing interval and period." msgstr "" #. translators: placeholder is trial period validation message if passed an invalid value (e.g. "Trial period can not exceed 4 weeks") -#: includes/core/admin/class-wc-subscriptions-admin.php:300 +#: includes/core/admin/class-wc-subscriptions-admin.php:298 #, php-format msgctxt "Trial period field tooltip on Edit Product administration screen" msgid "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" @@ -1401,7 +1401,7 @@ msgstr "" #. translators: %s: currency symbol. #. translators: placeholder is a currency symbol / code -#: includes/core/admin/class-wc-subscriptions-admin.php:315 +#: includes/core/admin/class-wc-subscriptions-admin.php:313 #: templates/admin/html-variation-price.php:23 #, php-format msgid "Subscription price (%s)" @@ -1409,126 +1409,126 @@ msgstr "" #. Translators: %s: formatted example price value. #. translators: %s the formatted example price value. -#: includes/core/admin/class-wc-subscriptions-admin.php:320 -#: includes/core/admin/class-wc-subscriptions-admin.php:359 -#: templates/admin/html-variation-price.php:30 +#: includes/core/admin/class-wc-subscriptions-admin.php:318 +#: includes/core/admin/class-wc-subscriptions-admin.php:360 +#: templates/admin/html-variation-price.php:33 #, php-format msgctxt "example price" msgid "e.g. %s" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:321 +#: includes/core/admin/class-wc-subscriptions-admin.php:319 msgid "Subscription interval" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:327 -#: includes/core/admin/class-wc-subscriptions-admin.php:484 +#: includes/core/admin/class-wc-subscriptions-admin.php:325 +#: includes/core/admin/class-wc-subscriptions-admin.php:488 msgid "Subscription period" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:343 -#: includes/core/admin/class-wc-subscriptions-admin.php:485 -#: templates/admin/html-variation-price.php:50 +#: includes/core/admin/class-wc-subscriptions-admin.php:344 +#: includes/core/admin/class-wc-subscriptions-admin.php:489 +#: templates/admin/html-variation-price.php:53 msgid "Stop renewing after" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:346 +#: includes/core/admin/class-wc-subscriptions-admin.php:347 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 -#: includes/core/admin/class-wc-subscriptions-admin.php:357 -#: templates/admin/html-variation-price.php:63 +#: includes/core/admin/class-wc-subscriptions-admin.php:358 +#: templates/admin/html-variation-price.php:69 #, php-format msgid "Sign-up fee (%s)" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:360 -#: templates/admin/html-variation-price.php:64 +#: includes/core/admin/class-wc-subscriptions-admin.php:361 +#: templates/admin/html-variation-price.php:72 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 "" -#: includes/core/admin/class-wc-subscriptions-admin.php:374 -#: includes/core/class-wc-subscriptions-cart.php:2416 -#: templates/admin/html-variation-price.php:70 +#: includes/core/admin/class-wc-subscriptions-admin.php:375 +#: includes/core/class-wc-subscriptions-cart.php:2426 +#: templates/admin/html-variation-price.php:79 msgid "Free trial" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:377 +#: includes/core/admin/class-wc-subscriptions-admin.php:378 #: templates/admin/deprecated/html-variation-price.php:115 msgid "Subscription Trial Period" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:417 +#: includes/core/admin/class-wc-subscriptions-admin.php:421 msgid "One time shipping" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:418 +#: includes/core/admin/class-wc-subscriptions-admin.php:422 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 "" -#: includes/core/admin/class-wc-subscriptions-admin.php:481 +#: includes/core/admin/class-wc-subscriptions-admin.php:485 msgid "Subscription pricing" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:482 +#: includes/core/admin/class-wc-subscriptions-admin.php:486 msgid "Subscription sign-up fee" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:483 +#: includes/core/admin/class-wc-subscriptions-admin.php:487 msgid "Subscription billing interval" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:486 +#: includes/core/admin/class-wc-subscriptions-admin.php:490 msgid "Free trial length" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:487 +#: includes/core/admin/class-wc-subscriptions-admin.php:491 msgid "Free trial period" msgstr "" #. translators: %s: subscription status. -#: includes/core/admin/class-wc-subscriptions-admin.php:810 +#: includes/core/admin/class-wc-subscriptions-admin.php:808 #, php-format msgid "Unable to change subscription status to \"%s\". Please assign a customer to the subscription to activate it." msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:864 +#: includes/core/admin/class-wc-subscriptions-admin.php:859 msgid "Trashing this order will also trash the subscriptions purchased with the order." msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:877 +#: includes/core/admin/class-wc-subscriptions-admin.php:872 msgid "Enter the new period, either day, week, month or year:" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:878 +#: includes/core/admin/class-wc-subscriptions-admin.php:873 msgid "Enter a new length (e.g. 5):" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:879 +#: includes/core/admin/class-wc-subscriptions-admin.php:874 msgid "Enter a new interval as a single number (e.g. to charge every 2nd month, enter 2):" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:880 +#: includes/core/admin/class-wc-subscriptions-admin.php:875 msgid "Delete all variations without a subscription" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:886 +#: includes/core/admin/class-wc-subscriptions-admin.php:881 msgid "An error occurred determining if that variation can be deleted. Please try again." msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:887 +#: includes/core/admin/class-wc-subscriptions-admin.php:882 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 "" -#: includes/core/admin/class-wc-subscriptions-admin.php:892 +#: includes/core/admin/class-wc-subscriptions-admin.php:887 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 "" -#: includes/core/admin/class-wc-subscriptions-admin.php:900 +#: includes/core/admin/class-wc-subscriptions-admin.php:895 msgid "" "WARNING: Bad things are about to happen!\n" "\n" @@ -1537,188 +1537,188 @@ 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 "" -#: includes/core/admin/class-wc-subscriptions-admin.php:901 +#: includes/core/admin/class-wc-subscriptions-admin.php:896 msgid "You are deleting a subscription item. You will also need to manually cancel and trash the subscription on the Manage Subscriptions screen." msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:908 +#: includes/core/admin/class-wc-subscriptions-admin.php:903 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 "" -#: includes/core/admin/class-wc-subscriptions-admin.php:912 +#: includes/core/admin/class-wc-subscriptions-admin.php:907 msgid "PayPal Standard has a number of limitations and does not support all subscription features." msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:912 +#: includes/core/admin/class-wc-subscriptions-admin.php:907 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 "" -#: includes/core/admin/class-wc-subscriptions-admin.php:915 +#: includes/core/admin/class-wc-subscriptions-admin.php:910 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$: "

" -#: includes/core/admin/class-wc-subscriptions-admin.php:932 +#: includes/core/admin/class-wc-subscriptions-admin.php:927 #, php-format 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$: "

" -#: includes/core/admin/class-wc-subscriptions-admin.php:934 +#: includes/core/admin/class-wc-subscriptions-admin.php:929 #, php-format 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 "" -#: includes/core/admin/class-wc-subscriptions-admin.php:960 +#: includes/core/admin/class-wc-subscriptions-admin.php:955 msgid "Active subscriber?" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:1004 +#: includes/core/admin/class-wc-subscriptions-admin.php:999 msgid "Manage Subscriptions" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:1008 -#: includes/core/class-wc-subscriptions-core-plugin.php:402 +#: includes/core/admin/class-wc-subscriptions-admin.php:1003 +#: includes/core/class-wc-subscriptions-core-plugin.php:405 msgid "Search Subscriptions" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:1234 +#: includes/core/admin/class-wc-subscriptions-admin.php:1230 msgctxt "options section heading" msgid "Miscellaneous" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:1241 +#: includes/core/admin/class-wc-subscriptions-admin.php:1237 msgid "Mixed Checkout" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:1242 +#: includes/core/admin/class-wc-subscriptions-admin.php:1238 msgid "Allow multiple subscriptions and products to be purchased simultaneously." msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:1246 +#: includes/core/admin/class-wc-subscriptions-admin.php:1242 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. -#: includes/core/admin/class-wc-subscriptions-admin.php:1348 -#: includes/core/admin/class-wc-subscriptions-admin.php:1590 +#: includes/core/admin/class-wc-subscriptions-admin.php:1344 +#: includes/core/admin/class-wc-subscriptions-admin.php:1587 #, php-format msgid "We can't find a subscription with ID #%d. Perhaps it was deleted?" msgstr "" #. translators: Placeholders are opening and closing link tags. -#: includes/core/admin/class-wc-subscriptions-admin.php:1441 -#: includes/core/admin/class-wc-subscriptions-admin.php:1503 +#: includes/core/admin/class-wc-subscriptions-admin.php:1437 +#: includes/core/admin/class-wc-subscriptions-admin.php:1499 #, php-format 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 "" -#: includes/core/admin/class-wc-subscriptions-admin.php:1558 +#: includes/core/admin/class-wc-subscriptions-admin.php:1555 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 -#: includes/core/admin/class-wc-subscriptions-admin.php:1597 +#: includes/core/admin/class-wc-subscriptions-admin.php:1594 #, php-format msgid "Showing orders for %1$sSubscription %2$s%3$s" msgstr "" #. translators: number of 1$: days, 2$: weeks, 3$: months, 4$: years -#: includes/core/admin/class-wc-subscriptions-admin.php:1620 +#: includes/core/admin/class-wc-subscriptions-admin.php:1617 #, php-format 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") -#: includes/core/admin/class-wc-subscriptions-admin.php:1625 +#: includes/core/admin/class-wc-subscriptions-admin.php:1622 #, php-format msgid "The trial period can not exceed %s." msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:1650 +#: includes/core/admin/class-wc-subscriptions-admin.php:1647 msgid "Please log in to your account to view your subscriptions." msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:1687 +#: includes/core/admin/class-wc-subscriptions-admin.php:1684 msgid "No subscriptions found for that customer." msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:1689 +#: includes/core/admin/class-wc-subscriptions-admin.php:1686 msgid "You do not have permission to view those subscriptions." msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:1723 +#: includes/core/admin/class-wc-subscriptions-admin.php:1720 #: includes/core/admin/class-wcs-admin-system-status.php:137 msgctxt "label that indicates whether debugging is turned on for the plugin" msgid "WCS_DEBUG" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:1729 +#: includes/core/admin/class-wc-subscriptions-admin.php:1726 #: includes/core/admin/class-wcs-admin-system-status.php:151 msgctxt "Live or Staging, Label on WooCommerce -> System Status page" msgid "Subscriptions Mode" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:1730 +#: includes/core/admin/class-wc-subscriptions-admin.php:1727 #: includes/core/admin/class-wcs-admin-system-status.php:153 msgctxt "refers to staging site" msgid "Staging" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:1730 +#: includes/core/admin/class-wc-subscriptions-admin.php:1727 #: includes/core/admin/class-wcs-admin-system-status.php:153 msgctxt "refers to live site" msgid "Live" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:1760 +#: includes/core/admin/class-wc-subscriptions-admin.php:1757 msgid "Automatic Recurring Payments" msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:1793 +#: includes/core/admin/class-wc-subscriptions-admin.php:1790 msgid "Supports automatic renewal payments." msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:1874 +#: includes/core/admin/class-wc-subscriptions-admin.php:1871 msgid "Subscription items can no longer be edited." msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:1879 +#: includes/core/admin/class-wc-subscriptions-admin.php:1876 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 -#: includes/core/admin/class-wc-subscriptions-admin.php:1898 +#: includes/core/admin/class-wc-subscriptions-admin.php:1895 #, php-format msgid "No payment gateways capable of processing automatic subscription payments are enabled. If you would like to process automatic payments, we recommend %1$sWooPayments%2$s." msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:1905 +#: includes/core/admin/class-wc-subscriptions-admin.php:1902 msgid "Recurring Payments" msgstr "" #. translators: placeholders are opening and closing link tags -#: includes/core/admin/class-wc-subscriptions-admin.php:1913 +#: includes/core/admin/class-wc-subscriptions-admin.php:1910 #, php-format msgid "Payment gateways which don't support automatic recurring payments can be used to process %1$smanual subscription renewal payments%2$s." msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:2033 +#: includes/core/admin/class-wc-subscriptions-admin.php:2031 msgid "Note that purchasing a subscription still requires an account." msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:2047 +#: includes/core/admin/class-wc-subscriptions-admin.php:2045 msgid "The product type can not be changed because this product is associated with subscriptions." msgstr "" -#: includes/core/admin/class-wc-subscriptions-admin.php:2104 -#: includes/core/admin/class-wc-subscriptions-admin.php:2105 +#: includes/core/admin/class-wc-subscriptions-admin.php:2102 +#: includes/core/admin/class-wc-subscriptions-admin.php:2103 msgid "Allow subscription customers to create an account during checkout" msgstr "" @@ -1771,115 +1771,119 @@ msgid "Please enter a date after the next payment." msgstr "" #: includes/core/admin/class-wcs-admin-meta-boxes.php:181 +msgid "Invalid date" +msgstr "" + +#: includes/core/admin/class-wcs-admin-meta-boxes.php:182 msgid "" "Are you sure you want to process a renewal?\n" "\n" "This will charge the customer and email them the renewal order (if emails are enabled)." msgstr "" -#: includes/core/admin/class-wcs-admin-meta-boxes.php:197 +#: includes/core/admin/class-wcs-admin-meta-boxes.php:198 msgid "" "Are you sure you want to retry payment for this renewal order?\n" "\n" "This will attempt to charge the customer and send renewal order emails (if emails are enabled)." msgstr "" -#: includes/core/admin/class-wcs-admin-meta-boxes.php:228 +#: includes/core/admin/class-wcs-admin-meta-boxes.php:229 msgid "Process renewal" msgstr "" -#: includes/core/admin/class-wcs-admin-meta-boxes.php:232 +#: includes/core/admin/class-wcs-admin-meta-boxes.php:233 msgid "Create pending renewal order" msgstr "" -#: includes/core/admin/class-wcs-admin-meta-boxes.php:234 +#: includes/core/admin/class-wcs-admin-meta-boxes.php:235 msgid "Create pending parent order" msgstr "" -#: includes/core/admin/class-wcs-admin-meta-boxes.php:238 +#: includes/core/admin/class-wcs-admin-meta-boxes.php:239 msgid "Retry Renewal Payment" msgstr "" -#: includes/core/admin/class-wcs-admin-meta-boxes.php:251 +#: includes/core/admin/class-wcs-admin-meta-boxes.php:252 msgid "Process renewal order action requested by admin." msgstr "" -#: includes/core/admin/class-wcs-admin-meta-boxes.php:261 +#: includes/core/admin/class-wcs-admin-meta-boxes.php:262 msgid "Create pending renewal order requested by admin action." msgstr "" -#: includes/core/admin/class-wcs-admin-meta-boxes.php:267 +#: includes/core/admin/class-wcs-admin-meta-boxes.php:268 msgid "Pending renewal order was not created, as it was not possible to update the subscription status." msgstr "" -#: includes/core/admin/class-wcs-admin-meta-boxes.php:279 +#: includes/core/admin/class-wcs-admin-meta-boxes.php:280 msgid "Creation of the pending renewal order failed." msgstr "" #. Translators: %1$s opening link tag, %2$s closing link tag. -#: includes/core/admin/class-wcs-admin-meta-boxes.php:300 +#: includes/core/admin/class-wcs-admin-meta-boxes.php:301 #, php-format msgid "A pending %1$srenewal order%2$s was successfully created!" msgstr "" #. Translators: %1$s opening link tag, %2$s closing link tag. -#: includes/core/admin/class-wcs-admin-meta-boxes.php:312 +#: includes/core/admin/class-wcs-admin-meta-boxes.php:313 #, php-format msgid "A %1$spending renewal order%2$s was successfully created, but there was a problem setting the payment method. Please review the order." msgstr "" -#: includes/core/admin/class-wcs-admin-meta-boxes.php:348 +#: includes/core/admin/class-wcs-admin-meta-boxes.php:349 msgid "Create pending parent order requested by admin action." msgstr "" -#: includes/core/admin/class-wcs-admin-meta-boxes.php:379 +#: includes/core/admin/class-wcs-admin-meta-boxes.php:380 msgid "Retry renewal payment action requested by admin." msgstr "" -#: includes/core/admin/class-wcs-admin-meta-boxes.php:475 +#: includes/core/admin/class-wcs-admin-meta-boxes.php:480 msgid "This order contains line items with prices above the current product price. To override the product's live price when the customer pays for this order, lock in the manual price increases." msgstr "" -#: includes/core/admin/class-wcs-admin-meta-boxes.php:479 +#: includes/core/admin/class-wcs-admin-meta-boxes.php:484 msgid "Lock manual price increases" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:239 +#: includes/core/admin/class-wcs-admin-post-types.php:237 msgid "Search for a product…" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:303 -#: includes/core/admin/class-wcs-admin-post-types.php:334 +#: includes/core/admin/class-wcs-admin-post-types.php:301 +#: includes/core/admin/class-wcs-admin-post-types.php:332 msgctxt "an action on a subscription" msgid "Move to Trash" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:312 +#: includes/core/admin/class-wcs-admin-post-types.php:310 msgctxt "an action on a subscription" msgid "Restore" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:313 +#: includes/core/admin/class-wcs-admin-post-types.php:311 msgctxt "an action on a subscription" msgid "Delete Permanently" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:331 -#: includes/core/admin/class-wcs-admin-post-types.php:1775 +#: includes/core/admin/class-wcs-admin-post-types.php:329 +#: includes/core/admin/class-wcs-admin-post-types.php:1774 msgctxt "an action on a subscription" msgid "Activate" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:332 -#: includes/core/admin/class-wcs-admin-post-types.php:1776 +#: includes/core/admin/class-wcs-admin-post-types.php:330 +#: includes/core/admin/class-wcs-admin-post-types.php:1775 msgctxt "an action on a subscription" msgid "Put on-hold" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:333 -#: includes/core/admin/class-wcs-admin-post-types.php:1388 -#: includes/core/admin/class-wcs-admin-post-types.php:1777 -#: includes/core/class-wc-subscriptions-manager.php:1955 +#: includes/core/admin/class-wcs-admin-post-types.php:331 +#: includes/core/admin/class-wcs-admin-post-types.php:1387 +#: includes/core/admin/class-wcs-admin-post-types.php:1776 +#: includes/core/class-wc-subscriptions-manager.php:1967 #: includes/core/wcs-user-functions.php:329 #: templates/myaccount/related-orders.php:83 msgctxt "an action on a subscription" @@ -1887,7 +1891,7 @@ msgid "Cancel" msgstr "" #. translators: placeholder is the number of subscriptions updated -#: includes/core/admin/class-wcs-admin-post-types.php:421 +#: includes/core/admin/class-wcs-admin-post-types.php:419 #, php-format msgid "%s subscription status changed." msgid_plural "%s subscription statuses changed." @@ -1895,14 +1899,14 @@ msgstr[0] "" msgstr[1] "" #. translators: 1$: is the number of subscriptions not updated, 2$: is the error message -#: includes/core/admin/class-wcs-admin-post-types.php:440 +#: includes/core/admin/class-wcs-admin-post-types.php:438 #, php-format msgid "%1$s subscription could not be updated: %2$s" msgid_plural "%1$s subscriptions could not be updated: %2$s" msgstr[0] "" msgstr[1] "" -#: includes/core/admin/class-wcs-admin-post-types.php:468 +#: includes/core/admin/class-wcs-admin-post-types.php:466 #: includes/core/admin/meta-boxes/views/html-related-orders-table.php:17 #: templates/myaccount/my-subscriptions.php:22 #: templates/myaccount/my-subscriptions.php:40 @@ -1914,8 +1918,8 @@ msgstr[1] "" msgid "Status" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:469 -#: includes/core/class-wc-subscriptions-core-plugin.php:394 +#: includes/core/admin/class-wcs-admin-post-types.php:467 +#: includes/core/class-wc-subscriptions-core-plugin.php:397 #: templates/emails/cancelled-subscription.php:39 #: templates/emails/expired-subscription.php:24 #: templates/emails/on-hold-subscription.php:24 @@ -1924,84 +1928,86 @@ msgstr "" msgid "Subscription" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:470 +#: includes/core/admin/class-wcs-admin-post-types.php:468 msgid "Items" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:471 +#: includes/core/admin/class-wcs-admin-post-types.php:469 #: assets/src/js/recurring-totals/index.js:188 #: assets/src/js/recurring-totals/index.js:285 +#: build/index.js:15 +#: build/index.js:18 msgid "Total" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:472 +#: includes/core/admin/class-wcs-admin-post-types.php:470 msgid "Start Date" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:473 +#: includes/core/admin/class-wcs-admin-post-types.php:471 msgid "Trial End" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:474 +#: includes/core/admin/class-wcs-admin-post-types.php:472 msgid "Next Payment" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:475 +#: includes/core/admin/class-wcs-admin-post-types.php:473 msgid "Last Order Date" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:476 +#: includes/core/admin/class-wcs-admin-post-types.php:474 msgid "End Date" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:477 +#: includes/core/admin/class-wcs-admin-post-types.php:475 msgctxt "number of orders linked to a subscription" msgid "Orders" msgstr "" #. translators: placeholder is a subscription ID. -#: includes/core/admin/class-wcs-admin-post-types.php:512 +#: includes/core/admin/class-wcs-admin-post-types.php:510 #, php-format msgctxt "hash before subscription number" msgid "#%s" msgstr "" #. translators: Placeholder is a
HTML tag. -#: includes/core/admin/class-wcs-admin-post-types.php:524 +#: includes/core/admin/class-wcs-admin-post-types.php:522 #, php-format msgid "This subscription couldn't be loaded from the database. %s Click to learn more." msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:562 +#: includes/core/admin/class-wcs-admin-post-types.php:560 msgctxt "meaning billing address" msgid "Billing:" msgstr "" #. translators: placeholder is customer's billing email -#: includes/core/admin/class-wcs-admin-post-types.php:567 +#: includes/core/admin/class-wcs-admin-post-types.php:565 #, php-format msgid "Email: %s" msgstr "" #. translators: placeholder is customer's billing phone number -#: includes/core/admin/class-wcs-admin-post-types.php:572 +#: includes/core/admin/class-wcs-admin-post-types.php:570 #, php-format msgid "Tel: %s" msgstr "" #. translators: $1: is opening link, $2: is subscription order number, $3: is closing link tag, $4: is user's name -#: includes/core/admin/class-wcs-admin-post-types.php:603 +#: includes/core/admin/class-wcs-admin-post-types.php:601 #, php-format msgctxt "Subscription title on admin table. (e.g.: #211 for John Doe)" msgid "%1$s#%2$s%3$s for %4$s" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:612 +#: includes/core/admin/class-wcs-admin-post-types.php:610 msgid "Show more details" msgstr "" #. translators: %d: item count. -#: includes/core/admin/class-wcs-admin-post-types.php:633 +#: includes/core/admin/class-wcs-admin-post-types.php:631 #, php-format msgid "%d item" msgid_plural "%d items" @@ -2010,135 +2016,135 @@ msgstr[1] "" #. translators: placeholder is the display name of a payment gateway a subscription was paid by #. translators: %s: payment method. -#: includes/core/admin/class-wcs-admin-post-types.php:652 -#: includes/core/class-wc-subscription.php:2327 +#: includes/core/admin/class-wcs-admin-post-types.php:650 +#: includes/core/class-wc-subscription.php:2388 #, php-format msgid "Via %s" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:701 +#: includes/core/admin/class-wcs-admin-post-types.php:699 msgid "Y/m/d g:i:s A" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:716 +#: includes/core/admin/class-wcs-admin-post-types.php:714 msgid "Subscription payment overdue.
" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:721 +#: includes/core/admin/class-wcs-admin-post-types.php:719 msgid "This date should be treated as an estimate only. The payment gateway for this subscription controls when payments are processed.
" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:1116 -#: includes/core/admin/class-wcs-admin-post-types.php:1119 -#: includes/core/admin/class-wcs-admin-post-types.php:1122 +#: includes/core/admin/class-wcs-admin-post-types.php:1114 +#: includes/core/admin/class-wcs-admin-post-types.php:1117 +#: includes/core/admin/class-wcs-admin-post-types.php:1120 msgid "Subscription updated." msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:1117 +#: includes/core/admin/class-wcs-admin-post-types.php:1115 msgid "Custom field updated." msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:1118 +#: includes/core/admin/class-wcs-admin-post-types.php:1116 msgid "Custom field deleted." msgstr "" #. translators: placeholder is previous post title -#: includes/core/admin/class-wcs-admin-post-types.php:1121 +#: includes/core/admin/class-wcs-admin-post-types.php:1119 #, php-format msgctxt "used in post updated messages" msgid "Subscription restored to revision from %s" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:1123 +#: includes/core/admin/class-wcs-admin-post-types.php:1121 msgid "Subscription saved." msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:1124 +#: includes/core/admin/class-wcs-admin-post-types.php:1122 msgid "Subscription submitted." msgstr "" #. translators: php date string -#: includes/core/admin/class-wcs-admin-post-types.php:1126 +#: includes/core/admin/class-wcs-admin-post-types.php:1124 #, php-format msgid "Subscription scheduled for: %1$s." msgstr "" #. translators: php date string -#: includes/core/admin/class-wcs-admin-post-types.php:1126 +#: includes/core/admin/class-wcs-admin-post-types.php:1124 msgctxt "used in \"Subscription scheduled for \"" msgid "M j, Y @ G:i" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:1127 +#: includes/core/admin/class-wcs-admin-post-types.php:1125 msgid "Subscription draft updated." msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:1170 +#: includes/core/admin/class-wcs-admin-post-types.php:1168 msgid "Any Payment Method" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:1171 +#: includes/core/admin/class-wcs-admin-post-types.php:1169 msgid "None" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:1177 -#: includes/core/class-wc-subscription.php:2309 -#: includes/core/class-wcs-change-payment-method-admin.php:170 +#: includes/core/admin/class-wcs-admin-post-types.php:1176 +#: includes/core/class-wc-subscription.php:2370 +#: includes/core/class-wcs-change-payment-method-admin.php:173 msgid "Manual Renewal" msgstr "" #. translators: 1: user display name 2: user ID 3: user email -#: includes/core/admin/class-wcs-admin-post-types.php:1341 +#: includes/core/admin/class-wcs-admin-post-types.php:1340 #, php-format msgid "%1$s (#%2$s – %3$s)" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:1348 -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:96 +#: includes/core/admin/class-wcs-admin-post-types.php:1347 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:99 msgid "Search for a customer…" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:1386 +#: includes/core/admin/class-wcs-admin-post-types.php:1385 #: includes/core/wcs-user-functions.php:311 msgid "Reactivate" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:1389 +#: includes/core/admin/class-wcs-admin-post-types.php:1388 msgid "Trash" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:1390 +#: includes/core/admin/class-wcs-admin-post-types.php:1389 msgid "Delete Permanently" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:1409 -#: includes/core/class-wc-subscriptions-product.php:765 +#: includes/core/admin/class-wcs-admin-post-types.php:1408 +#: includes/core/class-wc-subscriptions-product.php:763 msgid "Restore this item from the Trash" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:1411 -#: includes/core/class-wc-subscriptions-product.php:766 +#: includes/core/admin/class-wcs-admin-post-types.php:1410 +#: includes/core/class-wc-subscriptions-product.php:764 msgid "Restore" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:1416 +#: includes/core/admin/class-wcs-admin-post-types.php:1415 msgid "Move this item to the Trash" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:1430 +#: includes/core/admin/class-wcs-admin-post-types.php:1429 msgid "Delete this item permanently" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:1441 +#: includes/core/admin/class-wcs-admin-post-types.php:1440 msgid "Cancel Now" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:1507 +#: includes/core/admin/class-wcs-admin-post-types.php:1506 msgctxt "Used in order note. Reason why status changed." msgid "Subscription status changed by bulk edit:" msgstr "" -#: includes/core/admin/class-wcs-admin-post-types.php:1625 +#: includes/core/admin/class-wcs-admin-post-types.php:1624 msgid "All" msgstr "" @@ -2334,134 +2340,141 @@ msgctxt "relation to order" msgid "Unknown Order Type" msgstr "" +#. translators: 1$ is a comma-separated list of invalid dates fields like "Start Date", "Next Payment", 2$-3$: opening and closing tags. +#: includes/core/admin/meta-boxes/class-wcs-meta-box-schedule.php:126 +#, php-format +msgid "Some subscription dates could not be updated because they contain invalid values: %2$s%1$s%3$s. Please correct these dates and save the changes." +msgstr "" + #. translators: placeholder is the ID of the subscription -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:54 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:57 #, php-format msgctxt "edit subscription header" msgid "Subscription #%s details" msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:60 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:63 msgid "General" msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:63 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:66 msgid "Customer:" msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:73 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:76 msgid "View other subscriptions →" msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:78 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:81 msgid "Profile →" msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:105 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:108 msgid "Subscription status:" msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:123 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:126 msgid "Parent order: " msgstr "" #. translators: placeholder is an order number. -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:127 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:130 #, php-format msgid "#%1$s" msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:135 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:138 msgid "Parent order:" msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:142 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:145 msgid "Select an order…" msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:155 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:158 msgid "Billing" msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:156 -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:254 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:159 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:257 #: includes/payment-retry/data-stores/class-wcs-retry-post-store.php:53 msgid "Edit" msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:158 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:161 msgid "Load billing address" msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:166 -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:168 -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:265 -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:267 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:169 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:171 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:268 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:270 msgid "Address" msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:168 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:171 msgid "No billing address set." msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:188 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:191 #: includes/core/class-wcs-change-payment-method-admin.php:36 #: includes/core/class-wcs-change-payment-method-admin.php:47 msgid "Payment method" msgstr "" #. translators: %s: gateway ID. -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:193 -#: includes/core/class-wcs-change-payment-method-admin.php:49 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:196 +#: includes/core/class-wcs-change-payment-method-admin.php:51 #, php-format msgctxt "The gateway ID displayed on the Edit Subscriptions screen when editing payment method." msgid "Gateway ID: [%s]" msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:237 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:240 msgid "Customer change payment method page →" msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:239 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:242 msgid "Customer add payment method page →" msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:253 -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:291 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:256 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:298 #: assets/src/js/recurring-totals/index.js:104 +#: build/index.js:10 msgid "Shipping" msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:256 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:259 msgid "Load shipping address" msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:257 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:260 msgid "Copy billing address" msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:267 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:270 msgid "No shipping address set." msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:289 -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:325 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:292 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:328 msgid "Customer Provided Note" msgstr "" -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:326 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:329 msgid "Customer's notes about the order" msgstr "" #. translators: %s: parent order number (linked to its details screen). -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:430 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:433 #, php-format msgctxt "subscription note after linking to a parent order" msgid "Subscription linked to parent order %s via admin." msgstr "" #. translators: placeholder is error message from the payment gateway or subscriptions when updating the status -#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:445 +#: includes/core/admin/meta-boxes/class-wcs-meta-box-subscription-data.php:448 #, php-format msgid "Error updating some information: %s" msgstr "" @@ -2526,11 +2539,15 @@ msgstr "" msgid "Recurring:" msgstr "" -#: includes/core/admin/meta-boxes/views/html-subscription-schedule.php:66 +#: includes/core/admin/meta-boxes/views/html-subscription-schedule.php:65 +msgid "Error:" +msgstr "" + +#: includes/core/admin/meta-boxes/views/html-subscription-schedule.php:70 msgid "Timezone:" msgstr "" -#: includes/core/admin/meta-boxes/views/html-subscription-schedule.php:66 +#: includes/core/admin/meta-boxes/views/html-subscription-schedule.php:70 msgid "Error: unable to find timezone of your browser." msgstr "" @@ -2545,154 +2562,154 @@ msgid "Read more" msgstr "" #. translators: %s: subscription status. -#: includes/core/class-wc-subscription.php:444 +#: includes/core/class-wc-subscription.php:445 #, php-format msgid "Unable to change subscription status to \"%s\"." msgstr "" #. translators: 1: subscription status, 2: error message. -#: includes/core/class-wc-subscription.php:567 +#: includes/core/class-wc-subscription.php:568 #, php-format msgid "Unable to change subscription status to \"%1$s\". Exception: %2$s" msgstr "" #. translators: 1: old subscription status 2: new subscription status -#: includes/core/class-wc-subscription.php:600 +#: includes/core/class-wc-subscription.php:603 #, php-format msgid "Status changed from %1$s to %2$s." msgstr "" #. translators: %s: new order status -#: includes/core/class-wc-subscription.php:614 +#: includes/core/class-wc-subscription.php:617 #, php-format msgid "Status set to %s." msgstr "" -#: includes/core/class-wc-subscription.php:628 +#: includes/core/class-wc-subscription.php:631 msgid "Error during subscription status transition." msgstr "" #. translators: placeholder is human time diff (e.g. "3 weeks") -#: includes/core/class-wc-subscription.php:1344 -#: includes/core/class-wc-subscriptions-manager.php:2402 +#: includes/core/class-wc-subscription.php:1347 +#: includes/core/class-wc-subscriptions-manager.php:2414 #, php-format msgid "In %s" msgstr "" #. translators: placeholder is human time diff (e.g. "3 weeks") -#: includes/core/class-wc-subscription.php:1347 +#: includes/core/class-wc-subscription.php:1350 #: includes/core/wcs-formatting-functions.php:246 #, php-format msgid "%s ago" msgstr "" -#: includes/core/class-wc-subscription.php:1354 +#: includes/core/class-wc-subscription.php:1357 msgid "Not yet ended" msgstr "" -#: includes/core/class-wc-subscription.php:1357 +#: includes/core/class-wc-subscription.php:1360 msgid "Not cancelled" msgstr "" -#: includes/core/class-wc-subscription.php:1362 +#: includes/core/class-wc-subscription.php:1365 msgctxt "original denotes there is no date to display" msgid "-" msgstr "" -#: includes/core/class-wc-subscription.php:1471 +#: includes/core/class-wc-subscription.php:1529 msgid "The creation date of a subscription can not be deleted, only updated." msgstr "" -#: includes/core/class-wc-subscription.php:1474 +#: includes/core/class-wc-subscription.php:1532 msgid "The start date of a subscription can not be deleted, only updated." msgstr "" #. translators: %s: date type (e.g. "trial_end"). -#: includes/core/class-wc-subscription.php:1479 +#: includes/core/class-wc-subscription.php:1537 #, php-format msgid "The %s date of a subscription can not be deleted. You must delete the order." msgstr "" #. translators: %d: subscription ID. #. translators: %d: order ID. -#: includes/core/class-wc-subscription.php:1488 -#: includes/core/class-wc-subscription.php:2750 +#: includes/core/class-wc-subscription.php:1546 +#: includes/core/class-wc-subscription.php:2866 #, php-format msgid "Subscription #%d: " msgstr "" -#: includes/core/class-wc-subscription.php:1951 +#: includes/core/class-wc-subscription.php:2012 msgid "Payment status marked complete." msgstr "" -#: includes/core/class-wc-subscription.php:1993 +#: includes/core/class-wc-subscription.php:2054 msgid "Payment failed." msgstr "" -#: includes/core/class-wc-subscription.php:1998 +#: includes/core/class-wc-subscription.php:2059 msgid "Subscription Cancelled: maximum number of failed payments reached." msgstr "" -#: includes/core/class-wc-subscription.php:2112 +#: includes/core/class-wc-subscription.php:2173 msgid "The \"all\" value for $order_type parameter is deprecated. It was a misnomer, as it did not return resubscribe orders. It was also inconsistent with order type values accepted by wcs_get_subscription_orders(). Use array( \"parent\", \"renewal\", \"switch\" ) to maintain previous behaviour, or \"any\" to receive all order types, including switch and resubscribe." msgstr "" -#: includes/core/class-wc-subscription.php:2406 -#: includes/core/wcs-functions.php:868 +#: includes/core/class-wc-subscription.php:2468 +#: includes/core/wcs-functions.php:867 msgid "Payment method meta must be an array." msgstr "" -#: includes/core/class-wc-subscription.php:2642 +#: includes/core/class-wc-subscription.php:2735 msgid "Invalid format. First parameter needs to be an array." msgstr "" -#: includes/core/class-wc-subscription.php:2646 +#: includes/core/class-wc-subscription.php:2739 msgid "Invalid data. First parameter was empty when passed to update_dates()." msgstr "" -#: includes/core/class-wc-subscription.php:2653 +#: includes/core/class-wc-subscription.php:2746 msgid "Invalid data. First parameter has a date that is not in the registered date types." msgstr "" #. translators: placeholder is date type (e.g. "end", "next_payment"...) -#: includes/core/class-wc-subscription.php:2680 +#: includes/core/class-wc-subscription.php:2801 #, php-format msgctxt "appears in an error message if date is wrong format" msgid "Invalid %s date. The date must be of the format: \"Y-m-d H:i:s\"." msgstr "" #. translators: %s: date type (e.g. "end"). -#: includes/core/class-wc-subscription.php:2718 +#: includes/core/class-wc-subscription.php:2834 #, php-format msgid "The %s date must occur after the cancellation date." msgstr "" #. translators: %s: date type (e.g. "end"). -#: includes/core/class-wc-subscription.php:2724 +#: includes/core/class-wc-subscription.php:2840 #, php-format msgid "The %s date must occur after the last payment date." msgstr "" #. translators: %s: date type (e.g. "end"). -#: includes/core/class-wc-subscription.php:2729 +#: includes/core/class-wc-subscription.php:2845 #, php-format msgid "The %s date must occur after the next payment date." msgstr "" #. translators: %s: date type (e.g. "end"). -#: includes/core/class-wc-subscription.php:2735 +#: includes/core/class-wc-subscription.php:2851 #, php-format msgid "The %s date must occur after the trial end date." msgstr "" #. translators: %s: date type (e.g. "next_payment"). -#: includes/core/class-wc-subscription.php:2740 +#: includes/core/class-wc-subscription.php:2856 #, php-format msgid "The %s date must occur after the start date." msgstr "" -#: includes/core/class-wc-subscription.php:2770 -#: includes/core/class-wc-subscriptions-checkout.php:348 +#: includes/core/class-wc-subscription.php:2886 +#: includes/core/class-wc-subscriptions-checkout.php:349 msgid "Backordered" msgstr "" @@ -2720,7 +2737,7 @@ msgstr "" #. translators: %s: order number. #. translators: placeholder is a subscription ID. #: includes/core/class-wc-subscriptions-addresses.php:346 -#: includes/core/class-wc-subscriptions-change-payment-gateway.php:763 +#: includes/core/class-wc-subscriptions-change-payment-gateway.php:762 #: includes/core/class-wcs-query.php:101 #, php-format msgctxt "hash before order number" @@ -2763,24 +2780,24 @@ msgid "That product can not be added to your cart as it already contains a subsc msgstr "" #: includes/core/class-wc-subscriptions-cart-validator.php:179 -#: includes/core/class-wc-subscriptions-cart.php:1473 +#: includes/core/class-wc-subscriptions-cart.php:1472 msgid "That subscription product can not be added to your cart as it already contains a subscription renewal." msgstr "" -#: includes/core/class-wc-subscriptions-cart.php:536 +#: includes/core/class-wc-subscriptions-cart.php:535 msgid "Initial Shipment" msgstr "" -#: includes/core/class-wc-subscriptions-cart.php:1111 +#: includes/core/class-wc-subscriptions-cart.php:1110 msgid "Invalid recurring shipping method." msgstr "" -#: includes/core/class-wc-subscriptions-cart.php:2149 +#: includes/core/class-wc-subscriptions-cart.php:2157 msgid "now" msgstr "" #. translators: placeholder is a number of days. -#: includes/core/class-wc-subscriptions-cart.php:2311 +#: includes/core/class-wc-subscriptions-cart.php:2319 #: includes/core/wcs-time-functions.php:58 #, php-format msgid "%s day" @@ -2789,7 +2806,7 @@ msgstr[0] "" msgstr[1] "" #. translators: placeholder is a number of weeks. -#: includes/core/class-wc-subscriptions-cart.php:2315 +#: includes/core/class-wc-subscriptions-cart.php:2323 #: includes/core/wcs-time-functions.php:60 #, php-format msgid "%s week" @@ -2798,7 +2815,7 @@ msgstr[0] "" msgstr[1] "" #. translators: placeholder is a number of months. -#: includes/core/class-wc-subscriptions-cart.php:2319 +#: includes/core/class-wc-subscriptions-cart.php:2327 #: includes/core/wcs-time-functions.php:62 #, php-format msgid "%s month" @@ -2807,7 +2824,7 @@ msgstr[0] "" msgstr[1] "" #. translators: placeholder is a number of years. -#: includes/core/class-wc-subscriptions-cart.php:2323 +#: includes/core/class-wc-subscriptions-cart.php:2331 #: includes/core/wcs-time-functions.php:64 #, php-format msgid "%s year" @@ -2816,56 +2833,56 @@ msgstr[0] "" msgstr[1] "" #. translators: 1$: day of the week (e.g. "every Wednesday"). -#: includes/core/class-wc-subscriptions-cart.php:2345 +#: includes/core/class-wc-subscriptions-cart.php:2353 #, php-format msgid "every %1$s" msgstr "" #. translators: 1$: period, 2$: day of the week (e.g. "every 2nd week on Wednesday"). -#: includes/core/class-wc-subscriptions-cart.php:2349 +#: includes/core/class-wc-subscriptions-cart.php:2357 #, php-format msgid "every %1$s on %2$s" msgstr "" -#: includes/core/class-wc-subscriptions-cart.php:2358 +#: includes/core/class-wc-subscriptions-cart.php:2366 msgid "on the last day of each month" msgstr "" #. translators: 1$: day of the month (e.g. "23rd") (e.g. "every 23rd of each month"). -#: includes/core/class-wc-subscriptions-cart.php:2362 +#: includes/core/class-wc-subscriptions-cart.php:2370 #, php-format msgid "on the %1$s of each month" msgstr "" #. translators: 1$: interval (e.g. "3rd") (e.g. "on the last day of every 3rd month"). -#: includes/core/class-wc-subscriptions-cart.php:2370 +#: includes/core/class-wc-subscriptions-cart.php:2378 #, php-format msgid "on the last day of every %1$s month" msgstr "" #. translators: on the, 1$: day of every, 2$: month (e.g. "on the 23rd day of every 2nd month"). -#: includes/core/class-wc-subscriptions-cart.php:2376 +#: includes/core/class-wc-subscriptions-cart.php:2384 #, php-format msgid "on the %1$s day of every %2$s month" msgstr "" #. translators: on, 1$: , 2$: each year (e.g. "on March 15th each year"). -#: includes/core/class-wc-subscriptions-cart.php:2387 +#: includes/core/class-wc-subscriptions-cart.php:2395 #, php-format msgid "on %1$s %2$s each year" msgstr "" #. translators: 1$: month (e.g. "March"), 2$: day of the month (e.g. "23rd), 3$: interval year (r.g March 23rd every 2nd year"). -#: includes/core/class-wc-subscriptions-cart.php:2394 +#: includes/core/class-wc-subscriptions-cart.php:2402 #, php-format msgid "on %1$s %2$s every %3$s year" msgstr "" -#: includes/core/class-wc-subscriptions-cart.php:2426 +#: includes/core/class-wc-subscriptions-cart.php:2436 msgid "Sign up fee" msgstr "" -#: includes/core/class-wc-subscriptions-cart.php:2436 +#: includes/core/class-wc-subscriptions-cart.php:2446 msgid "Renews" msgstr "" @@ -2897,9 +2914,9 @@ msgid "Invalid Subscription." msgstr "" #: includes/core/class-wc-subscriptions-change-payment-gateway.php:232 -#: includes/core/class-wcs-cart-resubscribe.php:78 -#: includes/core/class-wcs-cart-resubscribe.php:129 -#: includes/core/class-wcs-user-change-status-handler.php:111 +#: includes/core/class-wcs-cart-resubscribe.php:83 +#: includes/core/class-wcs-cart-resubscribe.php:133 +#: includes/core/class-wcs-user-change-status-handler.php:112 #: includes/early-renewal/class-wcs-cart-early-renewal.php:98 msgid "That doesn't appear to be one of your subscriptions." msgstr "" @@ -2923,127 +2940,127 @@ msgctxt "label on button, imperative" msgid "Add payment" msgstr "" -#: includes/core/class-wc-subscriptions-change-payment-gateway.php:323 +#: includes/core/class-wc-subscriptions-change-payment-gateway.php:322 msgid "Payment method updated." msgstr "" -#: includes/core/class-wc-subscriptions-change-payment-gateway.php:323 +#: includes/core/class-wc-subscriptions-change-payment-gateway.php:322 msgid "Payment method added." msgstr "" -#: includes/core/class-wc-subscriptions-change-payment-gateway.php:376 -#: includes/core/class-wc-subscriptions-change-payment-gateway.php:378 +#: includes/core/class-wc-subscriptions-change-payment-gateway.php:375 +#: includes/core/class-wc-subscriptions-change-payment-gateway.php:377 msgid "Payment method updated for all your current subscriptions." msgstr "" #. translators: 1: old payment title, 2: new payment title. -#: includes/core/class-wc-subscriptions-change-payment-gateway.php:541 +#: includes/core/class-wc-subscriptions-change-payment-gateway.php:540 #, php-format msgctxt "%1$s: old payment title, %2$s: new payment title" msgid "Payment method changed from \"%1$s\" to \"%2$s\" by the subscriber." msgstr "" -#: includes/core/class-wc-subscriptions-change-payment-gateway.php:552 +#: includes/core/class-wc-subscriptions-change-payment-gateway.php:551 msgid "An error occurred updating your subscription's payment method. Please contact us for assistance." msgstr "" -#: includes/core/class-wc-subscriptions-change-payment-gateway.php:560 +#: includes/core/class-wc-subscriptions-change-payment-gateway.php:559 #, php-format msgid "%1$sError:%2$s %3$s" msgstr "" -#: includes/core/class-wc-subscriptions-change-payment-gateway.php:785 +#: includes/core/class-wc-subscriptions-change-payment-gateway.php:784 msgctxt "the page title of the change payment method form" msgid "Change payment method" msgstr "" -#: includes/core/class-wc-subscriptions-change-payment-gateway.php:787 +#: includes/core/class-wc-subscriptions-change-payment-gateway.php:786 msgctxt "the page title of the add payment method form" msgid "Add payment method" msgstr "" -#: includes/core/class-wc-subscriptions-change-payment-gateway.php:829 +#: includes/core/class-wc-subscriptions-change-payment-gateway.php:827 msgid "Please log in to your account below to choose a new payment method for your subscription." msgstr "" #. translators: placeholder is an internal error number -#: includes/core/class-wc-subscriptions-checkout.php:204 -#: includes/core/class-wc-subscriptions-checkout.php:392 +#: includes/core/class-wc-subscriptions-checkout.php:205 +#: includes/core/class-wc-subscriptions-checkout.php:393 #, php-format msgid "Error %d: Unable to create subscription. Please try again." msgstr "" #. translators: placeholder is an internal error number -#: includes/core/class-wc-subscriptions-checkout.php:221 +#: includes/core/class-wc-subscriptions-checkout.php:222 #, php-format msgid "Error %d: Unable to add tax to subscription. Please try again." msgstr "" #. translators: placeholder is an internal error number -#: includes/core/class-wc-subscriptions-checkout.php:233 +#: includes/core/class-wc-subscriptions-checkout.php:234 #, php-format msgid "Error %d: Unable to create order. Please try again." msgstr "" #. Translators: Placeholders are opening and closing strong and link tags. -#: includes/core/class-wc-subscriptions-checkout.php:521 +#: includes/core/class-wc-subscriptions-checkout.php:522 #, php-format msgid "Purchasing a subscription product requires an account. Please go to the %1$sMy Account%2$s page to login or register." msgstr "" #. Translators: Placeholders are opening and closing strong and link tags. -#: includes/core/class-wc-subscriptions-checkout.php:524 +#: includes/core/class-wc-subscriptions-checkout.php:525 #, php-format 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 "" -#: includes/core/class-wc-subscriptions-core-plugin.php:395 +#: includes/core/class-wc-subscriptions-core-plugin.php:398 msgctxt "custom post type setting" msgid "Add Subscription" msgstr "" -#: includes/core/class-wc-subscriptions-core-plugin.php:396 +#: includes/core/class-wc-subscriptions-core-plugin.php:399 msgctxt "custom post type setting" msgid "Add New Subscription" msgstr "" -#: includes/core/class-wc-subscriptions-core-plugin.php:397 +#: includes/core/class-wc-subscriptions-core-plugin.php:400 msgctxt "custom post type setting" msgid "Edit" msgstr "" -#: includes/core/class-wc-subscriptions-core-plugin.php:398 +#: includes/core/class-wc-subscriptions-core-plugin.php:401 msgctxt "custom post type setting" msgid "Edit Subscription" msgstr "" -#: includes/core/class-wc-subscriptions-core-plugin.php:399 +#: includes/core/class-wc-subscriptions-core-plugin.php:402 msgctxt "custom post type setting" msgid "New Subscription" msgstr "" -#: includes/core/class-wc-subscriptions-core-plugin.php:400 -#: includes/core/class-wc-subscriptions-core-plugin.php:401 +#: includes/core/class-wc-subscriptions-core-plugin.php:403 +#: includes/core/class-wc-subscriptions-core-plugin.php:404 msgctxt "custom post type setting" msgid "View Subscription" msgstr "" -#: includes/core/class-wc-subscriptions-core-plugin.php:404 +#: includes/core/class-wc-subscriptions-core-plugin.php:407 msgctxt "custom post type setting" msgid "No Subscriptions found in trash" msgstr "" -#: includes/core/class-wc-subscriptions-core-plugin.php:405 +#: includes/core/class-wc-subscriptions-core-plugin.php:408 msgctxt "custom post type setting" msgid "Parent Subscriptions" msgstr "" -#: includes/core/class-wc-subscriptions-core-plugin.php:408 +#: includes/core/class-wc-subscriptions-core-plugin.php:411 msgid "This is where subscriptions are stored." msgstr "" #. translators: placeholder is a post count. -#: includes/core/class-wc-subscriptions-core-plugin.php:466 +#: includes/core/class-wc-subscriptions-core-plugin.php:469 #, php-format msgctxt "post status label including post count" msgid "Active (%s)" @@ -3052,7 +3069,7 @@ msgstr[0] "" msgstr[1] "" #. translators: placeholder is a post count. -#: includes/core/class-wc-subscriptions-core-plugin.php:468 +#: includes/core/class-wc-subscriptions-core-plugin.php:471 #, php-format msgctxt "post status label including post count" msgid "Switched (%s)" @@ -3061,7 +3078,7 @@ msgstr[0] "" msgstr[1] "" #. translators: placeholder is a post count. -#: includes/core/class-wc-subscriptions-core-plugin.php:470 +#: includes/core/class-wc-subscriptions-core-plugin.php:473 #, php-format msgctxt "post status label including post count" msgid "Expired (%s)" @@ -3070,7 +3087,7 @@ msgstr[0] "" msgstr[1] "" #. translators: placeholder is a post count. -#: includes/core/class-wc-subscriptions-core-plugin.php:472 +#: includes/core/class-wc-subscriptions-core-plugin.php:475 #, php-format msgctxt "post status label including post count" msgid "Pending Cancellation (%s)" @@ -3078,22 +3095,22 @@ msgid_plural "Pending Cancellation (%s)" msgstr[0] "" msgstr[1] "" -#: includes/core/class-wc-subscriptions-core-plugin.php:521 +#: includes/core/class-wc-subscriptions-core-plugin.php:524 msgid "Variable Subscription" msgstr "" -#: includes/core/class-wc-subscriptions-core-plugin.php:575 +#: includes/core/class-wc-subscriptions-core-plugin.php:578 #: includes/core/upgrades/templates/wcs-about-2-0.php:36 msgctxt "short for documents" msgid "Docs" msgstr "" -#: includes/core/class-wc-subscriptions-core-plugin.php:576 +#: includes/core/class-wc-subscriptions-core-plugin.php:579 msgid "Support" msgstr "" #. translators: placeholders are opening and closing tags. Leads to docs on upgrading WooCommerce Subscriptions -#: includes/core/class-wc-subscriptions-core-plugin.php:599 +#: includes/core/class-wc-subscriptions-core-plugin.php:602 #, php-format 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 "" @@ -3114,64 +3131,65 @@ msgstr "" msgid "Recurring Product % Discount" msgstr "" -#: includes/core/class-wc-subscriptions-coupon.php:446 +#: includes/core/class-wc-subscriptions-coupon.php:448 msgid "Sorry, this coupon is only valid for an initial payment and the cart does not require an initial payment." msgstr "" -#: includes/core/class-wc-subscriptions-coupon.php:452 +#: includes/core/class-wc-subscriptions-coupon.php:454 msgid "Sorry, this coupon is only valid for new subscriptions." msgstr "" -#: includes/core/class-wc-subscriptions-coupon.php:457 +#: includes/core/class-wc-subscriptions-coupon.php:459 msgid "Sorry, this coupon is only valid for subscription products." msgstr "" #. translators: 1$: coupon code that is being removed -#: includes/core/class-wc-subscriptions-coupon.php:463 +#: includes/core/class-wc-subscriptions-coupon.php:465 #, php-format msgid "Sorry, the \"%1$s\" coupon is only valid for renewals." msgstr "" -#: includes/core/class-wc-subscriptions-coupon.php:468 +#: includes/core/class-wc-subscriptions-coupon.php:470 msgid "Sorry, this coupon is only valid for subscription products with a sign-up fee." msgstr "" -#: includes/core/class-wc-subscriptions-coupon.php:494 +#: includes/core/class-wc-subscriptions-coupon.php:496 msgid "Sorry, recurring coupons can only be applied to subscriptions or subscription orders." msgstr "" #. translators: placeholder is coupon code -#: includes/core/class-wc-subscriptions-coupon.php:498 +#: includes/core/class-wc-subscriptions-coupon.php:500 #, php-format msgid "Sorry, \"%s\" can only be applied to subscription parent orders which contain a product with signup fees." msgstr "" -#: includes/core/class-wc-subscriptions-coupon.php:501 +#: includes/core/class-wc-subscriptions-coupon.php:503 msgid "Sorry, only recurring coupons can be applied to subscriptions." msgstr "" -#: includes/core/class-wc-subscriptions-coupon.php:674 +#: includes/core/class-wc-subscriptions-coupon.php:677 msgid "Renewal % discount" msgstr "" -#: includes/core/class-wc-subscriptions-coupon.php:675 +#: includes/core/class-wc-subscriptions-coupon.php:678 msgid "Renewal product discount" msgstr "" -#: includes/core/class-wc-subscriptions-coupon.php:676 +#: includes/core/class-wc-subscriptions-coupon.php:679 msgid "Renewal cart discount" msgstr "" -#: includes/core/class-wc-subscriptions-coupon.php:677 +#: includes/core/class-wc-subscriptions-coupon.php:680 msgid "Initial payment discount" msgstr "" -#: includes/core/class-wc-subscriptions-coupon.php:694 +#: includes/core/class-wc-subscriptions-coupon.php:697 msgid "Renewal Discount" msgstr "" -#: includes/core/class-wc-subscriptions-coupon.php:697 +#: includes/core/class-wc-subscriptions-coupon.php:700 #: assets/src/js/recurring-totals/index.js:60 +#: build/index.js:10 msgid "Discount" msgstr "" @@ -3219,48 +3237,48 @@ msgstr "" msgid "N/A" msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:185 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:192 msgid "Subscription Product length." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:191 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:198 msgid "Subscription Product trial period." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:198 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:205 msgid "Subscription Product trial interval." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:204 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:211 msgid "Subscription Product signup fees." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:210 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:217 msgid "Subscription Product signup fees taxes." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:216 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:223 msgid "Indicates whether this product is being used to resubscribe the customer to an existing, expired subscription." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:225 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:232 msgid "Indicates whether this product a subscription update, downgrade, cross grade or none of the above." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:234 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:241 msgid "Synchronization data for the subscription." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:238 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:245 msgid "Synchronization day if subscription is annual." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:242 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:249 msgid "Synchronization month if subscription is annual." msgstr "" #. translators: %d subscription interval. -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:297 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:304 #, php-format msgid "Shipment every %d year" msgid_plural "Shipment every %d years" @@ -3268,12 +3286,12 @@ msgstr[0] "" msgstr[1] "" #. translators: %d subscription interval. -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:297 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:304 msgid "Yearly Shipment" msgstr "" #. translators: %d subscription interval. -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:301 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:308 #, php-format msgid "Shipment every %d month" msgid_plural "Shipment every %d months" @@ -3281,12 +3299,12 @@ msgstr[0] "" msgstr[1] "" #. translators: %d subscription interval. -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:301 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:308 msgid "Monthly Shipment" msgstr "" #. translators: %d subscription interval. -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:305 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:312 #, php-format msgid "Shipment every %d week" msgid_plural "Shipment every %d weeks" @@ -3294,12 +3312,12 @@ msgstr[0] "" msgstr[1] "" #. translators: %d subscription interval. -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:305 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:312 msgid "Weekly Shipment" msgstr "" #. translators: %d subscription interval. -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:309 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:316 #, php-format msgid "Shipment every %d day" msgid_plural "Shipment every %d days" @@ -3307,96 +3325,96 @@ msgstr[0] "" msgstr[1] "" #. translators: %d subscription interval. -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:309 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:316 msgid "Daily Shipment" msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:434 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:441 msgid "Subscription key" msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:459 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:466 msgid "Subscription length." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:465 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:472 msgid "Cart total amounts provided using the smallest unit of the currency." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:471 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:478 msgid "Total price of items in the cart." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:477 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:484 msgid "Total tax on items in the cart." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:483 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:490 msgid "Total price of any applied fees." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:489 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:496 msgid "Total tax on fees." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:495 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:502 msgid "Total discount from applied coupons." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:501 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:508 msgid "Total tax removed due to discount from applied coupons." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:507 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:514 msgid "Total price of shipping. If shipping has not been calculated, a null response will be sent." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:513 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:520 msgid "Total tax on shipping. If shipping has not been calculated, a null response will be sent." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:519 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:526 msgid "Total price the customer will pay." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:525 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:532 msgid "Total tax applied to items and shipping." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:531 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:538 msgid "Lines of taxes applied to items and shipping." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:539 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:546 msgid "The name of the tax." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:545 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:552 msgid "The amount of tax charged." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:554 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:561 msgid "Currency code (in ISO format) for returned prices." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:560 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:567 msgid "Currency symbol for the currency which can be used to format returned prices." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:566 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:573 msgid "Currency minor unit (number of digits after the decimal separator) for returned prices." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:572 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:579 msgid "Decimal separator for the currency which can be used to format returned prices." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:578 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:585 msgid "Thousand separator for the currency which can be used to format returned prices." msgstr "" -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:584 -#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:590 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:591 +#: includes/core/class-wc-subscriptions-extend-store-endpoint.php:597 msgid "Price prefix for the currency which can be used to format returned prices." msgstr "" @@ -3409,8 +3427,8 @@ msgid "Would you like to add a payment method now?" msgstr "" #: includes/core/class-wc-subscriptions-manager.php:114 -#: includes/core/class-wc-subscriptions-manager.php:2019 -#: includes/core/class-wc-subscriptions-manager.php:2034 +#: includes/core/class-wc-subscriptions-manager.php:2031 +#: includes/core/class-wc-subscriptions-manager.php:2046 msgctxt "used in order note as reason for why subscription status changed" msgid "Subscription renewal payment due:" msgstr "" @@ -3434,213 +3452,213 @@ msgid "Subscription doesn't exist in scheduled action: %d" msgstr "" #. translators: $1: order number, $2: error message -#: includes/core/class-wc-subscriptions-manager.php:344 +#: includes/core/class-wc-subscriptions-manager.php:346 #, php-format msgid "Failed to activate subscription status for order #%1$s: %2$s" msgstr "" #. translators: $1: order number, $2: error message -#: includes/core/class-wc-subscriptions-manager.php:372 +#: includes/core/class-wc-subscriptions-manager.php:374 #, php-format msgid "Failed to update subscription status after order #%1$s was put on-hold: %2$s" msgstr "" #. translators: $1: order number, $2: error message -#: includes/core/class-wc-subscriptions-manager.php:400 +#: includes/core/class-wc-subscriptions-manager.php:402 #, php-format msgid "Failed to cancel subscription after order #%1$s was cancelled: %2$s" msgstr "" #. translators: $1: order number, $2: error message -#: includes/core/class-wc-subscriptions-manager.php:428 +#: includes/core/class-wc-subscriptions-manager.php:430 #, php-format msgid "Failed to set subscription as expired for order #%1$s: %2$s" msgstr "" -#: includes/core/class-wc-subscriptions-manager.php:454 +#: includes/core/class-wc-subscriptions-manager.php:458 msgid "Subscription sign up failed." msgstr "" #. translators: $1: order number, $2: error message -#: includes/core/class-wc-subscriptions-manager.php:464 +#: includes/core/class-wc-subscriptions-manager.php:469 #, php-format msgid "Failed to process failed payment on subscription for order #%1$s: %2$s" msgstr "" -#: includes/core/class-wc-subscriptions-manager.php:538 +#: includes/core/class-wc-subscriptions-manager.php:549 msgid "Error: Unable to create subscription. Please try again." msgstr "" -#: includes/core/class-wc-subscriptions-manager.php:560 +#: includes/core/class-wc-subscriptions-manager.php:571 msgid "Error: Unable to add product to created subscription. Please try again." msgstr "" -#: includes/core/class-wc-subscriptions-manager.php:605 +#: includes/core/class-wc-subscriptions-manager.php:616 msgid "Pending subscription created." msgstr "" #. Translators: 1: The subscription ID number. 2: The current user's username. -#: includes/core/class-wc-subscriptions-manager.php:1015 +#: includes/core/class-wc-subscriptions-manager.php:1027 #, php-format msgid "The related subscription #%1$s has been deleted after the customer was deleted by %2$s." msgstr "" #. Translators: Placeholder is the subscription ID number. -#: includes/core/class-wc-subscriptions-manager.php:1018 +#: includes/core/class-wc-subscriptions-manager.php:1030 #, php-format msgid "The related subscription #%s has been deleted after the customer was deleted." msgstr "" -#: includes/core/class-wc-subscriptions-manager.php:1164 +#: includes/core/class-wc-subscriptions-manager.php:1176 #: includes/core/wcs-functions.php:220 msgctxt "Subscription status" msgid "Active" msgstr "" -#: includes/core/class-wc-subscriptions-manager.php:1167 +#: includes/core/class-wc-subscriptions-manager.php:1179 #: includes/core/wcs-functions.php:222 msgctxt "Subscription status" msgid "Cancelled" msgstr "" -#: includes/core/class-wc-subscriptions-manager.php:1170 +#: includes/core/class-wc-subscriptions-manager.php:1182 #: includes/core/wcs-functions.php:224 msgctxt "Subscription status" msgid "Expired" msgstr "" -#: includes/core/class-wc-subscriptions-manager.php:1173 +#: includes/core/class-wc-subscriptions-manager.php:1185 #: includes/core/wcs-functions.php:219 msgctxt "Subscription status" msgid "Pending" msgstr "" -#: includes/core/class-wc-subscriptions-manager.php:1176 +#: includes/core/class-wc-subscriptions-manager.php:1188 msgctxt "Subscription status" msgid "Failed" msgstr "" -#: includes/core/class-wc-subscriptions-manager.php:1180 +#: includes/core/class-wc-subscriptions-manager.php:1192 msgctxt "Subscription status" msgid "On-hold" msgstr "" #. translators: 1$: month number (e.g. "01"), 2$: month abbreviation (e.g. "Jan") -#: includes/core/class-wc-subscriptions-manager.php:1932 +#: includes/core/class-wc-subscriptions-manager.php:1944 #, php-format msgctxt "used in a select box" msgid "%1$s-%2$s" msgstr "" #. translators: all fields are full html nodes: 1$: month input, 2$: day input, 3$: year input, 4$: hour input, 5$: minute input. Change the order if you'd like -#: includes/core/class-wc-subscriptions-manager.php:1945 +#: includes/core/class-wc-subscriptions-manager.php:1957 #, php-format msgid "%1$s%2$s, %3$s @ %4$s : %5$s" msgstr "" #. translators: all fields are full html nodes: 1$: month input, 2$: day input, 3$: year input. Change the order if you'd like -#: includes/core/class-wc-subscriptions-manager.php:1949 +#: includes/core/class-wc-subscriptions-manager.php:1961 #, php-format msgid "%1$s%2$s, %3$s" msgstr "" -#: includes/core/class-wc-subscriptions-manager.php:1954 +#: includes/core/class-wc-subscriptions-manager.php:1966 msgid "Change" msgstr "" #. translators: placeholder is subscription ID -#: includes/core/class-wc-subscriptions-manager.php:2284 +#: includes/core/class-wc-subscriptions-manager.php:2296 #, php-format msgid "Failed sign-up for subscription %s." msgstr "" -#: includes/core/class-wc-subscriptions-manager.php:2375 +#: includes/core/class-wc-subscriptions-manager.php:2387 msgid "Invalid security token, please reload the page and try again." msgstr "" -#: includes/core/class-wc-subscriptions-manager.php:2379 +#: includes/core/class-wc-subscriptions-manager.php:2391 msgid "Only store managers can edit payment dates." msgstr "" -#: includes/core/class-wc-subscriptions-manager.php:2383 +#: includes/core/class-wc-subscriptions-manager.php:2395 msgid "Please enter all date fields." msgstr "" -#: includes/core/class-wc-subscriptions-manager.php:2408 +#: includes/core/class-wc-subscriptions-manager.php:2420 msgid "Date Changed" msgstr "" -#: includes/core/class-wc-subscriptions-order.php:364 +#: includes/core/class-wc-subscriptions-order.php:360 msgid "Your subscription will be activated when payment clears." msgid_plural "Your subscriptions will be activated when payment clears." msgstr[0] "" msgstr[1] "" #. translators: placeholders are opening and closing link tags -#: includes/core/class-wc-subscriptions-order.php:371 +#: includes/core/class-wc-subscriptions-order.php:367 #, php-format msgid "View the status of your subscription in %1$syour account%2$s." msgid_plural "View the status of your subscriptions in %1$syour account%2$s." msgstr[0] "" msgstr[1] "" -#: includes/core/class-wc-subscriptions-order.php:434 +#: includes/core/class-wc-subscriptions-order.php:430 msgid "Subscription Relationship" msgstr "" -#: includes/core/class-wc-subscriptions-order.php:534 +#: includes/core/class-wc-subscriptions-order.php:530 msgid "Payment completed on order after subscription was cancelled." msgstr "" #. translators: $1: opening link tag, $2: order number, $3: closing link tag -#: includes/core/class-wc-subscriptions-order.php:1171 +#: includes/core/class-wc-subscriptions-order.php:1169 #, php-format msgid "Subscription cancelled for refunded order %1$s#%2$s%3$s." msgstr "" -#: includes/core/class-wc-subscriptions-order.php:2559 +#: includes/core/class-wc-subscriptions-order.php:2568 msgctxt "An order type" msgid "Original" msgstr "" -#: includes/core/class-wc-subscriptions-order.php:2560 +#: includes/core/class-wc-subscriptions-order.php:2569 msgctxt "An order type" msgid "Subscription Parent" msgstr "" -#: includes/core/class-wc-subscriptions-order.php:2561 +#: includes/core/class-wc-subscriptions-order.php:2570 msgctxt "An order type" msgid "Subscription Renewal" msgstr "" -#: includes/core/class-wc-subscriptions-order.php:2562 +#: includes/core/class-wc-subscriptions-order.php:2571 msgctxt "An order type" msgid "Subscription Resubscribe" msgstr "" -#: includes/core/class-wc-subscriptions-order.php:2563 +#: includes/core/class-wc-subscriptions-order.php:2572 msgctxt "An order type" msgid "Subscription Switch" msgstr "" -#: includes/core/class-wc-subscriptions-order.php:2564 +#: includes/core/class-wc-subscriptions-order.php:2573 msgctxt "An order type" msgid "Non-subscription" msgstr "" -#: includes/core/class-wc-subscriptions-order.php:2573 +#: includes/core/class-wc-subscriptions-order.php:2582 msgid "All orders types" msgstr "" -#: includes/core/class-wc-subscriptions-order.php:2606 +#: includes/core/class-wc-subscriptions-order.php:2615 msgid "Renewal Order" msgstr "" -#: includes/core/class-wc-subscriptions-order.php:2608 +#: includes/core/class-wc-subscriptions-order.php:2617 msgid "Resubscribe Order" msgstr "" -#: includes/core/class-wc-subscriptions-order.php:2610 +#: includes/core/class-wc-subscriptions-order.php:2619 msgid "Parent Order" msgstr "" @@ -3843,39 +3861,39 @@ msgstr "" msgid "Month for Synchronisation" msgstr "" -#: includes/core/class-wc-subscriptions-synchroniser.php:754 -#: includes/core/class-wc-subscriptions-synchroniser.php:771 +#: includes/core/class-wc-subscriptions-synchroniser.php:760 +#: includes/core/class-wc-subscriptions-synchroniser.php:777 msgid "Do not synchronise" msgstr "" #. translators: placeholder is a day of the week -#: includes/core/class-wc-subscriptions-synchroniser.php:779 +#: includes/core/class-wc-subscriptions-synchroniser.php:785 #, php-format msgid "%s each week" msgstr "" #. translators: placeholder is a number of day with language specific suffix applied (e.g. "1st", "3rd", "5th", etc...) -#: includes/core/class-wc-subscriptions-synchroniser.php:785 +#: includes/core/class-wc-subscriptions-synchroniser.php:791 #, php-format msgid "%s day of the month" msgstr "" -#: includes/core/class-wc-subscriptions-synchroniser.php:787 +#: includes/core/class-wc-subscriptions-synchroniser.php:793 msgid "Last day of the month" msgstr "" -#: includes/core/class-wc-subscriptions-synchroniser.php:835 +#: includes/core/class-wc-subscriptions-synchroniser.php:841 msgid "Today!" msgstr "" #. translators: placeholder is a date -#: includes/core/class-wc-subscriptions-synchroniser.php:842 +#: includes/core/class-wc-subscriptions-synchroniser.php:848 #, php-format msgid "First payment prorated. Next payment: %s" msgstr "" #. translators: placeholder is a date -#: includes/core/class-wc-subscriptions-synchroniser.php:845 +#: includes/core/class-wc-subscriptions-synchroniser.php:851 #, php-format msgid "First payment: %s" msgstr "" @@ -3961,46 +3979,46 @@ msgctxt "The place order button text while renewing a subscription" msgid "Renew subscription" msgstr "" -#: includes/core/class-wcs-cart-resubscribe.php:70 +#: includes/core/class-wcs-cart-resubscribe.php:77 msgid "There was an error with your request to resubscribe. Please try again." msgstr "" -#: includes/core/class-wcs-cart-resubscribe.php:74 +#: includes/core/class-wcs-cart-resubscribe.php:79 #: includes/early-renewal/class-wcs-cart-early-renewal.php:94 msgid "That subscription does not exist. Has it been deleted?" msgstr "" -#: includes/core/class-wcs-cart-resubscribe.php:82 +#: includes/core/class-wcs-cart-resubscribe.php:87 msgid "You can not resubscribe to that subscription. Please contact us if you need assistance." msgstr "" -#: includes/core/class-wcs-cart-resubscribe.php:91 -#: includes/core/class-wcs-cart-resubscribe.php:119 +#: includes/core/class-wcs-cart-resubscribe.php:95 +#: includes/core/class-wcs-cart-resubscribe.php:123 msgid "Complete checkout to resubscribe." msgstr "" #. translators: %s: order number. -#: includes/core/class-wcs-cart-resubscribe.php:320 +#: includes/core/class-wcs-cart-resubscribe.php:324 #, php-format msgid "Customer resubscribed in order #%s" msgstr "" -#: includes/core/class-wcs-cart-resubscribe.php:338 +#: includes/core/class-wcs-cart-resubscribe.php:342 msgctxt "The place order button text while resubscribing to a subscription" msgid "Resubscribe" msgstr "" -#: includes/core/class-wcs-change-payment-method-admin.php:116 +#: includes/core/class-wcs-change-payment-method-admin.php:119 msgid "Please choose a valid payment gateway to change to." msgstr "" #. Translators: Placeholder is the payment gateway title. -#: includes/core/class-wcs-change-payment-method-admin.php:149 +#: includes/core/class-wcs-change-payment-method-admin.php:152 #, php-format msgid "Admin turned on automatic renewals by changing payment method to \"%s\" via the Edit Subscription screen." msgstr "" -#: includes/core/class-wcs-failed-scheduled-action-manager.php:227 +#: includes/core/class-wcs-failed-scheduled-action-manager.php:228 msgid "Ignore this error" msgstr "" @@ -4046,44 +4064,44 @@ msgstr "" msgid "Allow customers to turn on and off automatic renewals from their View Subscription page." msgstr "" -#: includes/core/class-wcs-my-account-payment-methods.php:112 +#: includes/core/class-wcs-my-account-payment-methods.php:113 msgid "The deleted payment method was used for automatic subscription payments, we couldn't find an alternative token payment method token to change your subscriptions to." msgstr "" #. translators: 1: deleted token, 2: new token. -#: includes/core/class-wcs-my-account-payment-methods.php:130 +#: includes/core/class-wcs-my-account-payment-methods.php:131 #, php-format msgctxt "used in subscription note" msgid "Payment method meta updated after customer deleted a token from their My Account page. Payment meta changed from %1$s to %2$s" msgstr "" #. translators: $1: the token/credit card label, 2$-3$: opening and closing strong and link tags -#: includes/core/class-wcs-my-account-payment-methods.php:135 +#: includes/core/class-wcs-my-account-payment-methods.php:136 #, php-format msgid "The deleted payment method was used for automatic subscription payments. To avoid failed renewal payments in future the subscriptions using this payment method have been updated to use your %1$s. To change the payment method of individual subscriptions go to your %2$sMy Account > Subscriptions%3$s page." msgstr "" #. translators: 1: token display name, 2: opening link tag, 4: closing link tag, 3: opening link tag. -#: includes/core/class-wcs-my-account-payment-methods.php:190 +#: includes/core/class-wcs-my-account-payment-methods.php:191 #, php-format msgid "Would you like to update your subscriptions to use this new payment method - %1$s?%2$sYes%4$s | %3$sNo%4$s" msgstr "" #. translators: 1: previous token, 2: new token. -#: includes/core/class-wcs-my-account-payment-methods.php:231 +#: includes/core/class-wcs-my-account-payment-methods.php:232 #, php-format msgctxt "used in subscription note" msgid "Payment method meta updated after customer changed their default token and opted to update their subscriptions. Payment meta changed from %1$s to %2$s" msgstr "" #. translators: %1$s opening strong HTML tag, %2$s closing strong HTML tag. -#: includes/core/class-wcs-my-account-payment-methods.php:253 +#: includes/core/class-wcs-my-account-payment-methods.php:254 #, php-format msgid "That payment method cannot be deleted because it is linked to an automatic subscription. Please %1$sadd a payment method%2$s, before trying again." msgstr "" #. translators: %1$s opening strong and em HTML tags, %2$s closing em HTML tag, %3$s closing strong HTML tag. -#: includes/core/class-wcs-my-account-payment-methods.php:259 +#: includes/core/class-wcs-my-account-payment-methods.php:260 #, php-format 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 "" @@ -4170,7 +4188,7 @@ msgid "You have successfully removed \"%1$s\" from your subscription. %2$sUndo?% msgstr "" #: includes/core/class-wcs-remove-item.php:180 -#: includes/core/class-wcs-user-change-status-handler.php:107 +#: includes/core/class-wcs-user-change-status-handler.php:108 msgid "Security error. Please contact us if you need assistance." msgstr "" @@ -4220,50 +4238,50 @@ msgstr "" msgid "My Account" msgstr "" -#: includes/core/class-wcs-user-change-status-handler.php:57 +#: includes/core/class-wcs-user-change-status-handler.php:58 msgctxt "order note left on subscription after user action" msgid "Subscription reactivated by the subscriber from their account page." msgstr "" -#: includes/core/class-wcs-user-change-status-handler.php:58 +#: includes/core/class-wcs-user-change-status-handler.php:59 msgctxt "Notice displayed to user confirming their action." msgid "Your subscription has been reactivated." msgstr "" -#: includes/core/class-wcs-user-change-status-handler.php:61 +#: includes/core/class-wcs-user-change-status-handler.php:62 msgid "You can not reactivate that subscription until paying to renew it. Please contact us if you need assistance." msgstr "" -#: includes/core/class-wcs-user-change-status-handler.php:67 +#: includes/core/class-wcs-user-change-status-handler.php:68 msgctxt "order note left on subscription after user action" msgid "Subscription put on hold by the subscriber from their account page." msgstr "" -#: includes/core/class-wcs-user-change-status-handler.php:68 +#: includes/core/class-wcs-user-change-status-handler.php:69 msgctxt "Notice displayed to user confirming their action." msgid "Your subscription has been put on hold." msgstr "" -#: includes/core/class-wcs-user-change-status-handler.php:71 +#: includes/core/class-wcs-user-change-status-handler.php:72 msgid "You can not suspend that subscription - the suspension limit has been reached. Please contact us if you need assistance." msgstr "" -#: includes/core/class-wcs-user-change-status-handler.php:76 +#: includes/core/class-wcs-user-change-status-handler.php:77 msgctxt "order note left on subscription after user action" msgid "Subscription cancelled by the subscriber from their account page." msgstr "" -#: includes/core/class-wcs-user-change-status-handler.php:77 +#: includes/core/class-wcs-user-change-status-handler.php:78 msgctxt "Notice displayed to user confirming their action." msgid "Your subscription has been cancelled." msgstr "" -#: includes/core/class-wcs-user-change-status-handler.php:103 +#: includes/core/class-wcs-user-change-status-handler.php:104 msgid "That subscription does not exist. Please contact us if you need assistance." msgstr "" #. translators: placeholder is subscription's new status, translated -#: includes/core/class-wcs-user-change-status-handler.php:116 +#: includes/core/class-wcs-user-change-status-handler.php:117 #, php-format msgid "That subscription can not be changed to %s. Please contact us if you need assistance." msgstr "" @@ -4325,15 +4343,15 @@ msgid "This will clear the persistent cache of all renewal, switch, resubscribe msgstr "" #. translators: %s: Order date -#: includes/core/data-stores/class-wcs-subscription-data-store-cpt.php:273 +#: includes/core/data-stores/class-wcs-subscription-data-store-cpt.php:275 #, php-format msgid "Subscription – %s" msgstr "" #. translators: %s: Order date #. translators: placeholders are strftime() strings. -#: includes/core/data-stores/class-wcs-subscription-data-store-cpt.php:273 -#: includes/core/wcs-order-functions.php:313 +#: includes/core/data-stores/class-wcs-subscription-data-store-cpt.php:275 +#: includes/core/wcs-order-functions.php:314 msgctxt "Order date parsed by DateTime::format" msgid "M d, Y @ h:i A" msgstr "" @@ -4831,24 +4849,24 @@ msgid "Your {site_title} renewal order receipt from {order_date}" msgstr "" #. translators: 1-2: opening/closing tags - link to documentation. -#: includes/core/gateways/class-wc-subscriptions-core-payment-gateways.php:178 +#: includes/core/gateways/class-wc-subscriptions-core-payment-gateways.php:176 #, php-format msgid "Sorry, it seems there are no available payment methods which support subscriptions. Please see %1$sEnabling Payment Gateways for Subscriptions%2$s if you require assistance." msgstr "" -#: includes/core/gateways/class-wc-subscriptions-core-payment-gateways.php:180 +#: includes/core/gateways/class-wc-subscriptions-core-payment-gateways.php:178 msgid "Sorry, it seems there are no available payment methods which support subscriptions. Please contact us if you require assistance or wish to make alternate arrangements." msgstr "" -#: includes/core/gateways/class-wc-subscriptions-core-payment-gateways.php:271 +#: includes/core/gateways/class-wc-subscriptions-core-payment-gateways.php:269 msgid "Supported features:" msgstr "" -#: includes/core/gateways/class-wc-subscriptions-core-payment-gateways.php:274 +#: includes/core/gateways/class-wc-subscriptions-core-payment-gateways.php:272 msgid "Subscription features:" msgstr "" -#: includes/core/gateways/class-wc-subscriptions-core-payment-gateways.php:278 +#: includes/core/gateways/class-wc-subscriptions-core-payment-gateways.php:276 msgid "Change payment features:" msgstr "" @@ -5273,64 +5291,64 @@ msgstr "" msgid "Customers with a subscription are excluded from this setting." msgstr "" -#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:193 +#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:297 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. -#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:255 +#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:359 #, php-format 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") -#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:394 +#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:498 #, php-format msgid "Database updated to version %s" msgstr "" #. translators: placeholder is number of upgraded subscriptions -#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:402 +#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:506 #, php-format 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 -#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:411 +#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:515 #, php-format 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 -#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:423 +#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:527 #, php-format 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 -#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:426 -#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:472 +#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:530 +#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:576 #, php-format 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 -#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:436 +#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:540 #, php-format 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 -#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:451 +#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:555 #, php-format 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! -#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:457 +#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:561 #, php-format msgctxt "Repair message that gets sent to front end." msgid " %d other subscription was checked and did not need any repairs." @@ -5339,36 +5357,36 @@ msgstr[0] "" msgstr[1] "" #. translators: placeholder is "{execution_time}", which will be replaced on front end with actual time -#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:461 +#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:565 #, php-format 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. -#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:464 +#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:568 #, php-format 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 -#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:483 +#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:587 #, php-format 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 "" -#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:690 +#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:795 msgid "Welcome to WooCommerce Subscriptions 2.1" msgstr "" -#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:690 +#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:795 msgid "About WooCommerce Subscriptions" msgstr "" #. translators: 1-2: opening/closing tags, 3-4: opening/closing tags linked to ticket form. -#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:924 +#: includes/core/upgrades/class-wc-subscriptions-upgrader.php:1033 #, php-format 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 "" @@ -5933,40 +5951,40 @@ msgid "There was an error with the update. Please refresh the page and try again msgstr "" #. translators: %s: shipping method label. -#: includes/core/wcs-cart-functions.php:98 -#: includes/core/wcs-cart-functions.php:104 +#: includes/core/wcs-cart-functions.php:94 +#: includes/core/wcs-cart-functions.php:100 #, php-format msgid "Shipping via %s" msgstr "" -#: includes/core/wcs-cart-functions.php:264 +#: includes/core/wcs-cart-functions.php:259 msgctxt "shipping method price" msgid "Free" msgstr "" -#: includes/core/wcs-cart-functions.php:289 +#: includes/core/wcs-cart-functions.php:284 msgid "[Remove]" msgstr "" -#: includes/core/wcs-cart-functions.php:319 +#: includes/core/wcs-cart-functions.php:314 msgid "Free shipping coupon" msgstr "" #. translators: placeholder is price string, denotes tax included in cart/order total -#: includes/core/wcs-cart-functions.php:357 +#: includes/core/wcs-cart-functions.php:352 #, php-format msgctxt "includes tax" msgid "(includes %s)" msgstr "" #. translators: placeholder is a date -#: includes/core/wcs-cart-functions.php:432 +#: includes/core/wcs-cart-functions.php:427 #, php-format 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") -#: includes/core/wcs-deprecated-functions.php:179 +#: includes/core/wcs-deprecated-functions.php:180 #, php-format msgid "Could not get subscription. Most likely the subscription key does not refer to a subscription. The key was: \"%s\"." msgstr "" @@ -6206,38 +6224,38 @@ msgid "Invalid data. Type of copy is not a string." msgstr "" #. translators: placeholder %1 is the order type. %2 is the subscription ID we attempted to create the order for. -#: includes/core/wcs-order-functions.php:277 +#: includes/core/wcs-order-functions.php:278 #, php-format msgid "There was an error fetching the new order (%1$s) for subscription %2$d." msgstr "" #. translators: placeholder is a date. -#: includes/core/wcs-order-functions.php:318 +#: includes/core/wcs-order-functions.php:319 #, php-format msgid "Subscription Renewal Order – %s" msgstr "" #. translators: placeholder is a date. -#: includes/core/wcs-order-functions.php:322 +#: includes/core/wcs-order-functions.php:323 #, php-format msgid "Resubscribe Order – %s" msgstr "" -#: includes/core/wcs-order-functions.php:341 +#: includes/core/wcs-order-functions.php:342 msgid "$type passed to the function was not a string." msgstr "" #. translators: placeholder is an order type. -#: includes/core/wcs-order-functions.php:346 +#: includes/core/wcs-order-functions.php:347 #, php-format msgid "\"%s\" is not a valid new order type." msgstr "" -#: includes/core/wcs-order-functions.php:596 +#: includes/core/wcs-order-functions.php:597 msgid "Invalid data. No valid subscription / order was passed in." msgstr "" -#: includes/core/wcs-order-functions.php:600 +#: includes/core/wcs-order-functions.php:601 msgid "Invalid data. No valid item id was passed in." msgstr "" @@ -6898,7 +6916,7 @@ msgid "Switch subscription" msgstr "" #. translators: placeholder is a switch type. -#: includes/switching/class-wcs-switch-cart-item.php:315 +#: includes/switching/class-wcs-switch-cart-item.php:328 #, php-format msgid "Invalid switch type \"%s\". Switch must be one of: \"upgrade\", \"downgrade\" or \"crossgrade\"." msgstr "" @@ -6950,7 +6968,7 @@ msgstr "" #. translators: placeholder is trial period validation message if passed an invalid value (e.g. "Trial period can not exceed 4 weeks") #: templates/admin/deprecated/html-variation-price.php:118 -#: templates/admin/html-variation-price.php:72 +#: templates/admin/html-variation-price.php:83 #, php-format msgctxt "Trial period dropdown's description in pricing fields" msgid "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" @@ -6961,7 +6979,7 @@ msgid "Synchronise Renewals" msgstr "" #: templates/admin/deprecated/html-variation-synchronisation.php:36 -#: templates/admin/html-variation-synchronisation.php:42 +#: templates/admin/html-variation-synchronisation.php:48 msgctxt "input field placeholder for day field for annual subscriptions" msgid "Day" msgstr "" @@ -7014,25 +7032,25 @@ msgstr[1] "" msgid "To see further details about these errors, view the %1$s log file from the %2$sWooCommerce logs screen.%2$s" msgstr "" -#: templates/admin/html-variation-price.php:32 +#: templates/admin/html-variation-price.php:35 msgid "Billing interval:" msgstr "" -#: templates/admin/html-variation-price.php:39 +#: templates/admin/html-variation-price.php:42 msgid "Billing Period:" msgstr "" -#: templates/admin/html-variation-price.php:51 +#: templates/admin/html-variation-price.php:56 msgctxt "Subscription Length dropdown's description in pricing fields" 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 "" -#: templates/admin/html-variation-price.php:66 +#: templates/admin/html-variation-price.php:75 msgctxt "example price" msgid "e.g." msgstr "" -#: templates/admin/html-variation-price.php:78 +#: templates/admin/html-variation-price.php:90 msgid "Subscription trial period:" msgstr "" @@ -7164,7 +7182,7 @@ msgid "The automatic recurring payment for order #%1$d from %2$s has failed. The msgstr "" #: templates/emails/admin-payment-retry.php:24 -#: templates/emails/plain/admin-payment-retry.php:21 +#: templates/emails/plain/admin-payment-retry.php:29 msgid "The renewal order is as follows:" msgstr "" @@ -7415,8 +7433,7 @@ msgctxt "table headings in notification email" msgid "Date Suspended" msgstr "" -#. translators: %1$s: an order number, %2$s: the customer's full name, %3$s: lowercase human time diff in the form returned by wcs_get_human_time_diff(), e.g. 'in 12 hours' -#: templates/emails/plain/admin-payment-retry.php:20 +#: templates/emails/plain/admin-payment-retry.php:22 #, php-format msgctxt "In customer renewal invoice email" msgid "The automatic recurring payment for order #%1$s from %2$s has failed. The payment will be retried %3$s." @@ -7435,8 +7452,9 @@ msgstr "" msgid "End of Prepaid Term: %s" msgstr "" -#: templates/emails/plain/cancelled-subscription.php:44 -#: templates/emails/plain/expired-subscription.php:44 +#. translators: %s: subscription URL +#: templates/emails/plain/cancelled-subscription.php:45 +#: templates/emails/plain/expired-subscription.php:45 #: templates/emails/plain/on-hold-subscription.php:40 #, php-format msgctxt "in plain emails for subscription information" @@ -7844,75 +7862,89 @@ msgid "Clear" msgstr "" #: assets/src/js/filters/index.js:33 +#: build/index.js:18 msgid "Total due today" msgstr "" #. translators: the word used to describe billing frequency, e.g. "for" 1 day or "for" 1 month. #: assets/src/js/filters/index.js:60 +#: build/index.js:21 msgid "for 1" msgstr "" #. translators: the word used to describe billing frequency, e.g. "for" 6 days or "for" 2 weeks. #: assets/src/js/filters/index.js:67 +#: build/index.js:24 msgid "for" msgstr "" #. translators: the word used to describe billing frequency, e.g. "every" 6 days or "every" 2 weeks. #: assets/src/js/filters/index.js:74 +#: build/index.js:27 msgid "every" msgstr "" #. translators: %s Product name. #: assets/src/js/filters/index.js:93 +#: build/index.js:30 #, js-format msgid "%s (resubscription)" msgstr "" #. translators: %1$s Product name, %2$s Switch type (upgraded, downgraded, or crossgraded). #: assets/src/js/filters/index.js:100 +#: build/index.js:33 #, js-format msgid "%1$s (%2$s)" msgstr "" #. translators: %s is the subscription price to pay immediately (ie: $10). #: assets/src/js/filters/index.js:112 +#: build/index.js:34 #, js-format msgid "Due today %s" msgstr "" #. translators: %s is the subscription price to pay immediately (ie: $10). #: assets/src/js/filters/index.js:117 +#: build/index.js:35 #, js-format msgid "%s due today" msgstr "" #: assets/src/js/recurring-totals/index.js:97 +#: build/index.js:10 msgid "Free" msgstr "" #. translators: %s selected shipping rate (ex: flat rate) #: assets/src/js/recurring-totals/index.js:110 +#: build/index.js:13 #, js-format msgid "via %s" msgstr "" #. Translators: %1$s is a date. #: assets/src/js/recurring-totals/index.js:142 +#: build/index.js:14 #, js-format msgid "Due: %1$s" msgstr "" #. Translators: %1$s is a date. #: assets/src/js/recurring-totals/index.js:147 +#: build/index.js:15 #, js-format msgid "Starting: %1$s" msgstr "" #: assets/src/js/recurring-totals/index.js:261 +#: build/index.js:18 msgid "Details" msgstr "" #: assets/src/js/utils/index.js:8 +#: build/index.js:1 msgctxt "Used in recurring totals section in Cart. 2+ will need plural, 1 will need singular." msgid "day" msgid_plural "days" @@ -7920,6 +7952,7 @@ msgstr[0] "" msgstr[1] "" #: assets/src/js/utils/index.js:15 +#: build/index.js:1 msgctxt "Used in recurring totals section in Cart. 2+ will need plural, 1 will need singular." msgid "week" msgid_plural "weeks" @@ -7927,6 +7960,7 @@ msgstr[0] "" msgstr[1] "" #: assets/src/js/utils/index.js:22 +#: build/index.js:1 msgctxt "Used in recurring totals section in Cart. 2+ will need plural, 1 will need singular." msgid "month" msgid_plural "months" @@ -7934,6 +7968,7 @@ msgstr[0] "" msgstr[1] "" #: assets/src/js/utils/index.js:29 +#: build/index.js:1 msgctxt "Used in recurring totals section in Cart. 2+ will need plural, 1 will need singular." msgid "year" msgid_plural "years" @@ -7941,53 +7976,64 @@ msgstr[0] "" msgstr[1] "" #: assets/src/js/utils/index.js:63 +#: build/index.js:15 msgid "Daily recurring total" msgstr "" #: assets/src/js/utils/index.js:68 +#: build/index.js:15 msgid "Weekly recurring total" msgstr "" #: assets/src/js/utils/index.js:73 +#: build/index.js:15 msgid "Monthly recurring total" msgstr "" #: assets/src/js/utils/index.js:78 +#: build/index.js:15 msgid "Yearly recurring total" msgstr "" #. translators: %1$s is week, month, year #: assets/src/js/utils/index.js:87 +#: build/index.js:16 #, js-format msgid "Recurring total every 2nd %1$s" msgstr "" #. Translators: %1$s is week, month, year #: assets/src/js/utils/index.js:97 +#: build/index.js:17 #, js-format msgid "Recurring total every 3rd %1$s" msgstr "" #. Translators: %1$d is number of weeks, months, days, years. %2$s is week, month, year #: assets/src/js/utils/index.js:106 +#: build/index.js:18 #, js-format msgid "Recurring total every %1$dth %2$s" msgstr "" #. translators: %1$s is the price of the product. %2$s is the separator used e.g "every" or "/", %3$d is the length, %4$s is week, month, year #: assets/src/js/utils/index.js:162 +#: build/index.js:10 #, js-format msgid "%1$s %2$s %3$d %4$s" msgstr "" #: assets/src/js/utils/index.js:181 +#: build/index.js:33 msgid "Upgrade" msgstr "" #: assets/src/js/utils/index.js:184 +#: build/index.js:33 msgid "Downgrade" msgstr "" #: assets/src/js/utils/index.js:187 +#: build/index.js:33 msgid "Crossgrade" msgstr "" diff --git a/phpstan.neon b/phpstan.neon index cb595db..56632a1 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,12 +2,51 @@ parameters: excludePaths: analyse: - includes/api/legacy/* + - includes/core/legacy/* + - templates/* + - includes/core/upgrades/* + - includes/core/emails/* + - includes/core/gateways/paypal/* + - includes/core/data-stores/* + - includes/core/class-wcs-template-loader.php ignoreErrors: - - '#Access to an undefined property WC_Cart::\$recurring_carts#' + - '#Access to an undefined property WooCommerce::\$#' + - '#Access to an undefined property WC_Cart::\$#' - '#Access to an undefined property WP_User::\$#' + - '#Access to an undefined property WC_Checkout::\$#' + - '#Access to an undefined property WC_Shipping_Rate::\$#' + - '#Access to an undefined property WC_Subscription_Legacy::\$#' + - '#Access to an undefined property WC_Product_Variable_Subscription_Legacy::\$#' + - '#Access to an undefined property WC_Product_Subscription_Variation_Legacy::\$#' + - '#Access to an undefined property WC_Product_Subscription_Legacy::\$#' + - '#Access to an undefined property WCS_PayPal_Reference_Transaction_API::\$#' + - '#Access to an undefined property WC_Email::\$#' + - '#Access to an undefined property WC_Subscriptions_Deprecation_Handler::\$#' + - '#Access to an undefined property WC_Session::\$#' + - '#Access to an undefined static property WC_Subscriptions_Deprecation_Handler::\$#' + - '#Access to an undefined property WooCommerce::\$payment_gateways#' - '#Call to an undefined method WC_Order_Item::get_product#' - '#Call to an undefined method WC_Order_Item::get_variation_id#' - '#Call to an undefined method WCS_Retry_Store::#' - '#Call to an undefined method WC_Data_Store::#' + - '#Call to an undefined method WC_Session::#' + - '#Call to an undefined method WC_Order_Data_Store_CPT::delete_all_metadata_by_key#' + - '#Call to an undefined method object::get_coupons#' + - '#Call to an undefined method object::get_changes#' + - '#Call to method .* on an unknown class WC_PayPal_Express_API#' + - '#Call to method .* on an unknown class OrdersTableQuery#' + - '#Call to method .* on an unknown class ActionScheduler_Action#' + - '#Call to static method .* on an unknown class ActionScheduler_.*#' + - '#Access to property .* on an unknown class WC_Gateway#' + - '#Access to an undefined property object::\$discount_cart#' + - '#Access to an undefined property object::\$value#' - '#Variable \$this might not be defined#' - '#Function as_.*_action not found#' + - '#Function as_.*_actions not found#' + - '#Parameter .* has invalid type WC_Gateway#' + - '#Parameter .* has invalid type ActionScheduler_.*#' + - '#Class WC_Payments_Subscription_Service not found#' + - '#Class .*OrdersTableDataStore not found#' + - '#Parameter .* has invalid type OrdersTableQuery.#' + - '#Constant WC_VERSION not found#' + - '#Method .* has invalid return type ActionScheduler_.*#' diff --git a/templates/admin/html-variation-price.php b/templates/admin/html-variation-price.php index 4253591..e0006bf 100644 --- a/templates/admin/html-variation-price.php +++ b/templates/admin/html-variation-price.php @@ -22,7 +22,10 @@ if ( ! defined( 'ABSPATH' ) ) { // translators: placeholder is a currency symbol / code printf( esc_html__( 'Subscription price (%s)', 'woocommerce-subscriptions' ), esc_html( get_woocommerce_currency_symbol() ) ); ?> - + @@ -48,7 +51,10 @@ if ( ! defined( 'ABSPATH' ) ) { @@ -69,7 +78,10 @@ if ( ! defined( 'ABSPATH' ) ) { diff --git a/templates/admin/html-variation-synchronisation.php b/templates/admin/html-variation-synchronisation.php index 1159bfa..19a2adb 100644 --- a/templates/admin/html-variation-synchronisation.php +++ b/templates/admin/html-variation-synchronisation.php @@ -20,7 +20,10 @@ global $wp_locale;
$value ) : ?> diff --git a/templates/admin/status.php b/templates/admin/status.php index 2367e08..5e084c1 100644 --- a/templates/admin/status.php +++ b/templates/admin/status.php @@ -19,7 +19,10 @@ if ( ! isset( $debug_data ) || ! is_array( $debug_data ) ) {

- +

@@ -44,7 +47,7 @@ if ( ! isset( $debug_data ) || ! is_array( $debug_data ) ) { } ?> - : + :   cart->get_taxes() as $tax_id => $tax_total ) { label ); ?> - + - - + $recurring_cart ) { countries->tax_or_vat() ); ?> - + - + get_formatted_billing_full_name() ); +printf( esc_html_x( 'You have received a subscription renewal order from %1$s. Their order is as follows:', 'Used in admin email: new renewal order', 'woocommerce-subscriptions' ), esc_html( $order->get_formatted_billing_full_name() ) ); echo "\n\n"; @@ -41,4 +41,4 @@ if ( $additional_content ) { echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n"; } -echo apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ); +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/templates/emails/plain/admin-new-switch-order.php b/templates/emails/plain/admin-new-switch-order.php index 94b95fc..b7715a5 100644 --- a/templates/emails/plain/admin-new-switch-order.php +++ b/templates/emails/plain/admin-new-switch-order.php @@ -10,12 +10,12 @@ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } -echo $email_heading . "\n\n"; +echo esc_html( $email_heading ) . "\n\n"; $count = count( $subscriptions ); // translators: $1: customer's first name and last name, $2: how many subscriptions customer switched -printf( _nx( 'Customer %1$s has switched their subscription. The details of their new subscription are as follows:', 'Customer %1$s has switched %2$d of their subscriptions. The details of their new subscriptions are as follows:', $count, 'Used in switch notification admin email', 'woocommerce-subscriptions' ), $order->get_formatted_billing_full_name(), $count ); +printf( esc_html( _nx( 'Customer %1$s has switched their subscription. The details of their new subscription are as follows:', 'Customer %1$s has switched %2$d of their subscriptions. The details of their new subscriptions are as follows:', $count, 'Used in switch notification admin email', 'woocommerce-subscriptions' ) ), esc_html( $order->get_formatted_billing_full_name() ), esc_html( $count ) ); echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n"; @@ -50,4 +50,4 @@ if ( $additional_content ) { echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n"; } -echo apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ); +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/templates/emails/plain/admin-payment-retry.php b/templates/emails/plain/admin-payment-retry.php index a88e7c9..0035df2 100644 --- a/templates/emails/plain/admin-payment-retry.php +++ b/templates/emails/plain/admin-payment-retry.php @@ -14,11 +14,19 @@ if ( ! defined( 'ABSPATH' ) ) { exit; } -echo '= ' . $email_heading . " =\n\n"; +echo '= ' . esc_html( $email_heading ) . " =\n\n"; // translators: %1$s: an order number, %2$s: the customer's full name, %3$s: lowercase human time diff in the form returned by wcs_get_human_time_diff(), e.g. 'in 12 hours' -echo sprintf( _x( 'The automatic recurring payment for order #%1$s from %2$s has failed. The payment will be retried %3$s.', 'In customer renewal invoice email', 'woocommerce-subscriptions' ), $order->get_order_number(), $order->get_formatted_billing_full_name(), wcs_get_human_time_diff( $retry->get_time() ) ) . "\n\n"; -echo sprintf( __( 'The renewal order is as follows:', 'woocommerce-subscriptions' ) ) . "\n\n"; +echo esc_html( + sprintf( + _x( 'The automatic recurring payment for order #%1$s from %2$s has failed. The payment will be retried %3$s.', 'In customer renewal invoice email', 'woocommerce-subscriptions' ), + $order->get_order_number(), + $order->get_formatted_billing_full_name(), + wcs_get_human_time_diff( $retry->get_time() ) + ) +) . "\n\n"; + +echo esc_html( sprintf( __( 'The renewal order is as follows:', 'woocommerce-subscriptions' ) ) ) . "\n\n"; echo "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n"; @@ -51,4 +59,4 @@ if ( $additional_content ) { echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n"; } -echo apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ); +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/templates/emails/plain/cancelled-subscription.php b/templates/emails/plain/cancelled-subscription.php index 95e0656..f0f3007 100644 --- a/templates/emails/plain/cancelled-subscription.php +++ b/templates/emails/plain/cancelled-subscription.php @@ -10,10 +10,10 @@ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } -echo $email_heading . "\n\n"; +echo wp_kses_post( $email_heading ) . "\n\n"; // translators: $1: customer's billing first name and last name -printf( __( 'A subscription belonging to %1$s has been cancelled. Their subscription\'s details are as follows:', 'woocommerce-subscriptions' ), $subscription->get_formatted_billing_full_name() ); +printf( esc_html__( 'A subscription belonging to %1$s has been cancelled. Their subscription\'s details are as follows:', 'woocommerce-subscriptions' ), esc_html( $subscription->get_formatted_billing_full_name() ) ); echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; @@ -29,19 +29,20 @@ $last_order_time_created = $subscription->get_time( 'last_order_date_created', ' if ( ! empty( $last_order_time_created ) ) { // translators: placeholder is last time subscription was paid - echo sprintf( __( 'Last Order Date: %s', 'woocommerce-subscriptions' ), date_i18n( wc_date_format(), $last_order_time_created ) ) . "\n"; + echo esc_html( sprintf( __( 'Last Order Date: %s', 'woocommerce-subscriptions' ), date_i18n( wc_date_format(), $last_order_time_created ) ) ) . "\n"; } $end_time = $subscription->get_time( 'end', 'site' ); if ( ! empty( $end_time ) ) { // translators: placeholder is localised date string - echo sprintf( __( 'End of Prepaid Term: %s', 'woocommerce-subscriptions' ), date_i18n( wc_date_format(), $end_time ) ) . "\n"; + echo esc_html( sprintf( __( 'End of Prepaid Term: %s', 'woocommerce-subscriptions' ), date_i18n( wc_date_format(), $end_time ) ) ) . "\n"; } do_action( 'woocommerce_email_order_meta', $subscription, $sent_to_admin, $plain_text, $email ); -echo "\n" . sprintf( _x( 'View Subscription: %s', 'in plain emails for subscription information', 'woocommerce-subscriptions' ), wcs_get_edit_post_link( $subscription->get_id() ) ) . "\n"; +// translators: %s: subscription URL +echo "\n" . esc_html( sprintf( _x( 'View Subscription: %s', 'in plain emails for subscription information', 'woocommerce-subscriptions' ), esc_url( wcs_get_edit_post_link( $subscription->get_id() ) ) ) ) . "\n"; echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n"; @@ -57,4 +58,4 @@ if ( $additional_content ) { echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n"; } -echo apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ); +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/templates/emails/plain/customer-completed-renewal-order.php b/templates/emails/plain/customer-completed-renewal-order.php index 8bf2786..5cefa08 100644 --- a/templates/emails/plain/customer-completed-renewal-order.php +++ b/templates/emails/plain/customer-completed-renewal-order.php @@ -10,7 +10,7 @@ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } -echo $email_heading . "\n\n"; +echo esc_html( $email_heading ) . "\n\n"; /* translators: %s: Customer first name */ echo sprintf( esc_html__( 'Hi %s,', 'woocommerce-subscriptions' ), esc_html( $order->get_billing_first_name() ) ) . "\n\n"; @@ -36,4 +36,4 @@ if ( $additional_content ) { echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n"; } -echo apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ); +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/templates/emails/plain/customer-completed-switch-order.php b/templates/emails/plain/customer-completed-switch-order.php index adc6b16..04c0114 100644 --- a/templates/emails/plain/customer-completed-switch-order.php +++ b/templates/emails/plain/customer-completed-switch-order.php @@ -10,7 +10,7 @@ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } -echo $email_heading . "\n\n"; +echo esc_html( $email_heading ) . "\n\n"; /* translators: %s: Customer first name */ echo sprintf( esc_html__( 'Hi %s,', 'woocommerce-subscriptions' ), esc_html( $order->get_billing_first_name() ) ) . "\n\n"; @@ -21,7 +21,7 @@ echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\ do_action( 'woocommerce_subscriptions_email_order_details', $order, $sent_to_admin, $plain_text, $email ); // translators: placeholder is order's view url -echo "\n" . sprintf( __( 'View your order: %s', 'woocommerce-subscriptions' ), $order->get_view_order_url() ); +echo "\n" . sprintf( esc_html__( 'View your order: %s', 'woocommerce-subscriptions' ), esc_url( $order->get_view_order_url() ) ); echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; @@ -32,7 +32,7 @@ foreach ( $subscriptions as $subscription ) { do_action( 'woocommerce_subscriptions_email_order_details', $subscription, $sent_to_admin, $plain_text, $email ); // translators: placeholder is subscription's view url - echo "\n" . sprintf( __( 'View your subscription: %s', 'woocommerce-subscriptions' ), $subscription->get_view_order_url() ); + echo "\n" . sprintf( esc_html__( 'View your subscription: %s', 'woocommerce-subscriptions' ), esc_url( $subscription->get_view_order_url() ) ); } echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; @@ -48,4 +48,4 @@ if ( $additional_content ) { echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n"; } -echo apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ); +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/templates/emails/plain/customer-notification-auto-renewal.php b/templates/emails/plain/customer-notification-auto-renewal.php index 023f796..cb37b45 100644 --- a/templates/emails/plain/customer-notification-auto-renewal.php +++ b/templates/emails/plain/customer-notification-auto-renewal.php @@ -56,4 +56,4 @@ if ( $additional_content ) { echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; } -echo esc_html( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/templates/emails/plain/customer-notification-auto-trial-ending.php b/templates/emails/plain/customer-notification-auto-trial-ending.php index b6090ef..57a94d0 100644 --- a/templates/emails/plain/customer-notification-auto-trial-ending.php +++ b/templates/emails/plain/customer-notification-auto-trial-ending.php @@ -59,4 +59,4 @@ if ( $additional_content ) { echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; } -echo esc_html( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/templates/emails/plain/customer-notification-expiring-subscription.php b/templates/emails/plain/customer-notification-expiring-subscription.php index 73320af..8a7582d 100644 --- a/templates/emails/plain/customer-notification-expiring-subscription.php +++ b/templates/emails/plain/customer-notification-expiring-subscription.php @@ -51,4 +51,4 @@ if ( $additional_content ) { echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; } -echo esc_html( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/templates/emails/plain/customer-notification-manual-renewal.php b/templates/emails/plain/customer-notification-manual-renewal.php index 848ee62..30ef07b 100644 --- a/templates/emails/plain/customer-notification-manual-renewal.php +++ b/templates/emails/plain/customer-notification-manual-renewal.php @@ -74,4 +74,4 @@ if ( $additional_content ) { echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; } -echo esc_html( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/templates/emails/plain/customer-notification-manual-trial-ending.php b/templates/emails/plain/customer-notification-manual-trial-ending.php index 45ebcdd..0accaf0 100644 --- a/templates/emails/plain/customer-notification-manual-trial-ending.php +++ b/templates/emails/plain/customer-notification-manual-trial-ending.php @@ -53,4 +53,4 @@ if ( $additional_content ) { echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; } -echo esc_html( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/templates/emails/plain/customer-payment-retry.php b/templates/emails/plain/customer-payment-retry.php index 3747ea0..ac92c06 100644 --- a/templates/emails/plain/customer-payment-retry.php +++ b/templates/emails/plain/customer-payment-retry.php @@ -10,7 +10,7 @@ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } -echo $email_heading . "\n\n"; +echo esc_html( $email_heading ) . "\n\n"; /* translators: %s: Customer first name */ echo sprintf( esc_html__( 'Hi %s,', 'woocommerce-subscriptions' ), esc_html( $order->get_billing_first_name() ) ) . "\n\n"; @@ -18,7 +18,7 @@ echo sprintf( esc_html__( 'Hi %s,', 'woocommerce-subscriptions' ), esc_html( $or echo sprintf( esc_html_x( 'The automatic payment to renew your subscription has failed. We will retry the payment %s.', 'In customer renewal invoice email', 'woocommerce-subscriptions' ), esc_html( wcs_get_human_time_diff( $retry->get_time() ) ) ) . "\n\n"; // translators: %1$s: link to checkout payment url, note: no full stop due to url at the end -echo sprintf( esc_html_x( 'To reactivate the subscription now, you can also log in and pay for the renewal from your account page: %1$s', 'In customer renewal invoice email', 'woocommerce-subscriptions' ), esc_attr( $order->get_checkout_payment_url() ) ); +echo sprintf( esc_html_x( 'To reactivate the subscription now, you can also log in and pay for the renewal from your account page: %1$s', 'In customer renewal invoice email', 'woocommerce-subscriptions' ), esc_url( $order->get_checkout_payment_url() ) ); echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; @@ -34,4 +34,4 @@ if ( $additional_content ) { echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n"; } -echo apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ); +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/templates/emails/plain/customer-processing-renewal-order.php b/templates/emails/plain/customer-processing-renewal-order.php index 2b27e0f..69aca2b 100644 --- a/templates/emails/plain/customer-processing-renewal-order.php +++ b/templates/emails/plain/customer-processing-renewal-order.php @@ -10,7 +10,7 @@ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } -echo $email_heading . "\n\n"; +echo esc_html( $email_heading ) . "\n\n"; /* translators: %s: Customer first name */ echo sprintf( esc_html__( 'Hi %s,', 'woocommerce-subscriptions' ), esc_html( $order->get_billing_first_name() ) ) . "\n\n"; @@ -37,4 +37,4 @@ if ( $additional_content ) { echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n"; } -echo apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ); +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/templates/emails/plain/customer-renewal-invoice.php b/templates/emails/plain/customer-renewal-invoice.php index f6c38e8..9c0bc18 100644 --- a/templates/emails/plain/customer-renewal-invoice.php +++ b/templates/emails/plain/customer-renewal-invoice.php @@ -10,7 +10,7 @@ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } -echo $email_heading . "\n\n"; +echo esc_html( $email_heading ) . "\n\n"; /* translators: %s: Customer first name */ echo sprintf( esc_html__( 'Hi %s,', 'woocommerce-subscriptions' ), esc_html( $order->get_billing_first_name() ) ) . "\n\n"; @@ -37,4 +37,4 @@ if ( $additional_content ) { echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n"; } -echo apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ); +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/templates/emails/plain/email-order-details.php b/templates/emails/plain/email-order-details.php index 2764688..a9d4eb5 100644 --- a/templates/emails/plain/email-order-details.php +++ b/templates/emails/plain/email-order-details.php @@ -13,18 +13,18 @@ if ( ! defined( 'ABSPATH' ) ) { do_action( 'woocommerce_email_before_' . $order_type . '_table', $order, $sent_to_admin, $plain_text, $email ); if ( 'order' == $order_type ) { - echo sprintf( __( 'Order number: %s', 'woocommerce-subscriptions' ), $order->get_order_number() ) . "\n"; - echo sprintf( __( 'Order date: %s', 'woocommerce-subscriptions' ), wcs_format_datetime( wcs_get_objects_property( $order, 'date_created' ) ) ) . "\n"; + echo esc_html( sprintf( __( 'Order number: %s', 'woocommerce-subscriptions' ), $order->get_order_number() ) ) . "\n"; + echo esc_html( sprintf( __( 'Order date: %s', 'woocommerce-subscriptions' ), wcs_format_datetime( wcs_get_objects_property( $order, 'date_created' ) ) ) ) . "\n"; } else { - echo sprintf( __( 'Subscription Number: %s', 'woocommerce-subscriptions' ), $order->get_order_number() ) . "\n"; + echo esc_html( sprintf( __( 'Subscription Number: %s', 'woocommerce-subscriptions' ), $order->get_order_number() ) ) . "\n"; } -echo "\n" . WC_Subscriptions_Email::email_order_items_table( $order, $order_items_table_args ); +echo "\n" . esc_html( WC_Subscriptions_Email::email_order_items_table( $order, $order_items_table_args ) ); echo "----------\n\n"; if ( $totals = $order->get_order_item_totals() ) { foreach ( $totals as $total ) { - echo $total['label'] . "\t " . $total['value'] . "\n"; + echo esc_html( $total['label'] ) . "\t " . esc_html( $total['value'] ) . "\n"; } } diff --git a/templates/emails/plain/expired-subscription.php b/templates/emails/plain/expired-subscription.php index 538afb1..b6ccf74 100644 --- a/templates/emails/plain/expired-subscription.php +++ b/templates/emails/plain/expired-subscription.php @@ -10,10 +10,10 @@ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } -echo $email_heading . "\n\n"; +echo wp_kses_post( $email_heading ) . "\n\n"; // translators: $1: customer's billing first name and last name -printf( __( 'A subscription belonging to %1$s has expired. Their subscription\'s details are as follows:', 'woocommerce-subscriptions' ), $subscription->get_formatted_billing_full_name() ); +printf( esc_html__( 'A subscription belonging to %1$s has expired. Their subscription\'s details are as follows:', 'woocommerce-subscriptions' ), esc_html( $subscription->get_formatted_billing_full_name() ) ); echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; @@ -29,19 +29,20 @@ $last_order_time_created = $subscription->get_time( 'last_order_date_created', ' if ( ! empty( $last_order_time_created ) ) { // translators: placeholder is last time subscription was paid - echo sprintf( __( 'Last Order Date: %s', 'woocommerce-subscriptions' ), date_i18n( wc_date_format(), $last_order_time_created ) ) . "\n"; + echo esc_html( sprintf( __( 'Last Order Date: %s', 'woocommerce-subscriptions' ), date_i18n( wc_date_format(), $last_order_time_created ) ) ) . "\n"; } $end_time = $subscription->get_time( 'end', 'site' ); if ( ! empty( $end_time ) ) { // translators: placeholder is localised date string - echo sprintf( __( 'End Date: %s', 'woocommerce-subscriptions' ), date_i18n( wc_date_format(), $end_time ) ) . "\n"; + echo esc_html( sprintf( __( 'End Date: %s', 'woocommerce-subscriptions' ), date_i18n( wc_date_format(), $end_time ) ) ) . "\n"; } do_action( 'woocommerce_email_order_meta', $subscription, $sent_to_admin, $plain_text, $email ); -echo "\n" . sprintf( _x( 'View Subscription: %s', 'in plain emails for subscription information', 'woocommerce-subscriptions' ), wcs_get_edit_post_link( $subscription->get_id() ) ) . "\n"; +// translators: %s: subscription URL +echo "\n" . esc_html( sprintf( _x( 'View Subscription: %s', 'in plain emails for subscription information', 'woocommerce-subscriptions' ), wcs_get_edit_post_link( $subscription->get_id() ) ) ) . "\n"; echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n"; @@ -57,4 +58,4 @@ if ( $additional_content ) { echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n"; } -echo apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ); +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/templates/emails/plain/on-hold-subscription.php b/templates/emails/plain/on-hold-subscription.php index 95de646..9712e6d 100644 --- a/templates/emails/plain/on-hold-subscription.php +++ b/templates/emails/plain/on-hold-subscription.php @@ -10,10 +10,10 @@ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } -echo $email_heading . "\n\n"; +echo esc_html( $email_heading ) . "\n\n"; // translators: $1: customer's billing first name and last name -printf( __( 'A subscription belonging to %1$s has been suspended by the user. Their subscription\'s details are as follows:', 'woocommerce-subscriptions' ), $subscription->get_formatted_billing_full_name() ); +printf( esc_html__( 'A subscription belonging to %1$s has been suspended by the user. Their subscription\'s details are as follows:', 'woocommerce-subscriptions' ), esc_html( $subscription->get_formatted_billing_full_name() ) ); echo "\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; @@ -29,15 +29,15 @@ $last_order_time_created = $subscription->get_time( 'last_order_date_created', ' if ( ! empty( $last_order_time_created ) ) { // translators: placeholder is last time subscription was paid - echo sprintf( __( 'Last Order: %s', 'woocommerce-subscriptions' ), date_i18n( wc_date_format(), $last_order_time_created ) ) . "\n"; + echo esc_html( sprintf( __( 'Last Order: %s', 'woocommerce-subscriptions' ), date_i18n( wc_date_format(), $last_order_time_created ) ) ) . "\n"; } // translators: placeholder is localised date string -echo sprintf( __( 'Date Suspended: %s', 'woocommerce-subscriptions' ), date_i18n( wc_date_format(), time() ) ) . "\n"; +echo esc_html( sprintf( __( 'Date Suspended: %s', 'woocommerce-subscriptions' ), date_i18n( wc_date_format(), time() ) ) ) . "\n"; do_action( 'woocommerce_email_order_meta', $subscription, $sent_to_admin, $plain_text, $email ); -echo "\n" . sprintf( _x( 'View Subscription: %s', 'in plain emails for subscription information', 'woocommerce-subscriptions' ), wcs_get_edit_post_link( $subscription->get_id() ) ) . "\n"; +echo "\n" . esc_html( sprintf( _x( 'View Subscription: %s', 'in plain emails for subscription information', 'woocommerce-subscriptions' ), wcs_get_edit_post_link( $subscription->get_id() ) ) ) . "\n"; echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n"; @@ -53,4 +53,4 @@ if ( $additional_content ) { echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n"; } -echo apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ); +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/templates/emails/plain/subscription-info.php b/templates/emails/plain/subscription-info.php index 5a92a94..cfcc79a 100644 --- a/templates/emails/plain/subscription-info.php +++ b/templates/emails/plain/subscription-info.php @@ -16,22 +16,22 @@ if ( empty( $subscriptions ) ) { $has_automatic_renewal = false; $is_parent_order = wcs_order_contains_subscription( $order, 'parent' ); -echo "\n\n" . __( 'Subscription information', 'woocommerce-subscriptions' ) . "\n\n"; +echo "\n\n" . esc_html__( 'Subscription information', 'woocommerce-subscriptions' ) . "\n\n"; foreach ( $subscriptions as $subscription ) { $has_automatic_renewal = $has_automatic_renewal || ! $subscription->is_manual(); // translators: placeholder is subscription's number - echo sprintf( _x( 'Subscription: %s', 'in plain emails for subscription information', 'woocommerce-subscriptions' ), $subscription->get_order_number() ) . "\n"; + echo esc_html( sprintf( _x( 'Subscription: %s', 'in plain emails for subscription information', 'woocommerce-subscriptions' ), $subscription->get_order_number() ) ) . "\n"; // translators: placeholder is either view or edit url for the subscription - echo sprintf( _x( 'View subscription: %s', 'in plain emails for subscription information', 'woocommerce-subscriptions' ), $is_admin_email ? wcs_get_edit_post_link( $subscription->get_id() ) : $subscription->get_view_order_url() ) . "\n"; + echo esc_html( sprintf( _x( 'View subscription: %s', 'in plain emails for subscription information', 'woocommerce-subscriptions' ), $is_admin_email ? wcs_get_edit_post_link( $subscription->get_id() ) : $subscription->get_view_order_url() ) ) . "\n"; // translators: placeholder is localised start date - echo sprintf( _x( 'Start date: %s', 'in plain emails for subscription information', 'woocommerce-subscriptions' ), date_i18n( wc_date_format(), $subscription->get_time( 'start_date', 'site' ) ) ) . "\n"; + echo esc_html( sprintf( _x( 'Start date: %s', 'in plain emails for subscription information', 'woocommerce-subscriptions' ), date_i18n( wc_date_format(), $subscription->get_time( 'start_date', 'site' ) ) ) ) . "\n"; $end_date = ( 0 < $subscription->get_time( 'end' ) ) ? date_i18n( wc_date_format(), $subscription->get_time( 'end', 'site' ) ) : _x( 'When Cancelled', 'Used as end date for an indefinite subscription', 'woocommerce-subscriptions' ); // translators: placeholder is localised end date, or "when cancelled" - echo sprintf( _x( 'End date: %s', 'in plain emails for subscription information', 'woocommerce-subscriptions' ), $end_date ) . "\n"; + echo esc_html( sprintf( _x( 'End date: %s', 'in plain emails for subscription information', 'woocommerce-subscriptions' ), $end_date ) ) . "\n"; // translators: placeholder is the formatted order total for the subscription - echo sprintf( _x( 'Recurring price: %s', 'in plain emails for subscription information', 'woocommerce-subscriptions' ), $subscription->get_formatted_order_total() ); + echo esc_html( sprintf( _x( 'Recurring price: %s', 'in plain emails for subscription information', 'woocommerce-subscriptions' ), $subscription->get_formatted_order_total() ) ); if ( $is_parent_order && $subscription->get_time( 'next_payment' ) > 0 ) { echo "\n" . sprintf( esc_html__( 'Next payment: %s', 'woocommerce-subscriptions' ), esc_html( date_i18n( wc_date_format(), $subscription->get_time( 'next_payment', 'site' ) ) ) ); diff --git a/templates/single-product/add-to-cart/variable-subscription.php b/templates/single-product/add-to-cart/variable-subscription.php index 813ea48..95d8ec3 100644 --- a/templates/single-product/add-to-cart/variable-subscription.php +++ b/templates/single-product/add-to-cart/variable-subscription.php @@ -16,7 +16,7 @@ $user_id = get_current_user_id(); do_action( 'woocommerce_before_add_to_cart_form' ); ?> - + @@ -37,7 +37,7 @@ do_action( 'woocommerce_before_add_to_cart_form' ); ?> $options ) : ?> - + get_variation_default_attribute( $attribute_name ); diff --git a/vendor/autoload.php b/vendor/autoload.php index c406157..e962f88 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -14,10 +14,7 @@ if (PHP_VERSION_ID < 50600) { echo $err; } } - trigger_error( - $err, - E_USER_ERROR - ); + throw new RuntimeException($err); } require_once __DIR__ . '/composer/autoload_real.php'; diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php index 51e734a..2052022 100644 --- a/vendor/composer/InstalledVersions.php +++ b/vendor/composer/InstalledVersions.php @@ -26,12 +26,23 @@ use Composer\Semver\VersionParser; */ class InstalledVersions { + /** + * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to + * @internal + */ + private static $selfDir = null; + /** * @var mixed[]|null * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null */ private static $installed; + /** + * @var bool + */ + private static $installedIsLocalDir; + /** * @var bool|null */ @@ -309,6 +320,24 @@ class InstalledVersions { self::$installed = $data; self::$installedByVendor = array(); + + // when using reload, we disable the duplicate protection to ensure that self::$installed data is + // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, + // so we have to assume it does not, and that may result in duplicate data being returned when listing + // all installed packages for example + self::$installedIsLocalDir = false; + } + + /** + * @return string + */ + private static function getSelfDir() + { + if (self::$selfDir === null) { + self::$selfDir = strtr(__DIR__, '\\', '/'); + } + + return self::$selfDir; } /** @@ -322,19 +351,27 @@ class InstalledVersions } $installed = array(); + $copiedLocalDir = false; if (self::$canGetVendors) { + $selfDir = self::getSelfDir(); foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + $vendorDir = strtr($vendorDir, '\\', '/'); if (isset(self::$installedByVendor[$vendorDir])) { $installed[] = self::$installedByVendor[$vendorDir]; } elseif (is_file($vendorDir.'/composer/installed.php')) { /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ $required = require $vendorDir.'/composer/installed.php'; - $installed[] = self::$installedByVendor[$vendorDir] = $required; - if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { - self::$installed = $installed[count($installed) - 1]; + self::$installedByVendor[$vendorDir] = $required; + $installed[] = $required; + if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { + self::$installed = $required; + self::$installedIsLocalDir = true; } } + if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { + $copiedLocalDir = true; + } } } @@ -350,7 +387,7 @@ class InstalledVersions } } - if (self::$installed !== array()) { + if (self::$installed !== array() && !$copiedLocalDir) { $installed[] = self::$installed; } diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 9a3216b..0325294 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -1,9 +1,9 @@ array( 'name' => 'woocommerce/woocommerce-subscriptions', - 'pretty_version' => 'dev-release/7.6.0', - 'version' => 'dev-release/7.6.0', - 'reference' => 'd10def0fd6301975f28b30ae7009dc41139e063f', + 'pretty_version' => 'dev-release/7.7.0', + 'version' => 'dev-release/7.7.0', + 'reference' => '0bc6889c24678c74eece0338f75469fdc59e8f69', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -20,9 +20,9 @@ 'dev_requirement' => false, ), 'woocommerce/woocommerce-subscriptions' => array( - 'pretty_version' => 'dev-release/7.6.0', - 'version' => 'dev-release/7.6.0', - 'reference' => 'd10def0fd6301975f28b30ae7009dc41139e063f', + 'pretty_version' => 'dev-release/7.7.0', + 'version' => 'dev-release/7.7.0', + 'reference' => '0bc6889c24678c74eece0338f75469fdc59e8f69', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), diff --git a/woocommerce-subscriptions.php b/woocommerce-subscriptions.php index 3e61754..056f437 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: 7.6.0 + * Version: 7.7.0 * Requires Plugins: woocommerce * - * WC requires at least: 8.7.1 - * WC tested up to: 9.9.0 + * WC requires at least: 9.8.5 + * WC tested up to: 9.9.5 * Woo: 27147:6115e6d7e297b623a169fdcf5728b224 * * Copyright 2019 WooCommerce @@ -67,6 +67,11 @@ $wcs_autoloader = wcs_init_autoloader(); * @since 1.0 */ class WC_Subscriptions { + /** + * Name of the option field used to store the active plugin version. This is principally + * used in support of plugin update logic. + */ + public const PLUGIN_VERSION_OPTION_NAME = 'wcs_plugin_version'; /** @var string */ public static $name = 'subscription'; @@ -78,7 +83,7 @@ class WC_Subscriptions { public static $plugin_file = __FILE__; /** @var string */ - public static $version = '7.6.0'; // WRCS: DEFINED_VERSION. + public static $version = '7.7.0'; // WRCS: DEFINED_VERSION. /** @var string */ public static $wc_minimum_supported_version = '7.7';