/** * Create a new subscription * * Returns a new WC_Subscription object on success which can then be used to add additional data. * * @return WC_Subscription | WP_Error A WC_Subscription on success or WP_Error object on failure * @since 2.0 */ function wcs_create_subscription($args = array()) { $order = isset($args['order_id']) ? wc_get_order($args['order_id']) : null; if (!empty($order) && isset($order->post->post_date)) { $default_start_date = '0000-00-00 00:00:00' != $order->post->post_date_gmt ? $order->post->post_date_gmt : get_gmt_from_date($order->post->post_date); } else { $default_start_date = current_time('mysql', true); } $default_args = array('status' => '', 'order_id' => 0, 'customer_note' => null, 'customer_id' => !empty($order) ? $order->get_user_id() : null, 'start_date' => $default_start_date, 'created_via' => !empty($order) ? $order->created_via : '', 'order_version' => !empty($order) ? $order->order_version : WC_VERSION, 'currency' => !empty($order) ? $order->order_currency : get_woocommerce_currency(), 'prices_include_tax' => !empty($order) ? $order->prices_include_tax ? 'yes' : 'no' : get_option('woocommerce_prices_include_tax')); $args = wp_parse_args($args, $default_args); $subscription_data = array(); // validate the start_date field if (!is_string($args['start_date']) || false === wcs_is_datetime_mysql_format($args['start_date'])) { return new WP_Error('woocommerce_subscription_invalid_start_date_format', _x('Invalid date. The date must be a string and of the format: "Y-m-d H:i:s".', 'Error message while creating a subscription', 'woocommerce-subscriptions')); } else { if (strtotime($args['start_date']) > current_time('timestamp', true)) { return new WP_Error('woocommerce_subscription_invalid_start_date', _x('Subscription start date must be before current day.', 'Error message while creating a subscription', 'woocommerce-subscriptions')); } } // check customer id is set if (empty($args['customer_id']) || !is_numeric($args['customer_id']) || $args['customer_id'] <= 0) { return new WP_Error('woocommerce_subscription_invalid_customer_id', _x('Invalid subscription customer_id.', 'Error message while creating a subscription', 'woocommerce-subscriptions')); } // check the billing period if (empty($args['billing_period']) || !in_array(strtolower($args['billing_period']), array_keys(wcs_get_subscription_period_strings()))) { return new WP_Error('woocommerce_subscription_invalid_billing_period', __('Invalid subscription billing period given.', 'woocommerce-subscriptions')); } // check the billing interval if (empty($args['billing_interval']) || !is_numeric($args['billing_interval']) || absint($args['billing_interval']) <= 0) { return new WP_Error('woocommerce_subscription_invalid_billing_interval', __('Invalid subscription billing interval given. Must be an integer greater than 0.', 'woocommerce-subscriptions')); } $subscription_data['post_type'] = 'shop_subscription'; $subscription_data['post_status'] = 'wc-' . apply_filters('woocommerce_default_subscription_status', 'pending'); $subscription_data['ping_status'] = 'closed'; $subscription_data['post_author'] = 1; $subscription_data['post_password'] = uniqid('order_'); // translators: Order date parsed by strftime $post_title_date = strftime(_x('%b %d, %Y @ %I:%M %p', 'Used in subscription post title. "Subscription renewal order - <this>"', 'woocommerce-subscriptions')); // translators: placeholder is order date parsed by strftime $subscription_data['post_title'] = sprintf(_x('Subscription – %s', 'The post title for the new subscription', 'woocommerce-subscriptions'), $post_title_date); $subscription_data['post_date_gmt'] = $args['start_date']; $subscription_data['post_date'] = get_date_from_gmt($args['start_date']); if ($args['order_id'] > 0) { $subscription_data['post_parent'] = absint($args['order_id']); } if (!is_null($args['customer_note']) && !empty($args['customer_note'])) { $subscription_data['post_excerpt'] = $args['customer_note']; } // Only set the status if creating a new subscription, use wcs_update_subscription to update the status if ($args['status']) { if (!in_array('wc-' . $args['status'], array_keys(wcs_get_subscription_statuses()))) { return new WP_Error('woocommerce_invalid_subscription_status', __('Invalid subscription status given.', 'woocommerce-subscriptions')); } $subscription_data['post_status'] = 'wc-' . $args['status']; } $subscription_id = wp_insert_post(apply_filters('woocommerce_new_subscription_data', $subscription_data, $args), true); if (is_wp_error($subscription_id)) { return $subscription_id; } // Default order meta data. update_post_meta($subscription_id, '_order_key', 'wc_' . apply_filters('woocommerce_generate_order_key', uniqid('order_'))); update_post_meta($subscription_id, '_order_currency', $args['currency']); update_post_meta($subscription_id, '_prices_include_tax', $args['prices_include_tax']); update_post_meta($subscription_id, '_created_via', sanitize_text_field($args['created_via'])); // add/update the billing update_post_meta($subscription_id, '_billing_period', $args['billing_period']); update_post_meta($subscription_id, '_billing_interval', absint($args['billing_interval'])); update_post_meta($subscription_id, '_customer_user', $args['customer_id']); update_post_meta($subscription_id, '_order_version', $args['order_version']); return new WC_Subscription($subscription_id); }
/** * Set the dates on the subscription. * * Because dates are interdependent on each other, this function will take an array of dates, make sure that all * dates are in the right order in the right format, that there is at least something to update. * * @param array $dates array containing dates with keys: 'start', 'trial_end', 'next_payment', * 'last_payment' or 'end'. Values are time * @param string $timezone The timezone of the $datetime param. Default 'gmt'. */ public function update_dates($dates, $timezone = 'gmt') { global $wpdb; if (!is_array($dates)) { throw new InvalidArgumentException(__('Invalid format. First parameter needs to be an array.', 'woocommerce-subscriptions')); } if (empty($dates)) { throw new InvalidArgumentException(__('Invalid data. First parameter was empty when passed to update_dates().', 'woocommerce-subscriptions')); } $allowed_date_keys = array_keys(wcs_get_subscription_date_types()); $passed_date_keys = array_keys($dates); $extra_keys = array_diff(str_replace('_date', '', $passed_date_keys), $allowed_date_keys); if (!empty($extra_keys)) { throw new InvalidArgumentException(__('Invalid data. First parameter has a date that is not in the registered date types.', 'woocommerce-subscriptions')); } $timestamps = array(); foreach ($dates as $date_type => $datetime) { if (!empty($datetime) && false === wcs_is_datetime_mysql_format($datetime)) { // translators: placeholder is date type (e.g. "end", "next_payment"...) throw new InvalidArgumentException(sprintf(_x('Invalid %s date. The date must be of the format: "Y-m-d H:i:s".', 'appears in an error message if date is wrong format', 'woocommerce-subscriptions'), $date_type)); } $date_type = str_replace('_date', '', $date_type); if (empty($datetime)) { $timestamps[$date_type] = 0; } else { if ('gmt' !== strtolower($timezone)) { $datetime = get_gmt_from_date($datetime); } $timestamps[$date_type] = strtotime($datetime); } } foreach ($allowed_date_keys as $date_type) { if (!array_key_exists($date_type, $timestamps)) { $timestamps[$date_type] = $this->get_time($date_type); } if (0 == $timestamps[$date_type]) { // Last payment is not in the UI, and it should NOT be deleted as that would mess with scheduling if ('last_payment' != $date_type && 'start' != $date_type) { $this->delete_date($date_type); } unset($timestamps[$date_type]); continue; } } $messages = array(); // And then iterate over them. We need the two separate loops as we need a full array before we start checking // the relationships between them. foreach ($timestamps as $date_type => $datetime) { switch ($date_type) { case 'end': if (array_key_exists('last_payment', $timestamps) && $datetime <= $timestamps['last_payment']) { $messages[] = sprintf(__('The %s date must occur after the last payment date.', 'woocommerce-subscriptions'), $date_type); } if (array_key_exists('next_payment', $timestamps) && $datetime <= $timestamps['next_payment']) { $messages[] = sprintf(__('The %s date must occur after the next payment date.', 'woocommerce-subscriptions'), $date_type); } case 'next_payment': // Guarantees that end is strictly after trial_end, because if next_payment and end can't be at same // time if (array_key_exists('trial_end', $timestamps) && $datetime < $timestamps['trial_end']) { $messages[] = sprintf(__('The %s date must occur after the trial end date.', 'woocommerce-subscriptions'), $date_type); } case 'trial_end': if ($datetime <= $timestamps['start']) { $messages[] = sprintf(__('The %s date must occur after the start date.', 'woocommerce-subscriptions'), $date_type); } } } if (!empty($messages)) { throw new Exception(join(' ', $messages)); } $is_updated = false; foreach ($timestamps as $date_type => $timestamp) { $datetime = date('Y-m-d H:i:s', $timestamp); if ($datetime == $this->get_date($date_type)) { continue; } switch ($date_type) { case 'next_payment': case 'trial_end': case 'end': $is_updated = update_post_meta($this->id, wcs_get_date_meta_key($date_type), $datetime); break; case 'start': $wpdb->query($wpdb->prepare("UPDATE {$wpdb->posts} SET post_date = %s, post_date_gmt = %s WHERE ID = %s", get_date_from_gmt($datetime), $datetime, $this->id)); // Don't use wp_update_post() to avoid infinite loops here $is_updated = true; break; case 'last_payment': $this->update_last_payment_date($datetime); $is_updated = true; break; } if ($is_updated) { $this->schedule->{$date_type} = $datetime; do_action('woocommerce_subscription_date_updated', $this, $date_type, $datetime); } } }