id === $order_screen_id && wcs_order_contains_subscription( $post_or_order_object, 'any' ) ) { add_meta_box( 'subscription_renewal_orders', __( 'Related Orders', 'woocommerce-subscriptions' ), 'WCS_Meta_Box_Related_Orders::output', $order_screen_id, 'normal', 'low' ); } // On HPOS environments we need to remove and readd the line items meta box so it appears after the subscription data. if ( wcs_is_custom_order_tables_usage_enabled() ) { self::reorder_subscription_line_items_meta_box(); } } /** * Removes the core Order Data meta box as we add our own Subscription Data meta box */ public function remove_meta_boxes() { remove_meta_box( 'woocommerce-order-data', 'shop_subscription', 'normal' ); } /** * Don't save some order related meta boxes. * * @see woocommerce_process_shop_order_meta * * @param int $order_id * @param WC_Order $order */ public function remove_meta_box_save( $order_id, $order ) { if ( wcs_is_subscription( $order_id ) ) { remove_action( 'woocommerce_process_shop_order_meta', 'WC_Meta_Box_Order_Data::save', 40 ); } } /** * Print admin styles/scripts */ public function enqueue_styles_scripts() { global $theorder; // Get admin screen ID. $screen = get_current_screen(); $screen_id = isset( $screen->id ) ? $screen->id : ''; // Get the script version. $ver = WC_Subscriptions_Core_Plugin::instance()->get_library_version(); if ( wcs_get_page_screen_id( 'shop_subscription' ) === $screen_id ) { // If global $theorder is empty, fallback to using the global post object. if ( empty( $theorder ) && ! empty( $GLOBALS['post']->ID ) ) { $subscription = wcs_get_subscription( $GLOBALS['post']->ID ); // If we have a subscription, set it as the global $theorder. if ( $subscription ) { $theorder = $subscription; } } else { $subscription = $theorder; } if ( ! wcs_is_subscription( $subscription ) ) { return; } wp_register_script( 'jstz', WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory_url( 'assets/js/admin/jstz.min.js' ), [], $ver, false ); wp_register_script( 'momentjs', WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory_url( 'assets/js/admin/moment.min.js' ), [], $ver, false ); wp_enqueue_script( 'wcs-admin-meta-boxes-subscription', WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory_url( 'assets/js/admin/meta-boxes-subscription.js' ), array( 'wc-admin-meta-boxes', 'jstz', 'momentjs' ), $ver, false ); wp_localize_script( 'wcs-admin-meta-boxes-subscription', 'wcs_admin_meta_boxes', apply_filters( 'woocommerce_subscriptions_admin_meta_boxes_script_parameters', array( 'i18n_start_date_notice' => __( 'Please enter a start date in the past.', 'woocommerce-subscriptions' ), 'i18n_past_date_notice' => WCS_Staging::is_duplicate_site() ? __( 'Please enter a date at least 2 minutes into the future.', 'woocommerce-subscriptions' ) : __( 'Please enter a date at least one hour into the future.', 'woocommerce-subscriptions' ), 'i18n_next_payment_start_notice' => __( 'Please enter a date after the trial end.', 'woocommerce-subscriptions' ), 'i18n_next_payment_trial_notice' => __( 'Please enter a date after the start date.', 'woocommerce-subscriptions' ), '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' ), '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' ), 'get_customer_orders_nonce' => wp_create_nonce( 'get-customer-orders' ), 'is_duplicate_site' => WCS_Staging::is_duplicate_site(), ) ) ); } elseif ( 'shop_order' === $screen_id ) { wp_enqueue_script( 'wcs-admin-meta-boxes-order', WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory_url( 'assets/js/admin/wcs-meta-boxes-order.js' ), [], $ver, false ); wp_localize_script( 'wcs-admin-meta-boxes-order', 'wcs_admin_order_meta_boxes', array( 'retry_renewal_payment_action_warning' => __( "Are you sure you want to retry payment for this renewal order?\n\nThis will attempt to charge the customer and send renewal order emails (if emails are enabled).", 'woocommerce-subscriptions' ), ) ); } elseif ( in_array( $screen_id, array( 'shop_coupon', 'edit-shop_coupon' ), true ) ) { wp_enqueue_script( 'wcs-admin-coupon-meta-boxes', WC_Subscriptions_Core_Plugin::instance()->get_subscriptions_core_directory_url( 'assets/js/admin/meta-boxes-coupon.js' ), array( 'jquery', 'wc-admin-meta-boxes' ), $ver, false ); } } /** * Adds actions to the admin edit subscriptions page, if the subscription hasn't ended and the payment method supports them. * * @param array $actions An array of available actions * @return array An array of updated actions * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ public static function add_subscription_actions( $actions ) { global $theorder; if ( wcs_is_subscription( $theorder ) ) { if ( ! wcs_is_woocommerce_pre( '3.2' ) ) { unset( $actions['send_order_details'], $actions['send_order_details_admin'] ); } if ( ! $theorder->has_status( wcs_get_subscription_ended_statuses() ) ) { if ( $theorder->payment_method_supports( 'subscription_date_changes' ) && $theorder->has_status( 'active' ) ) { $actions['wcs_process_renewal'] = esc_html__( 'Process renewal', 'woocommerce-subscriptions' ); } if ( count( $theorder->get_related_orders() ) > 0 ) { $actions['wcs_create_pending_renewal'] = esc_html__( 'Create pending renewal order', 'woocommerce-subscriptions' ); } else { $actions['wcs_create_pending_parent'] = esc_html__( 'Create pending parent order', 'woocommerce-subscriptions' ); } } } elseif ( self::can_renewal_order_be_retried( $theorder ) ) { $actions['wcs_retry_renewal_payment'] = esc_html__( 'Retry Renewal Payment', 'woocommerce-subscriptions' ); } return $actions; } /** * Handles the action request to process a renewal order. * * @param array $subscription * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ public static function process_renewal_action_request( $subscription ) { $subscription->add_order_note( __( 'Process renewal order action requested by admin.', 'woocommerce-subscriptions' ), false, true ); do_action( 'woocommerce_scheduled_subscription_payment', $subscription->get_id() ); } /** * Handles the action request to create a pending renewal order. * * @param WC_Subscription $subscription */ public static function create_pending_renewal_action_request( $subscription ) { $subscription->add_order_note( __( 'Create pending renewal order requested by admin action.', 'woocommerce-subscriptions' ), false, true ); try { $subscription->update_status( 'on-hold' ); } catch ( Exception $e ) { wcs_add_admin_notice( __( 'Pending renewal order was not created, as it was not possible to update the subscription status.', 'woocommerce-subscriptions' ), 'error' ); return; } $renewal_order = wcs_create_renewal_order( $subscription ); if ( is_wp_error( $renewal_order ) ) { self::notify( $subscription, 'error', esc_html__( 'Creation of the pending renewal order failed.', 'woocommerce-subscriptions' ) . ' ' . $renewal_order->get_error_message() ); return; } if ( ! $subscription->is_manual() ) { $renewal_url = $renewal_order->get_edit_order_url(); try { // We need to pass the payment gateway instance to be compatible with WC < 3.0, only WC 3.0+ supports passing the string name. $renewal_order->set_payment_method( wc_get_payment_gateway_by_order( $subscription ) ); if ( is_callable( array( $renewal_order, 'save' ) ) ) { // WC 3.0+ $renewal_order->save(); } wcs_add_admin_notice( sprintf( /* Translators: %1$s opening link tag, %2$s closing link tag. */ esc_html__( 'A pending %1$srenewal order%2$s was successfully created!', 'woocommerce-subscriptions' ), '', '' ), 'success' ); } catch ( WC_Data_Exception $e ) { self::notify( $subscription, 'error', sprintf( /* Translators: %1$s opening link tag, %2$s closing link tag. */ esc_html__( 'A %1$spending renewal order%2$s was successfully created, but there was a problem setting the payment method. Please review the order.', 'woocommerce-subscriptions' ), '', '' ) ); } } } /** * Handles the action request to create a pending parent order. * * @param array $subscription * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.3 */ public static function create_pending_parent_action_request( $subscription ) { if ( ! $subscription->has_status( array( 'pending', 'on-hold' ) ) ) { $subscription->update_status( 'on-hold' ); } $parent_order = wcs_create_order_from_subscription( $subscription, 'parent' ); $subscription->set_parent_id( wcs_get_objects_property( $parent_order, 'id' ) ); $subscription->save(); if ( ! $subscription->is_manual() ) { $parent_order->set_payment_method( wc_get_payment_gateway_by_order( $subscription ) ); // We need to pass the payment gateway instance to be compatible with WC < 3.0, only WC 3.0+ supports passing the string name if ( is_callable( array( $parent_order, 'save' ) ) ) { // WC 3.0+ $parent_order->save(); } } wc_maybe_reduce_stock_levels( $parent_order ); $subscription->add_order_note( __( 'Create pending parent order requested by admin action.', 'woocommerce-subscriptions' ), false, true ); } /** * Removes order related emails from the available actions. * * @param array $available_emails * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.0 */ public static function remove_order_email_actions( $email_actions ) { global $theorder; if ( wcs_is_subscription( $theorder ) ) { $email_actions = array(); } return $email_actions; } /** * Process the action request to retry renewal payment for failed renewal orders. * * @param WC_Order $order * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.1 */ public static function process_retry_renewal_payment_action_request( $order ) { if ( self::can_renewal_order_be_retried( $order ) ) { // init payment gateways WC()->payment_gateways(); $order->add_order_note( __( 'Retry renewal payment action requested by admin.', 'woocommerce-subscriptions' ), false, true ); do_action( 'woocommerce_scheduled_subscription_payment_' . wcs_get_objects_property( $order, 'payment_method' ), $order->get_total(), $order ); } } /** * Determines if a renewal order payment can be retried. A renewal order payment can only be retried when: * - Order is a renewal order * - Order status is failed * - Order payment method isn't empty * - Order total > 0 * - Subscription/s aren't manual * - Subscription payment method supports date changes * - Order payment method has_action('woocommerce_scheduled_subscription_payment_..') * * @param WC_Order $order * @return bool * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.1 */ private static function can_renewal_order_be_retried( $order ) { $can_be_retried = 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; $order_payment_gateway = wc_get_payment_gateway_by_order( $order ); $order_payment_gateway_supports = ( isset( $order_payment_gateway->id ) ) ? has_action( 'woocommerce_scheduled_subscription_payment_' . $order_payment_gateway->id ) : false; foreach ( wcs_get_subscriptions_for_renewal_order( $order ) as $subscription ) { $supports_date_changes = $subscription->payment_method_supports( 'subscription_date_changes' ); $is_automatic = ! $subscription->is_manual(); break; } $can_be_retried = $order_payment_gateway_supports && $supports_date_changes && $is_automatic; } return $can_be_retried; } /** * Disables stock management while adding items to a subscription via the edit subscription screen. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v3.0.6 * * @param string $manage_stock The default manage stock setting. * @return string Whether the stock should be managed. */ public static function override_stock_management( $manage_stock ) { // Override stock management while adding line items to a subscription via AJAX. if ( isset( $_POST['order_id'], $_REQUEST['security'] ) && wp_verify_nonce( wc_clean( wp_unslash( $_REQUEST['security'] ) ), 'order-item' ) && doing_action( 'wp_ajax_woocommerce_add_order_item' ) && wcs_is_subscription( absint( wp_unslash( $_POST['order_id'] ) ) ) ) { $manage_stock = 'no'; } return $manage_stock; } /** * Displays a checkbox allowing admin to lock in prices increases in the edit order line items meta box. * * This checkbox is only displayed if the following criteria is met: * - The order is unpaid. * - The order is a subscription parent order. Renewal orders already lock in the subscription recurring price. * - The order's currency matches the base store currency. * - The order contains a line item with a subtotal greater than the product's current live price. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v3.0.10 * * @param WC_Order $order The order being edited. */ public static function output_price_lock_html( $order ) { if ( ! $order->needs_payment() || ! wcs_order_contains_subscription( $order, 'parent' ) ) { return; } // If the order currency doesn't match the base currency we can't know if the order contains manually increased prices. if ( $order->get_currency() !== get_woocommerce_currency() ) { return; } $needs_price_lock = false; 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() ) { $needs_price_lock = true; break; } } if ( $needs_price_lock ) { $help_tip = __( "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.", 'woocommerce-subscriptions' ); printf( '