/**
  * Make sure the expiration date is calculated from the synced start date for products where the start date
  * will be synced.
  *
  * @param string $expiration_date MySQL formatted date on which the subscription is set to expire
  * @param mixed $product_id The product/post ID of the subscription
  * @param mixed $from_date A MySQL formatted date/time string from which to calculate the expiration date, or empty (default), which will use today's date/time.
  * @since 1.5
  */
 public static function recalculate_product_expiration_date($expiration_date, $product_id, $from_date)
 {
     if (self::is_product_synced($product_id) && ($subscription_length = WC_Subscriptions_Product::get_length($product_id)) > 0) {
         $subscription_period = WC_Subscriptions_Product::get_period($product_id);
         $first_payment_date = self::calculate_first_payment_date($product_id, 'timestamp');
         $expiration_date = date('Y-m-d H:i:s', wcs_add_time($subscription_length, $subscription_period, $first_payment_date));
     }
     return $expiration_date;
 }
 /**
  * '_subscription_end_date': if the subscription has a '_subscription_length' value and status of expired, the length can be used to
  * calculate the end date as it will be the same as the expiration date. If no length is set, or the subscription has a cancelled status,
  * some time within 24 hours after the last renewal order's date can be used to provide a rough estimate.
  *
  * @param  array $subscription data about the subscription
  * @param  numeric $item_id    the id of the product we're missing variation id for
  * @param  array $item_meta    meta data about the product
  * @return array               repaired data about the subscription
  */
 public static function repair_end_date($subscription, $item_id, $item_meta)
 {
     $subscription = self::repair_from_item_meta($subscription, $item_id, $item_meta, 'end_date', '_subscription_end_date', '');
     if ('' !== $subscription['end_date']) {
         return $subscription;
     }
     if ('expired' == $subscription['status'] && array_key_exists('expiry_date', $subscription) && !empty($subscription['expiry_date'])) {
         $subscription['end_date'] = $subscription['expiry_date'];
     } elseif ('cancelled' == $subscription['status'] || !array_key_exists('length', $subscription) || empty($subscription['length'])) {
         // get renewal orders
         $renewal_orders = self::get_renewal_orders($subscription);
         $last_order = array_shift($renewal_orders);
         if (empty($last_order)) {
             $subscription['end_date'] = 0;
         } else {
             $subscription['end_date'] = wcs_add_time(5, 'hours', strtotime($last_order->order_date));
         }
     } else {
         // if everything failed, let's have an empty one
         $subscription['end_date'] = 0;
     }
     return $subscription;
 }
 /**
  *  Determine if the subscription is for one payment only.
  *
  * @return bool whether the subscription is for only one payment
  * @since 2.0.17
  */
 public function is_one_payment()
 {
     $is_one_payment = false;
     if (0 != ($end_time = $this->get_time('end'))) {
         $from_timestamp = $this->get_time('start');
         if (0 != $this->get_time('trial_end') || WC_Subscriptions_Synchroniser::subscription_contains_synced_product($this)) {
             $subscription_order_count = count($this->get_related_orders());
             // when we have a sync'd subscription before its 1st payment, we need to base the calculations for the next payment on the first/next payment timestamp.
             if ($subscription_order_count < 2 && 0 != ($next_payment_timestamp = $this->get_time('next_payment'))) {
                 $from_timestamp = $next_payment_timestamp;
                 // when we have a sync'd subscription after its 1st payment, we need to base the calculations for the next payment on the last payment timestamp.
             } else {
                 if (!($subscription_order_count > 2) && 0 != ($last_payment_timestamp = $this->get_time('last_payment'))) {
                     $from_timestamp = $last_payment_timestamp;
                 }
             }
         }
         $next_payment_timestamp = wcs_add_time($this->billing_interval, $this->billing_period, $from_timestamp);
         if ($next_payment_timestamp + DAY_IN_SECONDS - 1 > $end_time) {
             $is_one_payment = true;
         }
     }
     return apply_filters('woocommerce_subscription_is_one_payment', $is_one_payment, $this);
 }
 /**
  * Calculates the next payment date for a subscription.
  *
  * Although an inactive subscription does not have a next payment date, this function will still calculate the date
  * so that it can be used to determine the date the next payment should be charged for inactive subscriptions.
  *
  * @return int | string Zero if the subscription has no next payment date, or a MySQL formatted date time if there is a next payment date
  */
 protected function calculate_next_payment_date()
 {
     $next_payment_date = 0;
     // If the subscription is not active, there is no next payment date
     $start_time = $this->get_time('start');
     $next_payment_time = $this->get_time('next_payment');
     $trial_end_time = $this->get_time('trial_end');
     $last_payment_time = $this->get_time('last_payment');
     $end_time = $this->get_time('end');
     // If the subscription has a free trial period, and we're still in the free trial period, the next payment is due at the end of the free trial
     if ($trial_end_time > current_time('timestamp', true)) {
         $next_payment_timestamp = $trial_end_time;
     } else {
         // The next payment date is {interval} billing periods from the start date, trial end date or last payment date
         if (0 !== $next_payment_time && $next_payment_time < gmdate('U') && 1 <= $this->get_completed_payment_count()) {
             $from_timestamp = $next_payment_time;
         } elseif ($last_payment_time > $start_time && apply_filters('wcs_calculate_next_payment_from_last_payment', true, $this)) {
             $from_timestamp = $last_payment_time;
         } elseif ($next_payment_time > $start_time) {
             // Use the currently scheduled next payment to preserve synchronisation
             $from_timestamp = $next_payment_time;
         } else {
             $from_timestamp = $start_time;
         }
         $next_payment_timestamp = wcs_add_time($this->billing_interval, $this->billing_period, $from_timestamp);
         // Make sure the next payment is in the future
         $i = 1;
         while ($next_payment_timestamp < current_time('timestamp', true) && $i < 30) {
             $next_payment_timestamp = wcs_add_time($this->billing_interval, $this->billing_period, $next_payment_timestamp);
             $i += 1;
         }
     }
     // If the subscription has an end date and the next billing period comes after that, return 0
     if (0 != $end_time && $next_payment_timestamp + 120 > $end_time) {
         $next_payment_timestamp = 0;
     }
     if ($next_payment_timestamp > 0) {
         $next_payment_date = date('Y-m-d H:i:s', $next_payment_timestamp);
     }
     return $next_payment_date;
 }
 /**
  * Takes a subscription product's ID and returns the date on which the subscription product will expire,
  * based on the subscription's length and calculated from either the $from_date if specified, or the current date/time.
  *
  * @param mixed $product_id The product/post ID of the subscription
  * @param mixed $from_date A MySQL formatted date/time string from which to calculate the expiration date, or empty (default), which will use today's date/time.
  * @since 1.0
  */
 public static function get_expiration_date($product_id, $from_date = '')
 {
     $subscription_length = self::get_length($product_id);
     if ($subscription_length > 0) {
         if (empty($from_date)) {
             $from_date = gmdate('Y-m-d H:i:s');
         }
         if (self::get_trial_length($product_id) > 0) {
             $from_date = self::get_trial_expiration_date($product_id, $from_date);
         }
         $expiration_date = gmdate('Y-m-d H:i:s', wcs_add_time($subscription_length, self::get_period($product_id), strtotime($from_date)));
     } else {
         $expiration_date = 0;
     }
     return apply_filters('woocommerce_subscriptions_product_expiration_date', $expiration_date, $product_id, $from_date);
 }