/**
  * Migrate subscriptions out of order item meta and into post/post meta tables for their own post type.
  *
  * @since 2.0
  */
 public static function upgrade_subscriptions($batch_size)
 {
     global $wpdb;
     WC()->payment_gateways();
     WCS_Upgrade_Logger::add(sprintf('Upgrading batch of %d subscriptions', $batch_size));
     $upgraded_subscription_count = 0;
     $execution_time_start = time();
     foreach (self::get_subscriptions($batch_size) as $original_order_item_id => $old_subscription) {
         try {
             $old_subscription = WCS_Repair_2_0::maybe_repair_subscription($old_subscription, $original_order_item_id);
             // don't allow data to be half upgraded on a subscription (but we need the subscription to be the atomic level, not the whole batch, to ensure that resubscribe and switch updates in the same batch have the new subscription available)
             $wpdb->query('START TRANSACTION');
             WCS_Upgrade_Logger::add(sprintf('For order %d: beginning subscription upgrade process', $old_subscription['order_id']));
             $original_order = wc_get_order($old_subscription['order_id']);
             // If we're still in a prepaid term, the new subscription has the new pending cancellation status
             if ('cancelled' == $old_subscription['status'] && false != wc_next_scheduled_action('scheduled_subscription_end_of_prepaid_term', array('user_id' => $old_subscription['user_id'], 'subscription_key' => $old_subscription['subscription_key']))) {
                 $subscription_status = 'pending-cancel';
             } elseif ('trash' == $old_subscription['status']) {
                 $subscription_status = 'cancelled';
                 // we'll trash it properly after migrating it
             } else {
                 $subscription_status = $old_subscription['status'];
             }
             // Create a new subscription for this user
             $new_subscription = wcs_create_subscription(array('status' => $subscription_status, 'order_id' => $old_subscription['order_id'], 'customer_id' => $old_subscription['user_id'], 'start_date' => $old_subscription['start_date'], 'customer_note' => !empty($original_order->customer_note) ? $original_order->customer_note : '', 'billing_period' => $old_subscription['period'], 'billing_interval' => $old_subscription['interval'], 'order_version' => !empty($original_order->order_version) ? $original_order->order_version : ''));
             if (!is_wp_error($new_subscription)) {
                 WCS_Upgrade_Logger::add(sprintf('For subscription %d: post created', $new_subscription->id));
                 // Set the order to be manual
                 if (isset($original_order->wcs_requires_manual_renewal) && 'true' == $original_order->wcs_requires_manual_renewal) {
                     $new_subscription->update_manual(true);
                 }
                 // Add the line item from the order
                 $subscription_item_id = self::add_product($new_subscription, $original_order_item_id, wcs_get_order_item($original_order_item_id, $original_order));
                 // Add the line item from the order
                 self::migrate_download_permissions($new_subscription, $subscription_item_id, $original_order);
                 // Set dates on the subscription
                 self::migrate_dates($new_subscription, $old_subscription);
                 // Set some meta from order meta
                 self::migrate_post_meta($new_subscription->id, $original_order);
                 // Copy over order notes which are now logged on the subscription
                 self::migrate_order_notes($new_subscription->id, $original_order->id);
                 // Migrate recurring tax, shipping and coupon line items to be plain line items on the subscription
                 self::migrate_order_items($new_subscription->id, $original_order->id);
                 // Update renewal orders to link via post meta key instead of post_parent column
                 self::migrate_renewal_orders($new_subscription->id, $original_order->id);
                 // Make sure the resubscribe meta data is migrated to use the new subscription ID + meta key
                 self::migrate_resubscribe_orders($new_subscription->id, $original_order->id);
                 // If the order for this subscription contains a switch, make sure the switch meta data is migrated to use the new subscription ID + meta key
                 self::migrate_switch_meta($new_subscription, $original_order, $subscription_item_id);
                 // If the subscription was in the trash, now that we've set on the meta on it, we need to trash it
                 if ('trash' == $old_subscription['status']) {
                     wp_trash_post($new_subscription->id);
                 }
                 WCS_Upgrade_Logger::add(sprintf('For subscription %d: upgrade complete', $new_subscription->id));
             } else {
                 self::deprecate_item_meta($original_order_item_id);
                 self::deprecate_post_meta($old_subscription['order_id']);
                 WCS_Upgrade_Logger::add(sprintf('!!! For order %d: unable to create subscription. Error: %s', $old_subscription['order_id'], $new_subscription->get_error_message()));
             }
             // If we got here, the batch was upgraded without problems
             $wpdb->query('COMMIT');
             $upgraded_subscription_count++;
         } catch (Exception $e) {
             // We can still recover from here.
             if (422 == $e->getCode()) {
                 self::deprecate_item_meta($original_order_item_id);
                 self::deprecate_post_meta($old_subscription['order_id']);
                 WCS_Upgrade_Logger::add(sprintf('!!! For order %d: unable to create subscription. Error: %s', $old_subscription['order_id'], $e->getMessage()));
                 $wpdb->query('COMMIT');
                 $upgraded_subscription_count++;
             } else {
                 // we couldn't upgrade this subscription don't commit the query
                 $wpdb->query('ROLLBACK');
                 throw $e;
             }
         }
         if ($upgraded_subscription_count >= $batch_size || array_key_exists('WPENGINE_ACCOUNT', $_SERVER) && time() - $execution_time_start > 50) {
             break;
         }
     }
     // Double check we actually have no more subscriptions to upgrade as sometimes they can fall through the cracks
     if ($upgraded_subscription_count < $batch_size && $upgraded_subscription_count > 0 && !array_key_exists('WPENGINE_ACCOUNT', $_SERVER)) {
         $upgraded_subscription_count += self::upgrade_subscriptions($batch_size);
     }
     WCS_Upgrade_Logger::add(sprintf('Upgraded batch of %d subscriptions', $upgraded_subscription_count));
     return $upgraded_subscription_count;
 }
 /**
  * Create a new subscription from a cart item on checkout.
  *
  * The function doesn't validate whether the cart item is a subscription product, meaning it can be used for any cart item,
  * but the item will need a `subscription_period` and `subscription_period_interval` value set on it, at a minimum.
  *
  * @param WC_Order $order
  * @param WC_Cart $cart
  * @since 2.0
  */
 public static function create_subscription($order, $cart)
 {
     global $wpdb;
     try {
         // Start transaction if available
         $wpdb->query('START TRANSACTION');
         // Set the recurring line totals on the subscription
         $variation_id = wcs_cart_pluck($cart, 'variation_id');
         $product_id = empty($variation_id) ? wcs_cart_pluck($cart, 'product_id') : $variation_id;
         // We need to use the $order->order_date value because the post_date_gmt isn't always set
         $order_date_gmt = get_gmt_from_date($order->order_date);
         $subscription = wcs_create_subscription(array('start_date' => $cart->start_date, 'order_id' => $order->id, 'customer_id' => $order->get_user_id(), 'billing_period' => wcs_cart_pluck($cart, 'subscription_period'), 'billing_interval' => wcs_cart_pluck($cart, 'subscription_period_interval'), 'customer_note' => $order->customer_note));
         if (is_wp_error($subscription)) {
             throw new Exception($subscription->get_error_message());
         }
         // Set the subscription's billing and shipping address
         $subscription = wcs_copy_order_address($order, $subscription);
         $subscription->update_dates(array('trial_end' => $cart->trial_end_date, 'next_payment' => $cart->next_payment_date, 'end' => $cart->end_date));
         // Store trial period for PayPal
         if (wcs_cart_pluck($cart, 'subscription_trial_length') > 0) {
             update_post_meta($subscription->id, '_trial_period', wcs_cart_pluck($cart, 'subscription_trial_period'));
         }
         // Set the payment method on the subscription
         $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
         if ($cart->needs_payment() && isset($available_gateways[$order->payment_method])) {
             $subscription->set_payment_method($available_gateways[$order->payment_method]);
         }
         if (!$cart->needs_payment() || 'yes' == get_option(WC_Subscriptions_Admin::$option_prefix . '_turn_off_automatic_payments', 'no')) {
             $subscription->update_manual('true');
         } elseif (!isset($available_gateways[$order->payment_method]) || !$available_gateways[$order->payment_method]->supports('subscriptions')) {
             $subscription->update_manual('true');
         }
         wcs_copy_order_meta($order, $subscription, 'subscription');
         // Store the line items
         foreach ($cart->get_cart() as $cart_item_key => $cart_item) {
             $item_id = self::add_cart_item($subscription, $cart_item, $cart_item_key);
         }
         // Store fees (although no fees recur by default, extensions may add them)
         foreach ($cart->get_fees() as $fee_key => $fee) {
             $item_id = $subscription->add_fee($fee);
             if (!$item_id) {
                 // translators: placeholder is an internal error number
                 throw new Exception(sprintf(__('Error %d: Unable to create subscription. Please try again.', 'woocommerce-subscriptions'), 403));
             }
             // Allow plugins to add order item meta to fees
             do_action('woocommerce_add_order_fee_meta', $order->id, $item_id, $fee, $fee_key);
         }
         self::add_shipping($subscription, $cart);
         // Store tax rows
         foreach (array_keys($cart->taxes + $cart->shipping_taxes) as $tax_rate_id) {
             if ($tax_rate_id && !$subscription->add_tax($tax_rate_id, $cart->get_tax_amount($tax_rate_id), $cart->get_shipping_tax_amount($tax_rate_id)) && apply_filters('woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated') !== $tax_rate_id) {
                 // translators: placeholder is an internal error number
                 throw new Exception(sprintf(__('Error %d: Unable to add tax to subscription. Please try again.', 'woocommerce-subscriptions'), 405));
             }
         }
         // Store coupons
         foreach ($cart->get_coupons() as $code => $coupon) {
             if (!$subscription->add_coupon($code, $cart->get_coupon_discount_amount($code), $cart->get_coupon_discount_tax_amount($code))) {
                 // translators: placeholder is an internal error number
                 throw new Exception(sprintf(__('Error %d: Unable to create order. Please try again.', 'woocommerce-subscriptions'), 406));
             }
         }
         // Set the recurring totals on the subscription
         $subscription->set_total($cart->shipping_total, 'shipping');
         $subscription->set_total($cart->get_cart_discount_total(), 'cart_discount');
         $subscription->set_total($cart->get_cart_discount_tax_total(), 'cart_discount_tax');
         $subscription->set_total($cart->tax_total, 'tax');
         $subscription->set_total($cart->shipping_tax_total, 'shipping_tax');
         $subscription->set_total($cart->total);
         // If we got here, the subscription was created without problems
         $wpdb->query('COMMIT');
     } catch (Exception $e) {
         // There was an error adding the subscription
         $wpdb->query('ROLLBACK');
         return new WP_Error('checkout-error', $e->getMessage());
     }
     return $subscription;
 }
 /**
  * Uses the details of an order to create a pending subscription on the customers account
  * for a subscription product, as specified with $product_id.
  *
  * @param int|WC_Order $order The order ID or WC_Order object to create the subscription from.
  * @param int $product_id The ID of the subscription product on the order, if a variation, it must be the variation's ID.
  * @param array $args An array of name => value pairs to customise the details of the subscription, including:
  * 			'start_date' A MySQL formatted date/time string on which the subscription should start, in UTC timezone
  * 			'expiry_date' A MySQL formatted date/time string on which the subscription should expire, in UTC timezone
  * @since 1.1
  */
 public static function create_pending_subscription_for_order($order, $product_id, $args = array())
 {
     _deprecated_function(__METHOD__, '2.0', 'wcs_create_subscription()');
     if (!is_object($order)) {
         $order = new WC_Order($order);
     }
     if (!WC_Subscriptions_Product::is_subscription($product_id)) {
         return;
     }
     $args = wp_parse_args($args, array('start_date' => get_gmt_from_date($order->order_date), 'expiry_date' => ''));
     $billing_period = WC_Subscriptions_Product::get_period($product_id);
     $billing_interval = WC_Subscriptions_Product::get_interval($product_id);
     // Support passing timestamps
     $args['start_date'] = is_numeric($args['start_date']) ? date('Y-m-d H:i:s', $args['start_date']) : $args['start_date'];
     $product = wc_get_product($product_id);
     // Check if there is already a subscription for this product and order
     $subscriptions = wcs_get_subscriptions(array('order_id' => $order->id, 'product_id' => $product_id));
     if (!empty($subscriptions)) {
         $subscription = array_pop($subscriptions);
         // Make sure the subscription is pending and start date is set correctly
         wp_update_post(array('ID' => $subscription->id, 'post_status' => 'wc-' . apply_filters('woocommerce_default_subscription_status', 'pending'), 'post_date' => get_date_from_gmt($args['start_date'])));
     } else {
         $subscription = wcs_create_subscription(array('start_date' => get_date_from_gmt($args['start_date']), 'order_id' => $order->id, 'customer_id' => $order->get_user_id(), 'billing_period' => $billing_period, 'billing_interval' => $billing_interval, 'customer_note' => $order->customer_note));
         if (is_wp_error($subscription)) {
             throw new Exception(__('Error: Unable to create subscription. Please try again.', 'woocommerce-subscriptions'));
         }
         $item_id = $subscription->add_product($product, 1, array('variation' => method_exists($product, 'get_variation_attributes') ? $product->get_variation_attributes() : array(), 'totals' => array('subtotal' => $product->get_price(), 'subtotal_tax' => 0, 'total' => $product->get_price(), 'tax' => 0, 'tax_data' => array('subtotal' => array(), 'total' => array()))));
         if (!$item_id) {
             throw new Exception(__('Error: Unable to add product to created subscription. Please try again.', 'woocommerce-subscriptions'));
         }
     }
     // Make sure some of the meta is copied form the order rather than the store's defaults
     update_post_meta($subscription->id, '_order_currency', $order->order_currency);
     update_post_meta($subscription->id, '_prices_include_tax', $order->prices_include_tax);
     // Adding a new subscription so set the expiry date/time from the order date
     if (!empty($args['expiry_date'])) {
         if (is_numeric($args['expiry_date'])) {
             $args['expiry_date'] = date('Y-m-d H:i:s', $args['expiry_date']);
         }
         $expiration = $args['expiry_date'];
     } else {
         $expiration = WC_Subscriptions_Product::get_expiration_date($product_id, $args['start_date']);
     }
     // Adding a new subscription so set the expiry date/time from the order date
     $trial_expiration = WC_Subscriptions_Product::get_trial_expiration_date($product_id, $args['start_date']);
     $dates_to_update = array();
     if ($trial_expiration > 0) {
         $dates_to_update['trial_end'] = $trial_expiration;
     }
     if ($expiration > 0) {
         $dates_to_update['end'] = $expiration;
     }
     if (!empty($dates_to_update)) {
         $subscription->update_dates($dates_to_update);
     }
     // Set the recurring totals on the subscription
     $subscription->set_total(0, 'tax');
     $subscription->set_total($product->get_price(), 'total');
     $subscription->add_order_note(__('Pending subscription created.', 'woocommerce-subscriptions'));
     do_action('pending_subscription_created_for_order', $order, $product_id);
 }
 /**
  * Override WC_API_Order::create_base_order() to create a subscription
  * instead of a WC_Order when calling WC_API_Order::create_order().
  *
  * @since 2.0
  * @param $array
  * @return WC_Subscription
  */
 protected function create_base_order($args, $data)
 {
     $args['order_id'] = !empty($data['order_id']) ? $data['order_id'] : '';
     $args['billing_interval'] = !empty($data['billing_interval']) ? $data['billing_interval'] : '';
     $args['billing_period'] = !empty($data['billing_period']) ? $data['billing_period'] : '';
     return wcs_create_subscription($args);
 }