diff --git a/.wp-env.json b/.wp-env.json index a4166b6..027ecfc 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -2,6 +2,7 @@ "plugins": [ "https://downloads.wordpress.org/plugin/woocommerce.zip", "https://downloads.wordpress.org/plugin/email-log.zip", + "https://github.com/woocommerce/woocommerce-gateway-dummy/releases/download/1.0.8/woocommerce-gateway-dummy.zip", ".", "./tests/e2e/test-configuration-plugin" ], diff --git a/changelog.txt b/changelog.txt index a8b0a49..a5947dd 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,14 @@ *** WooCommerce Subscriptions Changelog *** +2024-06-13 - version 6.4.0 +* Add: New WP CLI support to manage subscriptions via command line. +* Add: Introduce wc/v2 subscription REST API endpoints. +* Fix: label improvement on my subscription page template. +* Fix: Regenerate subscriptions related order caches (renewal, resubscribe, switch) if it's stored as an invalid value to prevent fatal errors. +* Update: Show "FREE" instead of 0 when there is no shipping cost in the recurring totals section of the Cart and Checkout blocks (requires WooCommerce 9.0+). +* Dev: New function wcs_set_recurring_item_total() to set line item totals that have been copied from an initial order to their proper recurring totals (i.e. remove sign-up fees). +* Dev: Updated subscriptions-core to 7.2.0. + 2024-05-24 - version 6.3.2 * Fix: Prevent overriding line item totals provided in request data when creating Orders via the REST API. diff --git a/includes/api/class-wc-rest-subscriptions-controller.php b/includes/api/class-wc-rest-subscriptions-controller.php index 57f6cad..ccafd6f 100644 --- a/includes/api/class-wc-rest-subscriptions-controller.php +++ b/includes/api/class-wc-rest-subscriptions-controller.php @@ -626,7 +626,7 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_Controller { wcs_copy_order_item( $item, $subscription_item ); // Don't include sign-up fees or $0 trial periods when setting the subscriptions item totals. - $this->maybe_set_recurring_item_total( $subscription_item ); + wcs_set_recurring_item_total( $subscription_item ); $subscription_item->save(); @@ -718,41 +718,4 @@ class WC_REST_Subscriptions_Controller extends WC_REST_Orders_Controller { return rest_ensure_response( $subscriptions ); } - - /** - * Set the subscription item total to its recurring product price. - * - * This function ensures that sign-up fees and/or $0 trial periods are not carried over from the initial order to the subscription. - * Note: If the line item has a custom total set by the merchant, don't override it with the recurring price. - * - * @param WC_Order_Item $item Subscription line item. - * - */ - private function maybe_set_recurring_item_total( &$item ) { - $product = $item->get_product(); - - if ( ! $product ) { - return; - } - - $sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee( $product ); - $sign_up_fee = is_numeric( $sign_up_fee ) ? (float) $sign_up_fee : 0; - $trial_length = WC_Subscriptions_Product::get_trial_length( $product ); - - $recurring_price = (float) $product->get_price(); - $initial_price = $trial_length > 0 ? $sign_up_fee : $recurring_price + $sign_up_fee; - $initial_total = wc_get_price_excluding_tax( $product, [ 'qty' => $item->get_quantity(), 'price' => $initial_price ] ); - - // Check if a custom item total was set on the order. If so, don't override it. - if ( (float) $item->get_subtotal() !== $initial_total ) { - return; - } - - $recurring_total = wc_get_price_excluding_tax( $product, [ 'qty' => $item->get_quantity(), 'price' => $recurring_price ] ); - - $item->set_props( [ - 'subtotal' => $recurring_total, - 'total' => $recurring_total, - ] ); - } } diff --git a/includes/api/v2/class-wc-rest-subscription-notes-v2-controller.php b/includes/api/v2/class-wc-rest-subscription-notes-v2-controller.php new file mode 100644 index 0000000..c3e73c6 --- /dev/null +++ b/includes/api/v2/class-wc-rest-subscription-notes-v2-controller.php @@ -0,0 +1,33 @@ +/notes endpoint. + */ + +if ( ! defined( 'ABSPATH' ) ) { + exit; +} +/** + * REST API Subscription Notes controller class. + * + * @package WooCommerce_Subscriptions/API + * @extends WC_REST_Order_Notes_Controller + */ +class WC_REST_Subscription_Notes_V2_Controller extends WC_REST_Order_Notes_V2_Controller { + + /** + * Route base. + * + * @var string + */ + protected $rest_base = 'subscriptions/(?P[\d]+)/notes'; + + /** + * Post type. + * + * @var string + */ + protected $post_type = 'shop_subscription'; + +} diff --git a/includes/api/v2/class-wc-rest-subscriptions-v2-controller.php b/includes/api/v2/class-wc-rest-subscriptions-v2-controller.php new file mode 100644 index 0000000..39c2df3 --- /dev/null +++ b/includes/api/v2/class-wc-rest-subscriptions-v2-controller.php @@ -0,0 +1,787 @@ + + * GET /subscriptions/status + * GET /subscriptions//orders + * POST /orders//subscriptions + * + * @since 6.4.0 + */ + public function register_routes() { + parent::register_routes(); + + register_rest_route( $this->namespace, "/{$this->rest_base}/statuses", [ // nosemgrep: audit.php.wp.security.rest-route.permission-callback.return-true -- /subscriptions/statuses is a public endpoint and doesn't need any permission checks. + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_statuses' ], + 'permission_callback' => '__return_true', + ], + 'schema' => [ $this, 'get_statuses_schema' ], + ] ); + + register_rest_route( $this->namespace, "/{$this->rest_base}/(?P[\d]+)/orders", [ + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_subscription_orders' ], + 'permission_callback' => [ $this, 'get_items_permissions_check' ], + 'args' => $this->get_collection_params(), + ], + 'schema' => [ $this, 'get_subscription_orders_schema' ], + ] ); + + register_rest_route( $this->namespace, "/orders/(?P[\d]+)/{$this->rest_base}", [ + [ + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => [ $this, 'create_subscriptions_from_order' ], + 'permission_callback' => [ $this, 'create_item_permissions_check' ], + 'args' => $this->get_collection_params(), + ], + 'schema' => [ $this, 'create_subscriptions_from_order_schema' ], + ] ); + } + + /** + * Gets the request object. Return false if the ID is not a subscription. + * + * @since 6.4.0 + * + * @param int $id Object ID. + * + * @return WC_Subscription|bool + */ + protected function get_object( $id ) { + $subscription = wcs_get_subscription( $id ); + + if ( ! $subscription || ! is_a( $subscription, 'WC_Subscription' ) ) { + return false; + } + + return $subscription; + } + + /** + * Prepare a single subscription output for response. + * + * @since 6.4.0 + * + * @param WC_Data $object Subscription object. + * @param WP_REST_Request $request Request object. + * + * @return WP_REST_Response + */ + public function prepare_object_for_response( $object, $request ) { + $response = parent::prepare_object_for_response( $object, $request ); + + // When generating the `/subscriptions/[id]/orders` response this function is called to generate related-order data so exit early if this isn't a subscription. + if ( ! wcs_is_subscription( $object ) ) { + return $response; + } + + // Add subscription specific data to the base order response data. + $response->data['billing_period'] = $object->get_billing_period(); + $response->data['billing_interval'] = $object->get_billing_interval(); + + foreach ( wcs_get_subscription_date_types() as $date_type => $date_name ) { + $date = $object->get_date( wcs_normalise_date_type_key( $date_type ) ); + $response->data[ $date_type . '_date' ] = ( ! empty( $date ) ) ? wc_rest_prepare_date_response( $date, false ) : ''; + $response->data[ $date_type . '_date_gmt' ] = ( ! empty( $date ) ) ? wc_rest_prepare_date_response( $date ) : ''; + } + + // Some base WC_Order dates need to be pulled from the subscription object to be correct. + $response->data['date_paid'] = wc_rest_prepare_date_response( $object->get_date_paid(), false ); + $response->data['date_paid_gmt'] = wc_rest_prepare_date_response( $object->get_date_paid() ); + $response->data['date_completed'] = wc_rest_prepare_date_response( $object->get_date_completed(), false ); + $response->data['date_completed_gmt'] = wc_rest_prepare_date_response( $object->get_date_completed() ); + + // Include resubscribe data. + $resubscribed_subscriptions = array_filter( $object->get_related_orders( 'ids', 'resubscribe' ), 'wcs_is_subscription' ); + $response->data['resubscribed_from'] = strval( $object->get_meta( '_subscription_resubscribe' ) ); + $response->data['resubscribed_subscription'] = strval( reset( $resubscribed_subscriptions ) ); // Subscriptions can only be resubscribed to once so return the first and only element. + + // Include the removed line items. + $response->data['removed_line_items'] = []; + + foreach ( $object->get_items( 'line_item_removed' ) as $item ) { + $response->data['removed_line_items'][] = $this->get_order_item_data( $item ); + } + + // Remove non-subscription properties + unset( $response->data['cart_hash'] ); + unset( $response->data['transaction_id'] ); + + return $response; + } + + /** + * Gets the /subscriptions/statuses response. + * + * @since 6.4.0 + * + * @return WP_REST_Response The response object. + */ + public function get_statuses() { + return rest_ensure_response( wcs_get_subscription_statuses() ); + } + + /** + * Gets the /subscriptions/[id]/orders response. + * + * @since 6.4.0 + * + * @param WP_REST_Request $request The request object. + * + * @return WP_Error|WP_REST_Response $response The response or an error if one occurs. + */ + public function get_subscription_orders( $request ) { + $id = absint( $request['id'] ); + + if ( empty( $id ) || ! wcs_is_subscription( $id ) ) { + return new WP_Error( 'woocommerce_rest_invalid_shop_subscription_id', __( 'Invalid subscription ID.', 'woocommerce-subscriptions' ), [ 'status' => 404 ] ); + } + + $subscription = wcs_get_subscription( $id ); + + if ( ! $subscription ) { + return new WP_Error( 'woocommerce_rest_invalid_shop_subscription_id', sprintf( __( 'Failed to load subscription object with the ID %d.', 'woocommerce-subscriptions' ), $id ), [ 'status' => 404 ] ); + } + + $orders = []; + + foreach ( [ 'parent', 'renewal', 'switch' ] as $order_type ) { + foreach ( $subscription->get_related_orders( 'ids', $order_type ) as $order_id ) { + + if ( ! wc_rest_check_post_permissions( 'shop_order', 'read', $order_id ) ) { + continue; + } + + // Validate that the order can be loaded before trying to generate a response object for it. + $order = wc_get_order( $order_id ); + + if ( ! $order ) { + continue; + } + + $response = parent::prepare_object_for_response( $order, $request ); + + // Add the order's relationship to the response. + $response->data['order_type'] = $order_type . '_order'; + + $orders[] = $this->prepare_response_for_collection( $response ); + } + } + + $response = rest_ensure_response( $orders ); + $response->header( 'X-WP-Total', count( $orders ) ); + $response->header( 'X-WP-TotalPages', 1 ); + + return apply_filters( 'wcs_rest_subscription_orders_response', $response, $request ); + } + + /** + * Overrides WC_REST_Orders_V2_Controller::get_order_statuses() so that subscription statuses are + * validated correctly. + * + * @since 6.4.0 + * + * @return array An array of valid subscription statuses. + */ + protected function get_order_statuses() { + $subscription_statuses = []; + + foreach ( wcs_get_subscription_statuses() as $status => $status_name ) { + $subscription_statuses[] = str_replace( 'wc-', '', $status ); + } + + return $subscription_statuses; + } + + /** + * Prepares a single subscription for creation or update. + * + * @since 6.4.0 + * + * @param WP_REST_Request $request Request object. + * @param bool $creating If the request is for creating a new object. + * + * @return WP_Error|WC_Subscription + */ + public function prepare_object_for_database( $request, $creating = false ) { + $id = isset( $request['id'] ) ? absint( $request['id'] ) : 0; + $subscription = new WC_Subscription( $id ); + $schema = $this->get_item_schema(); + $data_keys = array_keys( array_filter( $schema['properties'], [ $this, 'filter_writable_props' ] ) ); + + // Prepare variables for properties which need to be saved late (like status) or in a group (dates and payment data). + $status = ''; + $payment_method = ''; + $payment_meta = []; + $dates = []; + + // Both setting (set_status()) and updating (update_status()) are valid ways for requests to set a subscription's status. + $status_transition = 'set'; + + foreach ( $data_keys as $i => $key ) { + $value = $request[ $key ]; + + if ( is_null( $value ) ) { + continue; + } + + switch ( $key ) { + case 'parent_id': + $subscription->set_parent_id( $value ); + break; + case 'transition_status': + $status_transition = 'update'; + case 'status': + // This needs to be done later so status changes take into account other data like dates. + $status = $value; + break; + case 'billing': + case 'shipping': + $this->update_address( $subscription, $value, $key ); + break; + case 'start_date': + case 'trial_end_date': + case 'next_payment_date': + case 'cancelled_date': + case 'end_date': + // Group all the subscription date properties so they can be validated together. + $dates[ $key ] = $value; + break; + case 'payment_method': + $payment_method = $value; + break; + case 'payment_details': + // Format the value in a way payment gateways expect so it can be validated. + $payment_meta = $value; + break; + case 'line_items': + case 'shipping_lines': + case 'fee_lines': + if ( is_array( $value ) ) { + foreach ( $value as $item ) { + if ( is_array( $item ) ) { + if ( $this->item_is_null( $item ) || ( isset( $item['quantity'] ) && 0 === $item['quantity'] ) ) { + if ( isset( $item['id'] ) ) { + $subscription->remove_item( $item['id'] ); + } + } else { + $this->set_item( $subscription, $key, $item ); + } + } + } + } + break; + case 'meta_data': + if ( is_array( $value ) ) { + foreach ( $value as $meta ) { + $subscription->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' ); + } + } + break; + default: + if ( is_callable( [ $subscription, "set_{$key}" ] ) ) { + $subscription->{"set_{$key}"}( $value ); + } + break; + } + } + + if ( ! empty( $payment_method ) ) { + $this->update_payment_method( $subscription, $payment_method, $payment_meta ); + } + + if ( ! empty( $dates ) ) { + // If the start date is not set in the request when a subscription is being created, set its default to now. + if ( empty( $id ) && ! isset( $dates['start_date'] ) ) { + $dates['start_date'] = gmdate( 'Y-m-d H:i:s' ); + } + + try { + $subscription->update_dates( $dates ); + } catch ( Exception $e ) { + throw new WC_REST_Exception( 'woocommerce_rest_invalid_payment_data', sprintf( __( 'Subscription dates could not be set. Error message: %s', 'woocommerce-subscriptions' ), $e->getMessage() ), 400 ); + } + } + + if ( ! empty( $status ) ) { + if ( 'set' === $status_transition ) { + $subscription->set_status( $status ); + } else { + $subscription->update_status( $status ); + $request['status'] = $status; // Set the request status so parent::save_object() doesn't set it to the default 'pending' status. + } + } + + /** + * Filters an object before it is inserted via the REST API. + * + * The dynamic portion of the hook name, `$this->post_type`, + * refers to the object type slug. + * + * @param WC_Subscription $subscription The subscription object. + * @param WP_REST_Request $request Request object. + * @param bool $creating If is creating a new object. + */ + return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}_object", $subscription, $request, $creating ); + } + + /** + * Adds additional item schema information for subscription requests. + * + * @since 6.4.0 + * + * @return array + */ + public function get_item_schema() { + // If this is a request for a subscription's orders, return the subscription orders schema. + if ( $this->request instanceof WP_REST_Request && preg_match( "#/{$this->rest_base}/(?P[\d]+)/orders#", $this->request->get_route() ) ) { + return $this->get_subscription_orders_schema(); + } + + $schema = parent::get_item_schema(); + + // Base order schema overrides. + $schema['properties']['status']['description'] = __( 'Subscription status.', 'woocommerce-subscriptions' ); + $schema['properties']['status']['enum'] = $this->get_order_statuses(); + + $schema['properties']['created_via']['description'] = __( 'Where the subscription was created.', 'woocommerce-subscriptions' ); + $schema['properties']['currency']['description'] = __( 'Currency the subscription was created with, in ISO format.', 'woocommerce-subscriptions' ); + $schema['properties']['date_created']['description'] = __( "The date the subscription was created, in the site's timezone.", 'woocommerce-subscriptions' ); + $schema['properties']['date_created_gmt']['description'] = __( 'The date the subscription was created, as GMT.', 'woocommerce-subscriptions' ); + $schema['properties']['date_modified']['description'] = __( "The date the subscription was last modified, in the site's timezone.", 'woocommerce-subscriptions' ); + $schema['properties']['date_modified_gmt']['description'] = __( 'The date the subscription was last modified, as GMT.', 'woocommerce-subscriptions' ); + $schema['properties']['customer_id']['description'] = __( 'User ID who owns the subscription.', 'woocommerce-subscriptions' ); + + unset( $schema['properties']['transaction_id'] ); + unset( $schema['properties']['refunds'] ); + unset( $schema['properties']['set_paid'] ); + unset( $schema['properties']['cart_hash'] ); + + // Add subscription schema. + $schema['properties'] += [ + 'transition_status' => [ + 'description' => __( 'The status to transition a subscription to.', 'woocommerce-subscriptions' ), + 'type' => 'string', + 'context' => [ 'edit' ], + 'enum' => $this->get_order_statuses(), + ], + 'billing_interval' => [ + 'description' => __( 'The number of billing periods between subscription renewals.', 'woocommerce-subscriptions' ), + 'type' => 'integer', + 'context' => [ 'view', 'edit' ], + ], + 'billing_period' => [ + 'description' => __( 'Billing period for the subscription.', 'woocommerce-subscriptions' ), + 'type' => 'string', + 'enum' => array_keys( wcs_get_subscription_period_strings() ), + 'context' => [ 'view', 'edit' ], + ], + 'payment_details' => [ + 'description' => __( 'Subscription payment details.', 'woocommerce-subscriptions' ), + 'type' => 'object', + 'context' => [ 'edit' ], + 'properties' => [ + 'post_meta' => [ + 'description' => __( 'Payment method meta and token in a post_meta_key: token format.', 'woocommerce-subscriptions' ), + 'type' => 'object', + 'context' => [ 'edit' ], + ], + 'user_meta' => [ + 'description' => __( 'Payment method meta and token in a user_meta_key : token format.', 'woocommerce-subscriptions' ), + 'type' => 'object', + 'context' => [ 'view' ], + ], + ], + ], + 'start_date' => [ + 'description' => __( "The subscription's start date, as GMT.", 'woocommerce-subscriptions' ), + 'type' => 'date-time', + 'context' => [ 'view', 'edit' ], + ], + 'trial_end_date' => [ + 'description' => __( "The subscription's trial end date, as GMT.", 'woocommerce-subscriptions' ), + 'type' => 'date-time', + 'context' => [ 'view', 'edit' ], + ], + 'next_payment_date' => [ + 'description' => __( "The subscription's next payment date, as GMT.", 'woocommerce-subscriptions' ), + 'type' => 'date-time', + 'context' => [ 'view', 'edit' ], + ], + 'cancelled_date' => [ + 'description' => __( "The subscription's cancelled date, as GMT.", 'woocommerce-subscriptions' ), + 'type' => 'date-time', + 'context' => [ 'view', 'edit' ], + ], + 'end_date' => [ + 'description' => __( "The subscription's end date, as GMT.", 'woocommerce-subscriptions' ), + 'type' => 'date-time', + 'context' => [ 'view', 'edit' ], + ], + ]; + + return $schema; + } + + /** + * Get the query params for collections. + * + * @since 6.4.0 + * + * @return array + */ + public function get_collection_params() { + $params = parent::get_collection_params(); + + // Override the base order status description to be subscription specific. + $params['status']['description'] = __( 'Limit result set to subscriptions which have specific statuses.', 'woocommerce-subscriptions' ); + return $params; + } + + /** + * Gets an object's links to include in the response. + * + * Because this class also handles retrieving order data, we need + * to edit the links generated so the correct REST API href is included + * when its generated for an order. + * + * @since 6.4.0 + * + * @param WC_Data $object Object data. + * @param WP_REST_Request $request Request object. + * + * @return array Links for the given object. + */ + protected function prepare_links( $object, $request ) { + $links = parent::prepare_links( $object, $request ); + + if ( isset( $links['self'] ) && wcs_is_order( $object ) ) { + $links['self'] = [ + 'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, 'orders', $object->get_id() ) ), + ]; + } + + return $links; + } + + /** + * Updates a subscription's payment method and meta from data provided in a REST API request. + * + * @since 6.4.0 + * + * @param WC_Subscription $subscription The subscription to update. + * @param string $payment_method The ID of the payment method to set. + * @param array $payment_meta The payment method meta. + * + * @return void + */ + public function update_payment_method( $subscription, $payment_method, $payment_meta ) { + $updating_subscription = (bool) $subscription->get_id(); + + try { + if ( $updating_subscription && ! array_key_exists( $payment_method, WCS_Change_Payment_Method_Admin::get_valid_payment_methods( $subscription ) ) ) { + // translators: placeholder is the payment method ID. + throw new Exception( sprintf( __( 'The %s payment gateway does not support admin changing the payment method.', 'woocommerce-subscriptions' ), $payment_method ) ); + } + + // Format the payment meta in the way payment gateways expect so it can be validated. + $payment_method_meta = []; + + foreach ( $payment_meta as $table => $meta ) { + foreach ( $meta as $meta_key => $value ) { + $payment_method_meta[ $table ][ $meta_key ] = [ 'value' => $value ]; + } + } + + $subscription->set_payment_method( $payment_method, $payment_method_meta ); + } catch ( Exception $e ) { + $subscription->set_payment_method(); + $subscription->save(); + // translators: 1$: gateway id, 2$: error message + throw new WC_REST_Exception( 'woocommerce_rest_invalid_payment_data', sprintf( __( 'Subscription payment method could not be set to %1$s with error message: %2$s', 'woocommerce-subscriptions' ), $payment_method, $e->getMessage() ), 400 ); + } + } + + /** + * Creates subscriptions from an order. + * + * @since 6.4.0 + * + * @param WP_REST_Request $request + * + * @return array Subscriptions created from the order. + */ + public function create_subscriptions_from_order( $request ) { + $order_id = absint( $request->get_param( 'id' ) ); + + if ( empty( $order_id ) ) { + return new WP_Error( 'woocommerce_rest_order_invalid_id', __( 'Invalid order ID.', 'woocommerce-subscriptions' ), [ 'status' => 404 ] ); + } + + $order = wc_get_order( $order_id ); + + if ( ! $order || ! wcs_is_order( $order ) ) { + return new WP_Error( 'woocommerce_rest_order_invalid_id', sprintf( __( 'Failed to load order object with the ID %d.', 'woocommerce-subscriptions' ), $order_id ), [ 'status' => 404 ] ); + } + + if ( ! $order->get_customer_id() ) { + return new WP_Error( 'woocommerce_rest_invalid_order', __( 'Order does not have a customer associated with it. Subscriptions require a customer.', 'woocommerce-subscriptions' ), [ 'status' => 404 ] ); + } + + if ( wcs_order_contains_subscription( $order, 'any' ) ) { + return new WP_Error( 'woocommerce_rest_invalid_order', __( 'Order already has subscriptions associated with it.', 'woocommerce-subscriptions' ), [ 'status' => 404 ] ); + } + + $subscription_groups = []; + $subscriptions = []; + + // Group the order items into subscription groups. + foreach ( $order->get_items() as $item ) { + $product = $item->get_product(); + + if ( ! WC_Subscriptions_Product::is_subscription( $product ) ) { + continue; + } + + $subscription_groups[ wcs_get_subscription_item_grouping_key( $item ) ][] = $item; + } + + // Return a 204 if there are no subscriptions to be created. + if ( empty( $subscription_groups ) ) { + $response = rest_ensure_response( $subscriptions ); + $response->set_status( 204 ); + return $response; + } + + /** + * Start creating any subscriptions start transaction if available. + * + * To ensure data integrity, if any subscription fails to be created, the transaction will be rolled back. This will enable + * the client to resubmit the request without having to worry about duplicate subscriptions being created. + */ + $transaction = new WCS_SQL_Transaction(); + $transaction->start(); + + try { + // Create subscriptions. + foreach ( $subscription_groups as $items ) { + // Get the first item in the group to use as the base for the subscription. + $product = $items[0]->get_product(); + $start_date = wcs_get_datetime_utc_string( $order->get_date_created( 'edit' ) ); + $subscription = wcs_create_subscription( [ + 'order_id' => $order_id, + 'created_via' => 'rest-api', + 'start_date' => $start_date, + 'status' => $order->is_paid() ? 'active' : 'pending', + 'billing_period' => WC_Subscriptions_Product::get_period( $product ), + 'billing_interval' => WC_Subscriptions_Product::get_interval( $product ), + 'customer_note' => $order->get_customer_note(), + ] ); + + if ( is_wp_error( $subscription ) ) { + throw new Exception( $subscription->get_error_message() ); + } + + wcs_copy_order_address( $order, $subscription ); + + $subscription->update_dates( + [ + 'trial_end' => WC_Subscriptions_Product::get_trial_expiration_date( $product, $start_date ), + 'next_payment' => WC_Subscriptions_Product::get_first_renewal_payment_date( $product, $start_date ), + 'end' => WC_Subscriptions_Product::get_expiration_date( $product, $start_date ), + ] + ); + + $subscription->set_payment_method( $order->get_payment_method() ); + + wcs_copy_order_meta( $order, $subscription, 'subscription' ); + + // Add items. + $subscription_needs_shipping = false; + foreach ( $items as $item ) { + // Create order line item. + $item_id = wc_add_order_item( + $subscription->get_id(), + [ + 'order_item_name' => $item->get_name(), + 'order_item_type' => $item->get_type(), + ] + ); + + $subscription_item = $subscription->get_item( $item_id ); + + wcs_copy_order_item( $item, $subscription_item ); + + // Don't include sign-up fees or $0 trial periods when setting the subscriptions item totals. + wcs_set_recurring_item_total( $subscription_item ); + + $subscription_item->save(); + + // Check if this subscription will need shipping. + if ( ! $subscription_needs_shipping ) { + $product = $item->get_product(); + + if ( $product ) { + $subscription_needs_shipping = $product->needs_shipping() && ! WC_Subscriptions_Product::needs_one_time_shipping( $product ); + } + } + } + + // Add coupons. + foreach ( $order->get_coupons() as $coupon_item ) { + $coupon = new WC_Coupon( $coupon_item->get_code() ); + + try { + // validate_subscription_coupon_for_order will throw an exception if the coupon cannot be applied to the subscription. + WC_Subscriptions_Coupon::validate_subscription_coupon_for_order( true, $coupon, $subscription ); + + $subscription->apply_coupon( $coupon->get_code() ); + } catch ( Exception $e ) { + // Do nothing. The coupon will not be applied to the subscription. + } + } + + // Add shipping. + if ( $subscription_needs_shipping ) { + foreach ( $order->get_shipping_methods() as $shipping_item ) { + $rate = new WC_Shipping_Rate( $shipping_item->get_method_id(), $shipping_item->get_method_title(), $shipping_item->get_total(), $shipping_item->get_taxes(), $shipping_item->get_instance_id() ); + + $item = new WC_Order_Item_Shipping(); + $item->set_order_id( $subscription->get_id() ); + $item->set_shipping_rate( $rate ); + + $subscription->add_item( $item ); + } + } + + // Add fees. + foreach ( $order->get_fees() as $fee_item ) { + if ( ! apply_filters( 'wcs_should_copy_fee_item_to_subscription', true, $fee_item, $subscription, $order ) ) { + continue; + } + + $item = new WC_Order_Item_Fee(); + $item->set_props( + [ + 'name' => $fee_item->get_name(), + 'tax_class' => $fee_item->get_tax_class(), + 'amount' => $fee_item->get_amount(), + 'total' => $fee_item->get_total(), + 'total_tax' => $fee_item->get_total_tax(), + 'taxes' => $fee_item->get_taxes(), + ] + ); + + $subscription->add_item( $item ); + } + + + /* + * Fetch a fresh instance of the subscription because the current instance has an empty line item cache generated before we had copied the line items. + * Fetching a new instance will ensure the line items are used when calculating totals. + */ + $subscription = wcs_get_subscription( $subscription->get_id() ); + $subscription->calculate_totals(); + + /** + * Fires after a single subscription is created or updated via the REST API. + * + * @param WC_Subscription $object Inserted subscription. + * @param WP_REST_Request $request Request object. + * @param boolean $creating True when creating object, false when updating. + */ + do_action( "woocommerce_rest_insert_{$this->post_type}_object", $subscription, $request, true ); + + $response = $this->prepare_object_for_response( wcs_get_subscription( $subscription->get_id() ), $request ); + $subscriptions[] = $this->prepare_response_for_collection( $response ); + } + } catch ( Exception $e ) { + $transaction->rollback(); + return new WP_Error( 'woocommerce_rest_invalid_subscription_data', $e->getMessage(), [ 'status' => 404 ] ); + } + + // If we got here, the subscription was created without problems + $transaction->commit(); + + return rest_ensure_response( $subscriptions ); + } + + /** + * Subscriptions statuses schema, conforming to JSON Schema. + * + * @return array + */ + public function get_statuses_schema() { + $schema = [ + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'shop_subscription statuses', // Use a unique title for the schema so that CLI commands aren't overridden. + 'type' => 'object', + 'properties' => [], + ]; + + // Add the subscription statuses to the schema. + foreach ( wcs_get_subscription_statuses() as $status => $status_name ) { + $schema['properties'][ $status ] = [ + 'type' => 'string', + 'description' => sprintf( __( 'Subscription status: %s', 'woocommerce-subscription' ), $status_name ), + ]; + } + + return $schema; + } + + /** + * Subscriptions orders schema, conforming to JSON Schema. + * + * @return array + */ + public function get_subscription_orders_schema() { + $schema = parent::get_item_schema(); // Fetch the order schema. + $schema['title'] = 'shop_subscription orders'; // Use a unique title for the schema so that CLI commands aren't overridden. + $schema['properties']['order_type'] = [ + 'type' => 'string', + 'description' => __( 'The type of order related to the subscription.', 'woocommerce-subscriptions' ), + ]; + + return $schema; + } + + /** + * Subscriptions schema, conforming to JSON Schema. + * + * @return array + */ + public function create_subscriptions_from_order_schema() { + $schema = $this->get_public_item_schema(); + $schema['title'] = 'shop_order subscriptions'; // Use a unique title for the schema so that CLI commands aren't overridden and we can target this endpoint specifically. + + return $schema; + } +} diff --git a/includes/class-wc-subscriptions-cli.php b/includes/class-wc-subscriptions-cli.php new file mode 100644 index 0000000..b79fe51 --- /dev/null +++ b/includes/class-wc-subscriptions-cli.php @@ -0,0 +1,31 @@ + true, 'wcs_auth' => true, 'wcs_upgrade_notice_manager' => true, + 'wc_subscriptions_cli' => true, ); /** diff --git a/languages/woocommerce-subscriptions.pot b/languages/woocommerce-subscriptions.pot index cb8b257..88c13ce 100644 --- a/languages/woocommerce-subscriptions.pot +++ b/languages/woocommerce-subscriptions.pot @@ -2,16 +2,16 @@ # This file is distributed under the same license as the WooCommerce Subscriptions plugin. msgid "" msgstr "" -"Project-Id-Version: WooCommerce Subscriptions 6.3.2\n" +"Project-Id-Version: WooCommerce Subscriptions 6.4.0\n" "Report-Msgid-Bugs-To: https://woocommerce.com/contact-us\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2024-05-24T04:44:48+00:00\n" +"POT-Creation-Date: 2024-06-13T01:43:31+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"X-Generator: WP-CLI 2.10.0\n" +"X-Generator: WP-CLI 2.9.0\n" "X-Domain: woocommerce-subscriptions\n" #. Plugin Name of the plugin @@ -673,56 +673,69 @@ msgid "Payment Gateway Feature Support." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:165 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:164 msgid "Invalid subscription ID." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:171 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:170 msgid "Failed to load subscription object with the ID %d." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:328 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:329 msgid "Subscription dates could not be set. Error message: %s" msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:364 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:371 msgid "Subscription status." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:367 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:374 msgid "Where the subscription was created." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:368 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:375 msgid "Currency the subscription was created with, in ISO format." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:369 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:376 msgid "The date the subscription was created, in the site's timezone." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:370 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:377 msgid "The date the subscription was created, as GMT." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:371 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:378 msgid "The date the subscription was last modified, in the site's timezone." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:372 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:379 msgid "The date the subscription was last modified, as GMT." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:373 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:380 msgid "User ID who owns the subscription." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:383 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:390 msgid "The status to transition a subscription to." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:389 #: includes/api/legacy/class-wc-rest-subscriptions-controller.php:350 #: includes/api/v1/class-wc-rest-subscriptions-v1-controller.php:506 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:396 #: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-extend-store-endpoint.php:179 #: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-extend-store-endpoint.php:453 msgid "The number of billing periods between subscription renewals." @@ -731,6 +744,7 @@ msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:394 #: includes/api/legacy/class-wc-rest-subscriptions-controller.php:355 #: includes/api/v1/class-wc-rest-subscriptions-v1-controller.php:511 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:401 #: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-extend-store-endpoint.php:172 #: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-extend-store-endpoint.php:446 msgid "Billing period for the subscription." @@ -739,43 +753,53 @@ msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:400 #: includes/api/legacy/class-wc-rest-subscriptions-controller.php:361 #: includes/api/v1/class-wc-rest-subscriptions-v1-controller.php:517 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:407 msgid "Subscription payment details." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:405 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:412 msgid "Payment method meta and token in a post_meta_key: token format." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:410 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:417 msgid "Payment method meta and token in a user_meta_key : token format." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:417 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:424 msgid "The subscription's start date, as GMT." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:422 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:429 msgid "The subscription's trial end date, as GMT." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:427 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:434 msgid "The subscription's next payment date, as GMT." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:432 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:439 msgid "The subscription's cancelled date, as GMT." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:437 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:444 msgid "The subscription's end date, as GMT." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:456 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:464 msgid "Limit result set to subscriptions which have specific statuses." msgstr "" #. translators: placeholder is the payment method ID. #: includes/api/class-wc-rest-subscriptions-controller.php:500 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:511 msgid "The %s payment gateway does not support admin changing the payment method." msgstr "" @@ -783,22 +807,27 @@ msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:517 #: includes/api/legacy/class-wc-rest-subscriptions-controller.php:336 #: includes/api/v1/class-wc-rest-subscriptions-v1-controller.php:405 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:528 msgid "Subscription payment method could not be set to %1$s with error message: %2$s" msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:531 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:545 msgid "Invalid order ID." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:537 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:551 msgid "Failed to load order object with the ID %d." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:541 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:555 msgid "Order does not have a customer associated with it. Subscriptions require a customer." msgstr "" #: includes/api/class-wc-rest-subscriptions-controller.php:545 +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:559 msgid "Order already has subscriptions associated with it." msgstr "" @@ -1002,6 +1031,10 @@ msgstr "" msgid "Meta value." msgstr "" +#: includes/api/v2/class-wc-rest-subscriptions-v2-controller.php:770 +msgid "The type of order related to the subscription." +msgstr "" + #. translators: 1$-2$: opening and closing tags, 3$-4$: link tags, takes to woocommerce plugin on wp.org, 5$-6$: opening and closing link tags, leads to plugins.php in admin #: includes/class-wc-subscriptions-dependency-manager.php:156 msgid "%1$sWooCommerce Subscriptions is inactive.%2$s The %3$sWooCommerce plugin%4$s must be active for WooCommerce Subscriptions to work. Please %5$sinstall & activate WooCommerce »%6$s" @@ -1013,24 +1046,24 @@ msgid "%1$sWooCommerce Subscriptions is inactive.%2$s This version of Subscripti msgstr "" #. translators: 1-2: opening/closing tags, 3: Subscriptions version. -#: includes/class-wc-subscriptions-plugin.php:58 +#: includes/class-wc-subscriptions-plugin.php:62 msgid "%1$sWarning!%2$s We can see the %1$sWooCommerce Subscriptions Early Renewal%2$s plugin is active. Version %3$s of %1$sWooCommerce Subscriptions%2$s comes with that plugin's functionality packaged into the core plugin. Please deactivate WooCommerce Subscriptions Early Renewal to avoid any conflicts." msgstr "" -#: includes/class-wc-subscriptions-plugin.php:62 +#: includes/class-wc-subscriptions-plugin.php:66 msgid "Installed Plugins" msgstr "" #. translators: $1-$2: opening and closing tags, $3-$4: opening and closing tags. -#: includes/class-wc-subscriptions-plugin.php:204 +#: includes/class-wc-subscriptions-plugin.php:208 msgid "%1$sWooCommerce Subscriptions Installed%2$s – %3$sYou're ready to start selling subscriptions!%4$s" msgstr "" -#: includes/class-wc-subscriptions-plugin.php:222 +#: includes/class-wc-subscriptions-plugin.php:226 msgid "Add a Subscription Product" msgstr "" -#: includes/class-wc-subscriptions-plugin.php:223 +#: includes/class-wc-subscriptions-plugin.php:227 #: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-core-plugin.php:548 #: vendor/woocommerce/subscriptions-core/includes/upgrades/templates/wcs-about-2-0.php:35 #: vendor/woocommerce/subscriptions-core/includes/upgrades/templates/wcs-about.php:34 @@ -1293,7 +1326,7 @@ msgstr "" #: vendor/woocommerce/subscriptions-core/includes/admin/meta-boxes/views/html-related-orders-row.php:17 #: vendor/woocommerce/subscriptions-core/includes/admin/meta-boxes/views/html-unknown-related-orders-row.php:18 #: vendor/woocommerce/subscriptions-core/includes/class-wc-subscriptions-renewal-order.php:170 -#: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:34 +#: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:35 #: vendor/woocommerce/subscriptions-core/templates/myaccount/related-orders.php:44 #: vendor/woocommerce/subscriptions-core/templates/myaccount/related-subscriptions.php:33 msgctxt "hash before order number" @@ -1889,46 +1922,6 @@ msgstr "" msgid "Want to renew early via the checkout? Click %shere.%s" msgstr "" -#: tests/unit/scheduler/scheduler.php:65 -#: vendor/woocommerce/subscriptions-core/wcs-functions.php:290 -msgctxt "table heading" -msgid "Start Date" -msgstr "" - -#: tests/unit/scheduler/scheduler.php:66 -#: vendor/woocommerce/subscriptions-core/wcs-functions.php:291 -msgctxt "table heading" -msgid "Trial End" -msgstr "" - -#: tests/unit/scheduler/scheduler.php:67 -#: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:40 -#: vendor/woocommerce/subscriptions-core/wcs-functions.php:292 -msgctxt "table heading" -msgid "Next Payment" -msgstr "" - -#: tests/unit/scheduler/scheduler.php:68 -#: vendor/woocommerce/subscriptions-core/templates/emails/cancelled-subscription.php:23 -#: vendor/woocommerce/subscriptions-core/templates/emails/expired-subscription.php:23 -#: vendor/woocommerce/subscriptions-core/templates/emails/on-hold-subscription.php:23 -#: vendor/woocommerce/subscriptions-core/wcs-functions.php:293 -msgctxt "table heading" -msgid "Last Order Date" -msgstr "" - -#: tests/unit/scheduler/scheduler.php:69 -#: vendor/woocommerce/subscriptions-core/wcs-functions.php:294 -msgctxt "table heading" -msgid "Cancelled Date" -msgstr "" - -#: tests/unit/scheduler/scheduler.php:70 -#: vendor/woocommerce/subscriptions-core/wcs-functions.php:295 -msgctxt "table heading" -msgid "End Date" -msgstr "" - #. translators: 1: relation type, 2: list of valid relation types. #: vendor/woocommerce/subscriptions-core/includes/abstracts/abstract-wcs-related-order-store.php:148 msgid "Invalid relation type: %1$s. Order relationship type must be one of: %2$s." @@ -2435,7 +2428,7 @@ msgstr[1] "" #: vendor/woocommerce/subscriptions-core/includes/admin/class-wcs-admin-post-types.php:468 #: vendor/woocommerce/subscriptions-core/includes/admin/meta-boxes/views/html-related-orders-table.php:21 #: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:22 -#: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:37 +#: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:39 #: vendor/woocommerce/subscriptions-core/templates/myaccount/related-orders.php:24 #: vendor/woocommerce/subscriptions-core/templates/myaccount/related-orders.php:50 #: vendor/woocommerce/subscriptions-core/templates/myaccount/related-subscriptions.php:22 @@ -6414,6 +6407,14 @@ msgctxt "table headings in notification email" msgid "Price" msgstr "" +#: vendor/woocommerce/subscriptions-core/templates/emails/cancelled-subscription.php:23 +#: vendor/woocommerce/subscriptions-core/templates/emails/expired-subscription.php:23 +#: vendor/woocommerce/subscriptions-core/templates/emails/on-hold-subscription.php:23 +#: vendor/woocommerce/subscriptions-core/wcs-functions.php:293 +msgctxt "table heading" +msgid "Last Order Date" +msgstr "" + #: vendor/woocommerce/subscriptions-core/templates/emails/cancelled-subscription.php:24 msgctxt "table headings in notification email" msgid "End of Prepaid Term" @@ -6696,37 +6697,43 @@ msgstr "" msgid "ID" msgstr "" -#: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:46 +#: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:42 +#: vendor/woocommerce/subscriptions-core/wcs-functions.php:292 +msgctxt "table heading" +msgid "Next Payment" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:48 #: vendor/woocommerce/subscriptions-core/templates/myaccount/related-orders.php:53 #: vendor/woocommerce/subscriptions-core/templates/myaccount/related-subscriptions.php:42 msgctxt "Used in data attribute. Escaped" msgid "Total" msgstr "" -#: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:50 +#: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:52 #: vendor/woocommerce/subscriptions-core/templates/myaccount/related-orders.php:84 #: vendor/woocommerce/subscriptions-core/templates/myaccount/related-subscriptions.php:46 msgctxt "view a subscription" msgid "View" msgstr "" -#: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:61 +#: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:63 msgid "Previous" msgstr "" -#: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:65 +#: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:67 msgid "Next" msgstr "" -#: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:72 +#: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:74 msgid "You have reached the end of subscriptions. Go to the %sfirst page%s." msgstr "" -#: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:74 +#: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:76 msgid "You have no active subscriptions." msgstr "" -#: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:77 +#: vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php:79 msgid "Browse products" msgstr "" @@ -6875,6 +6882,26 @@ msgstr "" msgid "Can not get address type display name. Address type is not a string." msgstr "" +#: vendor/woocommerce/subscriptions-core/wcs-functions.php:290 +msgctxt "table heading" +msgid "Start Date" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/wcs-functions.php:291 +msgctxt "table heading" +msgid "Trial End" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/wcs-functions.php:294 +msgctxt "table heading" +msgid "Cancelled Date" +msgstr "" + +#: vendor/woocommerce/subscriptions-core/wcs-functions.php:295 +msgctxt "table heading" +msgid "End Date" +msgstr "" + #: vendor/woocommerce/subscriptions-core/wcs-functions.php:330 msgid "Date type is not a string." msgstr "" @@ -6916,6 +6943,10 @@ msgstr[1] "" msgid "%1$s %2$s %3$d %4$s" msgstr "" +#: vendor/woocommerce/subscriptions-core/build/index.js:6 +msgid "Free" +msgstr "" + #. translators: %s selected shipping rate (ex: flat rate) #: vendor/woocommerce/subscriptions-core/build/index.js:7 msgid "via %s" diff --git a/vendor/autoload.php b/vendor/autoload.php index 6ca52cd..05985c7 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -2,6 +2,24 @@ // autoload.php @generated by Composer +if (PHP_VERSION_ID < 50600) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, $err); + } elseif (!headers_sent()) { + echo $err; + } + } + trigger_error( + $err, + E_USER_ERROR + ); +} + require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInit2a572b7cb1c49650721c1f7426949a4c::getLoader(); +return ComposerAutoloaderInit02567f250c7765c8ea2ed71a416c6653::getLoader(); diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php index afef3fa..a72151c 100644 --- a/vendor/composer/ClassLoader.php +++ b/vendor/composer/ClassLoader.php @@ -42,6 +42,9 @@ namespace Composer\Autoload; */ class ClassLoader { + /** @var \Closure(string):void */ + private static $includeFile; + /** @var ?string */ private $vendorDir; @@ -106,6 +109,7 @@ class ClassLoader public function __construct($vendorDir = null) { $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); } /** @@ -425,7 +429,8 @@ class ClassLoader public function loadClass($class) { if ($file = $this->findFile($class)) { - includeFile($file); + $includeFile = self::$includeFile; + $includeFile($file); return true; } @@ -555,18 +560,26 @@ class ClassLoader return false; } -} -/** - * Scope isolated include. - * - * Prevents access to $this/self from included files. - * - * @param string $file - * @return void - * @private - */ -function includeFile($file) -{ - include $file; + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } } diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php index 41bc143..51e734a 100644 --- a/vendor/composer/InstalledVersions.php +++ b/vendor/composer/InstalledVersions.php @@ -28,7 +28,7 @@ class InstalledVersions { /** * @var mixed[]|null - * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}|array{}|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; @@ -39,7 +39,7 @@ class InstalledVersions /** * @var array[] - * @psalm-var array}> + * @psalm-var array}> */ private static $installedByVendor = array(); @@ -98,7 +98,7 @@ class InstalledVersions { foreach (self::getInstalled() as $installed) { if (isset($installed['versions'][$packageName])) { - return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; } } @@ -119,7 +119,7 @@ class InstalledVersions */ public static function satisfies(VersionParser $parser, $packageName, $constraint) { - $constraint = $parser->parseConstraints($constraint); + $constraint = $parser->parseConstraints((string) $constraint); $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); return $provided->matches($constraint); @@ -243,7 +243,7 @@ class InstalledVersions /** * @return array - * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string} + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} */ public static function getRootPackage() { @@ -257,7 +257,7 @@ class InstalledVersions * * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. * @return array[] - * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} */ public static function getRawData() { @@ -280,7 +280,7 @@ class InstalledVersions * Returns the raw data of all installed.php which are currently loaded for custom implementations * * @return array[] - * @psalm-return list}> + * @psalm-return list}> */ public static function getAllRawData() { @@ -303,7 +303,7 @@ class InstalledVersions * @param array[] $data A vendor/composer/installed.php data set * @return void * - * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} $data + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data */ public static function reload($data) { @@ -313,7 +313,7 @@ class InstalledVersions /** * @return array[] - * @psalm-return list}> + * @psalm-return list}> */ private static function getInstalled() { @@ -328,7 +328,9 @@ class InstalledVersions if (isset(self::$installedByVendor[$vendorDir])) { $installed[] = self::$installedByVendor[$vendorDir]; } elseif (is_file($vendorDir.'/composer/installed.php')) { - $installed[] = self::$installedByVendor[$vendorDir] = require $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]; } @@ -340,12 +342,17 @@ class InstalledVersions // only require the installed.php file if this file is loaded from its dumped location, // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 if (substr(__DIR__, -8, 1) !== 'C') { - self::$installed = require __DIR__ . '/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 __DIR__ . '/installed.php'; + self::$installed = $required; } else { self::$installed = array(); } } - $installed[] = self::$installed; + + if (self::$installed !== array()) { + $installed[] = self::$installed; + } return $installed; } diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index d72907e..ebc175f 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -2,7 +2,7 @@ // autoload_real.php @generated by Composer -class ComposerAutoloaderInit2a572b7cb1c49650721c1f7426949a4c +class ComposerAutoloaderInit02567f250c7765c8ea2ed71a416c6653 { private static $loader; @@ -24,12 +24,12 @@ class ComposerAutoloaderInit2a572b7cb1c49650721c1f7426949a4c require __DIR__ . '/platform_check.php'; - spl_autoload_register(array('ComposerAutoloaderInit2a572b7cb1c49650721c1f7426949a4c', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInit02567f250c7765c8ea2ed71a416c6653', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); - spl_autoload_unregister(array('ComposerAutoloaderInit2a572b7cb1c49650721c1f7426949a4c', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInit02567f250c7765c8ea2ed71a416c6653', 'loadClassLoader')); require __DIR__ . '/autoload_static.php'; - \Composer\Autoload\ComposerStaticInit2a572b7cb1c49650721c1f7426949a4c::getInitializer($loader)(); + call_user_func(\Composer\Autoload\ComposerStaticInit02567f250c7765c8ea2ed71a416c6653::getInitializer($loader)); $loader->register(true); diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 6bddaa1..77bc462 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -4,7 +4,7 @@ namespace Composer\Autoload; -class ComposerStaticInit2a572b7cb1c49650721c1f7426949a4c +class ComposerStaticInit02567f250c7765c8ea2ed71a416c6653 { public static $prefixLengthsPsr4 = array ( 'C' => @@ -129,9 +129,9 @@ class ComposerStaticInit2a572b7cb1c49650721c1f7426949a4c public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInit2a572b7cb1c49650721c1f7426949a4c::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInit2a572b7cb1c49650721c1f7426949a4c::$prefixDirsPsr4; - $loader->classMap = ComposerStaticInit2a572b7cb1c49650721c1f7426949a4c::$classMap; + $loader->prefixLengthsPsr4 = ComposerStaticInit02567f250c7765c8ea2ed71a416c6653::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit02567f250c7765c8ea2ed71a416c6653::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit02567f250c7765c8ea2ed71a416c6653::$classMap; }, null, ClassLoader::class); } diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index c768227..08d12b3 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -156,17 +156,17 @@ }, { "name": "woocommerce/subscriptions-core", - "version": "7.1.1", - "version_normalized": "7.1.1.0", + "version": "7.2.0", + "version_normalized": "7.2.0.0", "source": { "type": "git", "url": "https://github.com/Automattic/woocommerce-subscriptions-core.git", - "reference": "5ba92addca2996576d39376907157243b061175e" + "reference": "9f42efee04a8e8966c2fda22c25ee1793fe9839e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/woocommerce-subscriptions-core/zipball/5ba92addca2996576d39376907157243b061175e", - "reference": "5ba92addca2996576d39376907157243b061175e", + "url": "https://api.github.com/repos/Automattic/woocommerce-subscriptions-core/zipball/9f42efee04a8e8966c2fda22c25ee1793fe9839e", + "reference": "9f42efee04a8e8966c2fda22c25ee1793fe9839e", "shasum": "" }, "require": { @@ -179,7 +179,7 @@ "woocommerce/woocommerce-sniffs": "0.1.0", "yoast/phpunit-polyfills": "1.1.0" }, - "time": "2024-05-09T07:21:43+00:00", + "time": "2024-06-13T01:14:44+00:00", "type": "wordpress-plugin", "extra": { "phpcodesniffer-search-depth": 2 @@ -209,7 +209,7 @@ "description": "Sell products and services with recurring payments in your WooCommerce Store.", "homepage": "https://github.com/Automattic/woocommerce-subscriptions-core", "support": { - "source": "https://github.com/Automattic/woocommerce-subscriptions-core/tree/7.1.1", + "source": "https://github.com/Automattic/woocommerce-subscriptions-core/tree/7.2.0", "issues": "https://github.com/Automattic/woocommerce-subscriptions-core/issues" }, "install-path": "../woocommerce/subscriptions-core" diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 03b69c7..d0db352 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -1,22 +1,22 @@ array( - 'pretty_version' => 'dev-release/6.3.2', - 'version' => 'dev-release/6.3.2', + 'name' => 'woocommerce/woocommerce-subscriptions', + 'pretty_version' => 'dev-release/6.4.0', + 'version' => 'dev-release/6.4.0', + 'reference' => '4f5a7362b75b8b0f747cc271320809adba667cb7', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => '1d9382ce5301ebe8df492ad898947a5d4534b5b1', - 'name' => 'woocommerce/woocommerce-subscriptions', 'dev' => false, ), 'versions' => array( 'composer/installers' => array( 'pretty_version' => 'v1.12.0', 'version' => '1.12.0.0', + 'reference' => 'd20a64ed3c94748397ff5973488761b22f6d3f19', 'type' => 'composer-plugin', 'install_path' => __DIR__ . '/./installers', 'aliases' => array(), - 'reference' => 'd20a64ed3c94748397ff5973488761b22f6d3f19', 'dev_requirement' => false, ), 'roundcube/plugin-installer' => array( @@ -32,21 +32,21 @@ ), ), 'woocommerce/subscriptions-core' => array( - 'pretty_version' => '7.1.1', - 'version' => '7.1.1.0', + 'pretty_version' => '7.2.0', + 'version' => '7.2.0.0', + 'reference' => '9f42efee04a8e8966c2fda22c25ee1793fe9839e', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../woocommerce/subscriptions-core', 'aliases' => array(), - 'reference' => '5ba92addca2996576d39376907157243b061175e', 'dev_requirement' => false, ), 'woocommerce/woocommerce-subscriptions' => array( - 'pretty_version' => 'dev-release/6.3.2', - 'version' => 'dev-release/6.3.2', + 'pretty_version' => 'dev-release/6.4.0', + 'version' => 'dev-release/6.4.0', + 'reference' => '4f5a7362b75b8b0f747cc271320809adba667cb7', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => '1d9382ce5301ebe8df492ad898947a5d4534b5b1', 'dev_requirement' => false, ), ), diff --git a/vendor/woocommerce/subscriptions-core/build/index.asset.php b/vendor/woocommerce/subscriptions-core/build/index.asset.php index 64a433c..430588b 100644 --- a/vendor/woocommerce/subscriptions-core/build/index.asset.php +++ b/vendor/woocommerce/subscriptions-core/build/index.asset.php @@ -1 +1 @@ - array('wc-blocks-checkout', 'wc-price-format', 'wc-settings', 'wp-element', 'wp-i18n', 'wp-plugins'), 'version' => 'b74144542a686437daa06d7e57f2567f'); \ No newline at end of file + array('wc-blocks-checkout', 'wc-price-format', 'wc-settings', 'wp-element', 'wp-i18n', 'wp-plugins'), 'version' => '7e9abfc482fe190e29673f828b33f482'); \ No newline at end of file diff --git a/vendor/woocommerce/subscriptions-core/build/index.js b/vendor/woocommerce/subscriptions-core/build/index.js index 99bcdc3..3c60d82 100644 --- a/vendor/woocommerce/subscriptions-core/build/index.js +++ b/vendor/woocommerce/subscriptions-core/build/index.js @@ -1,9 +1,9 @@ -!function(e){var t={};function n(r){if(t[r])return t[r].exports;var c=t[r]={i:r,l:!1,exports:{}};return e[r].call(c.exports,c,c.exports,n),c.l=!0,c.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var c in e)n.d(r,c,function(t){return e[t]}.bind(null,c));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=10)}([function(e,t){e.exports=window.wp.i18n},function(e,t){e.exports=window.wp.element},function(e,t){e.exports=window.wc.blocksCheckout},function(e,t,n){var r=n(7);e.exports=function(e,t){if(null==e)return{};var n,c,i=r(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(c=0;c=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}},function(e,t){e.exports=window.wc.wcSettings},function(e,t){e.exports=window.wp.plugins},function(e,t){e.exports=window.wc.priceFormat},function(e,t){e.exports=function(e,t){if(null==e)return{};var n,r,c={},i=Object.keys(e);for(r=0;r=0||(c[n]=e[n]);return c}},function(e,t,n){},function(e,t,n){},function(e,t,n){"use strict";n.r(t);var r=n(1),c=n(5),i=n(2),o=n(3),s=n.n(o),l=n(0),a=n(6),u=n(4);function p(e){return{day:Object(l._nx)("day","days",e,"Used in recurring totals section in Cart. 2+ will need plural, 1 will need singular.","woocommerce-subscriptions"),week:Object(l._nx)("week","weeks",e,"Used in recurring totals section in Cart. 2+ will need plural, 1 will need singular.","woocommerce-subscriptions"),month:Object(l._nx)("month","months",e,"Used in recurring totals section in Cart. 2+ will need plural, 1 will need singular.","woocommerce-subscriptions"),year:Object(l._nx)("year","years",e,"Used in recurring totals section in Cart. 2+ will need plural, 1 will need singular.","woocommerce-subscriptions")}}function b(e,t,n){var r=e.billing_interval,c=e.billing_period,i=p(r)[c];switch(t=t.trim(),r){case 1:return"".concat(n," ").concat(t," ").concat(i);default:return Object(l.sprintf)( +!function(e){var t={};function n(r){if(t[r])return t[r].exports;var c=t[r]={i:r,l:!1,exports:{}};return e[r].call(c.exports,c,c.exports,n),c.l=!0,c.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var c in e)n.d(r,c,function(t){return e[t]}.bind(null,c));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=10)}([function(e,t){e.exports=window.wp.i18n},function(e,t){e.exports=window.wp.element},function(e,t){e.exports=window.wc.blocksCheckout},function(e,t){e.exports=window.wc.wcSettings},function(e,t,n){var r=n(7);e.exports=function(e,t){if(null==e)return{};var n,c,i=r(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(c=0;c=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}},function(e,t){e.exports=window.wp.plugins},function(e,t){e.exports=window.wc.priceFormat},function(e,t){e.exports=function(e,t){if(null==e)return{};var n,r,c={},i=Object.keys(e);for(r=0;r=0||(c[n]=e[n]);return c}},function(e,t,n){},function(e,t,n){},function(e,t,n){"use strict";n.r(t);var r=n(1),c=n(5),i=n(2),o=n(4),s=n.n(o),l=n(0),a=n(6),u=n(3);function p(e){return{day:Object(l._nx)("day","days",e,"Used in recurring totals section in Cart. 2+ will need plural, 1 will need singular.","woocommerce-subscriptions"),week:Object(l._nx)("week","weeks",e,"Used in recurring totals section in Cart. 2+ will need plural, 1 will need singular.","woocommerce-subscriptions"),month:Object(l._nx)("month","months",e,"Used in recurring totals section in Cart. 2+ will need plural, 1 will need singular.","woocommerce-subscriptions"),year:Object(l._nx)("year","years",e,"Used in recurring totals section in Cart. 2+ will need plural, 1 will need singular.","woocommerce-subscriptions")}}function b(e,t,n){var r=e.billing_interval,c=e.billing_period,i=p(r)[c];switch(t=t.trim(),r){case 1:return"".concat(n," ").concat(t," ").concat(i);default:return Object(l.sprintf)( /* * translators: %1$s is the price of the product. %2$s is the separator used e.g "every" or "/", * %3$d is the length, %4$s is week, month, year */ -Object(l.__)("%1$s %2$s %3$d %4$s","woocommerce-subscriptions"),n,t,r,i)}}function m(e){return e.subscriptionLength===e.billingInterval}n(8);var g=Object(u.getSetting)("displayCartPricesIncludingTax",!1),d=function(e){var t=e.currency,n=e.values,c=n.total_discount,o=n.total_discount_tax,s=parseInt(c,10);if(!s)return null;var a=parseInt(o,10),u=g?s+a:s;return Object(r.createElement)(i.TotalsItem,{className:"wc-block-components-totals-discount",currency:t,label:Object(l.__)("Discount","woocommerce-subscriptions"),value:-1*u})},_=function(e){var t=e.values,n=e.currency,c=e.selectedRate,o=e.needsShipping,s=e.calculatedShipping;if(!o||!s)return null;var a=g?parseInt(t.total_shipping,10)+parseInt(t.total_shipping_tax,10):parseInt(t.total_shipping,10);return Object(r.createElement)(i.TotalsItem,{value:a,label:Object(l.__)("Shipping","woocommerce-subscriptions"),currency:n,description:!!c&&Object(l.sprintf)(// translators: %s selected shipping rate (ex: flat rate) +Object(l.__)("%1$s %2$s %3$d %4$s","woocommerce-subscriptions"),n,t,r,i)}}function m(e){return e.subscriptionLength===e.billingInterval}n(8);var g=Object(u.getSetting)("displayCartPricesIncludingTax",!1),d=function(e){var t=e.currency,n=e.values,c=n.total_discount,o=n.total_discount_tax,s=parseInt(c,10);if(!s)return null;var a=parseInt(o,10),u=g?s+a:s;return Object(r.createElement)(i.TotalsItem,{className:"wc-block-components-totals-discount",currency:t,label:Object(l.__)("Discount","woocommerce-subscriptions"),value:-1*u})},_=function(e){var t=e.values,n=e.currency,c=e.selectedRate,o=e.needsShipping,s=e.calculatedShipping;if(!o||!s)return null;var a=g?parseInt(t.total_shipping,10)+parseInt(t.total_shipping_tax,10):parseInt(t.total_shipping,10),p=0===a&&Object(u.isWcVersion)("9.0",">=")?Object(r.createElement)("strong",null,Object(l.__)("Free","woocommerce-subscriptions")):a;return Object(r.createElement)(i.TotalsItem,{value:p,label:Object(l.__)("Shipping","woocommerce-subscriptions"),currency:n,description:!!c&&Object(l.sprintf)(// translators: %s selected shipping rate (ex: flat rate) Object(l.__)("via %s","woocommerce-subscriptions"),c)})},f=function(e){var t=e.nextPaymentDate,n=e.subscriptionLength,c=e.billingPeriod,i=e.billingInterval,o=function(e){var t=e.subscriptionLength,n=e.billingPeriod,r=p(t);return Object(l.sprintf)("For %1$d %2$s",t,r[n],"woocommerce-subscriptions")}({subscriptionLength:n,billingPeriod:c}),s=m({subscriptionLength:n,billingInterval:i})?Object(l.sprintf)( /* Translators: %1$s is a date. */ Object(l.__)("Due: %1$s","woocommerce-subscriptions"),t):Object(l.sprintf)( @@ -14,7 +14,7 @@ Object(l.__)("Recurring total every 2nd %1$s","woocommerce-subscriptions"),n);ca /* Translators: %1$s is week, month, year */ Object(l.__)("Recurring total every 3rd %1$s","woocommerce-subscriptions"),n);default:return Object(l.sprintf)( /* Translators: %1$d is number of weeks, months, days, years. %2$s is week, month, year */ -Object(l.__)("Recurring total every %1$dth %2$s","woocommerce-subscriptions"),t,n)}}({billingInterval:n,billingPeriod:c});return Object(r.createElement)(i.TotalsItem,{className:"wcs-recurring-totals-panel__title",currency:t,label:u,value:a,description:Object(r.createElement)(f,{nextPaymentDate:o,subscriptionLength:s,billingInterval:n,billingPeriod:c})})},j=function(e){var t,n,c,o=e.subscription,s=e.needsShipping,u=e.calculatedShipping,p=o.totals,b=o.billing_interval,m=o.billing_period,f=o.next_payment_date,j=o.subscription_length,w=o.shipping_rates;if(!f)return null;var v=null==w||null===(t=w[0])||void 0===t||null===(n=t.shipping_rates)||void 0===n||null===(c=n.find((function(e){return e.selected})))||void 0===c?void 0:c.name,y=Object(a.getCurrencyFromPriceResponse)(p);return Object(r.createElement)("div",{className:"wcs-recurring-totals-panel"},Object(r.createElement)(O,{billingInterval:b,billingPeriod:m,nextPaymentDate:f,subscriptionLength:j,totals:parseInt(p.total_price,10),currency:y}),Object(r.createElement)(i.Panel,{className:"wcs-recurring-totals-panel__details",initialOpen:!1,title:Object(l.__)("Details","woocommerce-subscriptions")},Object(r.createElement)(i.TotalsWrapper,null,Object(r.createElement)(i.Subtotal,{currency:y,values:p}),Object(r.createElement)(d,{currency:y,values:p})),Object(r.createElement)(i.TotalsWrapper,null,Object(r.createElement)(_,{currency:y,needsShipping:s,calculatedShipping:u,values:p,selectedRate:v})),!g&&Object(r.createElement)(i.TotalsWrapper,null,Object(r.createElement)(i.TotalsTaxes,{currency:y,values:p})),Object(r.createElement)(i.TotalsWrapper,null,Object(r.createElement)(i.TotalsItem,{className:"wcs-recurring-totals-panel__details-total",currency:y,label:Object(l.__)("Total","woocommerce-subscriptions"),value:parseInt(p.total_price,10)}))))},w=function(e){var t=e.extensions,n=e.cart,c=t.subscriptions,i=n.cartNeedsShipping,o=n.cartHasCalculatedShipping;return c&&0!==c.length?c.map((function(e){var t=e.key,n=s()(e,["key"]);return Object(r.createElement)(j,{subscription:n,needsShipping:i,calculatedShipping:o,key:t})})):null},v=function(e){var t=e.extensions,n=e.collapsible,c=e.collapse,i=e.showItems,o=e.noResultsMessage,l=e.renderOption,a=e.components,u=e.context,p=t.subscriptions,b=void 0===p?[]:p,m=a.ShippingRatesControlPackage,g=Object(r.useMemo)((function(){return Object.values(b).map((function(e){return e.shipping_rates})).filter(Boolean).flat()}),[b]),d=Object(r.useMemo)((function(){return 1get_related_order_metadata( $subscription, $relation_type ); - return $meta_data ? maybe_unserialize( $meta_data->meta_value ) : ''; + $related_orders = $meta_data ? maybe_unserialize( $meta_data->meta_value ) : ''; + + return is_array( $related_orders ) ? $related_orders : ''; } /** diff --git a/vendor/woocommerce/subscriptions-core/includes/wcs-order-functions.php b/vendor/woocommerce/subscriptions-core/includes/wcs-order-functions.php index 0cdd197..32e26b4 100644 --- a/vendor/woocommerce/subscriptions-core/includes/wcs-order-functions.php +++ b/vendor/woocommerce/subscriptions-core/includes/wcs-order-functions.php @@ -1003,3 +1003,55 @@ function wcs_order_contains_early_renewal( $order ) { function wcs_get_subscription_item_grouping_key( $item, $renewal_time = '' ) { return apply_filters( 'woocommerce_subscriptions_item_grouping_key', wcs_get_subscription_grouping_key( $item->get_product(), $renewal_time ), $item ); } + +/** + * Sets the order item total to its recurring product price. + * + * This function takes an order item and checks if its totals have been modified to account for free trials or sign-up fees (i.e. parent orders). + * If the totals have been adjusted, the function sets the item's total back to their recurring total. + * + * 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. + */ +function wcs_set_recurring_item_total( &$item ) { + $product = $item->get_product(); + + if ( ! $product || ! WC_Subscriptions_Product::is_subscription( $product ) ) { + return; + } + + $sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee( $product ); + $sign_up_fee = is_numeric( $sign_up_fee ) ? (float) $sign_up_fee : 0; + $trial_length = WC_Subscriptions_Product::get_trial_length( $product ); + + $recurring_price = (float) $product->get_price(); + $initial_price = $trial_length > 0 ? $sign_up_fee : $recurring_price + $sign_up_fee; + $initial_total = wc_get_price_excluding_tax( + $product, + [ + 'qty' => $item->get_quantity(), + 'price' => $initial_price, + ] + ); + + // Check if a custom item total was set on the order. If so, don't override it. + if ( (float) $item->get_subtotal() !== $initial_total ) { + return; + } + + $recurring_total = wc_get_price_excluding_tax( + $product, + [ + 'qty' => $item->get_quantity(), + 'price' => $recurring_price, + ] + ); + + $item->set_props( + [ + 'subtotal' => $recurring_total, + 'total' => $recurring_total, + ] + ); +} diff --git a/vendor/woocommerce/subscriptions-core/includes/wcs-renewal-functions.php b/vendor/woocommerce/subscriptions-core/includes/wcs-renewal-functions.php index f63b450..057f995 100644 --- a/vendor/woocommerce/subscriptions-core/includes/wcs-renewal-functions.php +++ b/vendor/woocommerce/subscriptions-core/includes/wcs-renewal-functions.php @@ -20,8 +20,8 @@ if ( ! defined( 'ABSPATH' ) ) { * This method simply creates an order with the same post meta, order items and order item meta as the subscription * passed to it. * - * @param int | WC_Subscription $subscription Post ID of a 'shop_subscription' post, or instance of a WC_Subscription object - * @return WC_Order | WP_Error + * @param int|WC_Subscription $subscription Post ID of a 'shop_subscription' post, or instance of a WC_Subscription object + * @return WC_Order|WP_Error * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ function wcs_create_renewal_order( $subscription ) { @@ -41,7 +41,8 @@ function wcs_create_renewal_order( $subscription ) { /** * Check if a given order is a subscription renewal order. * - * @param WC_Order|int $order The WC_Order object or ID of a WC_Order order. + * @param WC_Order|int $order The WC_Order object or ID of a WC_Order order. + * @return bool Whether the order contains renewal. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ function wcs_order_contains_renewal( $order ) { @@ -64,8 +65,7 @@ function wcs_order_contains_renewal( $order ) { /** * Checks the cart to see if it contains a subscription product renewal. * - * @param bool | Array The cart item containing the renewal, else false. - * @return string + * @return bool|array The cart item containing the renewal, else false. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ function wcs_cart_contains_renewal() { @@ -111,6 +111,7 @@ function wcs_cart_contains_failed_renewal_order_payment() { * Get the subscription/s to which a resubscribe order relates. * * @param WC_Order|int $order The WC_Order object or ID of a WC_Order order. + * @return WC_Subscription[] Subscription details in post_id => WC_Subscription form. * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ function wcs_get_subscriptions_for_renewal_order( $order ) { @@ -146,7 +147,7 @@ function wcs_get_last_non_early_renewal_order( $subscription ) { * Checks if manual renewals are required - automatic renewals are disabled. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v4.0.0 - * @return bool Weather manual renewal are required. + * @return bool Whether manual renewal is required. */ function wcs_is_manual_renewal_required() { return class_exists( 'WCS_Manual_Renewal_Manager' ) ? WCS_Manual_Renewal_Manager::is_manual_renewal_required() : false; @@ -156,7 +157,7 @@ function wcs_is_manual_renewal_required() { * Checks if manual renewals are enabled. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v4.0.0 - * @return bool Weather manual renewal are enabled. + * @return bool Whether manual renewal is enabled. */ function wcs_is_manual_renewal_enabled() { return class_exists( 'WCS_Manual_Renewal_Manager' ) ? WCS_Manual_Renewal_Manager::is_manual_renewal_enabled() : false; diff --git a/vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php b/vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php index cb5a827..4864b4f 100644 --- a/vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php +++ b/vendor/woocommerce/subscriptions-core/templates/myaccount/my-subscriptions.php @@ -4,7 +4,7 @@ * * @author Prospress * @category WooCommerce Subscriptions/Templates - * @version 1.0.0 - Migrated from WooCommerce Subscriptions v2.6.4 + * @version 7.2.0 - Migrated from WooCommerce Subscriptions v2.6.4 */ if ( ! defined( 'ABSPATH' ) ) { @@ -31,7 +31,9 @@ if ( ! defined( 'ABSPATH' ) ) { $subscription ) : ?> - get_order_number() ) ); ?> + + get_order_number() ) ); ?> + diff --git a/vendor/woocommerce/subscriptions-core/woocommerce-subscriptions-core.php b/vendor/woocommerce/subscriptions-core/woocommerce-subscriptions-core.php index df15150..569549b 100644 --- a/vendor/woocommerce/subscriptions-core/woocommerce-subscriptions-core.php +++ b/vendor/woocommerce/subscriptions-core/woocommerce-subscriptions-core.php @@ -6,5 +6,5 @@ * Author: Automattic * Author URI: https://woocommerce.com/ * Requires WP: 5.6 - * Version: 7.1.1 + * Version: 7.2.0 */ diff --git a/woocommerce-subscriptions.php b/woocommerce-subscriptions.php index f80ffc7..f711922 100644 --- a/woocommerce-subscriptions.php +++ b/woocommerce-subscriptions.php @@ -5,10 +5,10 @@ * Description: Sell products and services with recurring payments in your WooCommerce Store. * Author: WooCommerce * Author URI: https://woocommerce.com/ - * Version: 6.3.2 + * Version: 6.4.0 * Requires Plugins: woocommerce * - * WC requires at least: 7.9.0 + * WC requires at least: 8.7.1 * WC tested up to: 8.9.0 * Woo: 27147:6115e6d7e297b623a169fdcf5728b224 * @@ -78,7 +78,7 @@ class WC_Subscriptions { public static $plugin_file = __FILE__; /** @var string */ - public static $version = '6.3.2'; // WRCS: DEFINED_VERSION. + public static $version = '6.4.0'; // WRCS: DEFINED_VERSION. /** @var string */ public static $wc_minimum_supported_version = '7.7';