/** * WCS API function to get all the subscriptions tied to a particular customer. * * @since 2.0 * @param $id int * @param $fields array */ public function get_customer_subscriptions($id, $fields = null) { global $wpdb; // check the customer id given is a valid customer in the store. We're able to leech off WC-API for this. $id = $this->validate_request($id, 'customer', 'read'); if (is_wp_error($id)) { return $id; } $subscription_ids = $wpdb->get_col($wpdb->prepare("SELECT ID, post_date_gmt\n\t\t\t\t\t\tFROM {$wpdb->posts} AS posts\n\t\t\t\t\t\tLEFT JOIN {$wpdb->postmeta} AS meta on posts.ID = meta.post_id\n\t\t\t\t\t\tWHERE meta.meta_key = '_customer_user'\n\t\t\t\t\t\tAND meta.meta_value = '%d'\n\t\t\t\t\t\tAND posts.post_type = 'shop_subscription'\n\t\t\t\t\t\tAND posts.post_status IN ( '" . implode("','", array_keys(wcs_get_subscription_statuses())) . "' )\n\t\t\t\t\t\tGROUP BY posts.ID\n\t\t\t\t\t\tORDER BY posts.post_date_gmt DESC\n\t\t\t\t\t", $id)); $subscriptions = array(); foreach ($subscription_ids as $subscription_id) { $subscriptions[] = WC()->api->WC_API_Subscriptions->get_subscription($subscription_id, $fields); } return array('customer_subscriptions' => apply_filters('wc_subscriptions_api_customer_subscriptions', $subscriptions, $id, $fields, $subscription_ids, $this->server)); }
/** * Get the nice name for a subscription's status * * @since 2.0 * @param string $status * @return string */ function wcs_get_subscription_status_name($status) { if (!is_string($status)) { return new WP_Error('woocommerce_subscription_wrong_status_format', __('Can not get status name. Status is not a string.', 'woocommerce-subscriptions')); } $statuses = wcs_get_subscription_statuses(); $sanitized_status_key = wcs_sanitize_subscription_status_key($status); // if the sanitized status key is not in the list of filtered subscription names, return the // original key, without the wc- $status_name = isset($statuses[$sanitized_status_key]) ? $statuses[$sanitized_status_key] : $status; return apply_filters('woocommerce_subscription_status_name', $status_name, $status); }
/** * Output the metabox */ public static function output($post) { global $the_subscription; if (!is_object($the_subscription) || $the_subscription->id !== $post->ID) { $the_subscription = wc_get_order($post->ID); } $subscription = $the_subscription; self::init_address_fields(); wp_nonce_field('woocommerce_save_data', 'woocommerce_meta_nonce'); ?> <style type="text/css"> #post-body-content, #titlediv, #major-publishing-actions, #minor-publishing-actions, #visibility, #submitdiv { display:none } </style> <div class="panel-wrap woocommerce"> <input name="post_title" type="hidden" value="<?php echo empty($post->post_title) ? esc_attr(get_post_type_object($subscription->post->post_type)->labels->singular_name) : esc_attr($post->post_title); ?> " /> <input name="post_status" type="hidden" value="<?php echo esc_attr($subscription->get_status()); ?> " /> <div id="order_data" class="panel"> <h2><?php // translators: placeholder is the ID of the subscription printf(esc_html_x('Subscription %s details', 'edit subscription header', 'woocommerce-subscriptions'), esc_html($subscription->get_order_number())); ?> </h2> <div class="order_data_column_container"> <div class="order_data_column"> <p class="form-field form-field-wide wc-customer-user"> <label for="customer_user"><?php esc_html_e('Customer:', 'woocommerce-subscriptions'); ?> <?php if (!empty($subscription->customer_user)) { $args = array('post_status' => 'all', 'post_type' => 'shop_subscription', '_customer_user' => absint($subscription->customer_user)); printf('<a href="%s">%s →</a>', esc_url(add_query_arg($args, admin_url('edit.php'))), esc_html__('View other subscriptions', 'woocommerce-subscriptions')); } ?> </label> <?php $user_string = ''; $user_id = ''; if (!empty($subscription->customer_user) && false !== get_userdata($subscription->customer_user)) { $user_id = absint($subscription->customer_user); $user = get_user_by('id', $user_id); $user_string = esc_html($user->display_name) . ' (#' . absint($user->ID) . ' – ' . esc_html($user->user_email); } ?> <input type="hidden" class="wc-customer-search" id="customer_user" name="customer_user" data-placeholder="<?php esc_attr_e('Search for a customer…', 'woocommerce-subscriptions'); ?> " data-selected="<?php echo esc_attr($user_string); ?> " value="<?php echo esc_attr($user_id); ?> " /> </p> <p class="form-field form-field-wide"> <label for="order_status"><?php esc_html_e('Subscription Status:', 'woocommerce-subscriptions'); ?> </label> <select id="order_status" name="order_status"> <?php $statuses = wcs_get_subscription_statuses(); foreach ($statuses as $status => $status_name) { if (!$subscription->can_be_updated_to($status) && !$subscription->has_status(str_replace('wc-', '', $status))) { continue; } echo '<option value="' . esc_attr($status) . '" ' . selected($status, 'wc-' . $subscription->get_status(), false) . '>' . esc_html($status_name) . '</option>'; } ?> </select> </p> <?php do_action('woocommerce_admin_order_data_after_order_details', $subscription); ?> </div> <div class="order_data_column"> <h4><?php esc_html_e('Billing Details', 'woocommerce-subscriptions'); ?> <a class="edit_address" href="#"><a href="#" class="tips load_customer_billing" data-tip="Load billing address" style="display:none;">Load billing address</a></a></h4> <?php // Display values echo '<div class="address">'; if ($subscription->get_formatted_billing_address()) { echo '<p><strong>' . esc_html__('Address', 'woocommerce-subscriptions') . ':</strong>' . wp_kses($subscription->get_formatted_billing_address(), array('br' => array())) . '</p>'; } else { echo '<p class="none_set"><strong>' . esc_html__('Address', 'woocommerce-subscriptions') . ':</strong> ' . esc_html__('No billing address set.', 'woocommerce-subscriptions') . '</p>'; } foreach (self::$billing_fields as $key => $field) { if (isset($field['show']) && false === $field['show']) { continue; } $field_name = 'billing_' . $key; if ($subscription->{$field_name}) { echo '<p><strong>' . esc_html($field['label']) . ':</strong> ' . wp_kses_post(make_clickable(esc_html($subscription->{$field_name}))) . '</p>'; } } echo '<p' . (!empty($subscription->payment_method) ? ' class="' . esc_attr($subscription->payment_method) . '"' : '') . '><strong>' . esc_html__('Payment Method', 'woocommerce-subscriptions') . ':</strong>' . wp_kses_post(nl2br($subscription->get_payment_method_to_display())); // Display help tip if (!empty($subscription->payment_method) && !$subscription->is_manual()) { echo '<img class="help_tip" data-tip="Gateway ID: [' . esc_attr($subscription->payment_gateway->id) . ']" src="' . esc_url(WC()->plugin_url()) . '/assets/images/help.png" height="16" width="16" />'; } echo '</p>'; echo '</div>'; // Display form echo '<div class="edit_address">'; foreach (self::$billing_fields as $key => $field) { if (!isset($field['type'])) { $field['type'] = 'text'; } switch ($field['type']) { case 'select': // allow for setting a default value programaticaly, and draw the selectbox woocommerce_wp_select(array('id' => '_billing_' . $key, 'label' => $field['label'], 'options' => $field['options'], 'value' => isset($field['value']) ? $field['value'] : null)); break; default: // allow for setting a default value programaticaly, and draw the textbox woocommerce_wp_text_input(array('id' => '_billing_' . $key, 'label' => $field['label'], 'value' => isset($field['value']) ? $field['value'] : null)); break; } } WCS_Change_Payment_Method_Admin::display_fields($subscription); echo '</div>'; do_action('woocommerce_admin_order_data_after_billing_address', $subscription); ?> </div> <div class="order_data_column"> <h4><?php esc_html_e('Shipping Details', 'woocommerce-subscriptions'); ?> <a class="edit_address" href="#"> <a href="#" class="tips billing-same-as-shipping" data-tip="Copy from billing" style="display:none;">Copy from billing</a> <a href="#" class="tips load_customer_shipping" data-tip="Load shipping address" style="display:none;">Load shipping address</a> </a> </h4> <?php // Display values echo '<div class="address">'; if ($subscription->get_formatted_shipping_address()) { echo '<p><strong>' . esc_html__('Address', 'woocommerce-subscriptions') . ':</strong>' . wp_kses($subscription->get_formatted_shipping_address(), array('br' => array())) . '</p>'; } else { echo '<p class="none_set"><strong>' . esc_html__('Address', 'woocommerce-subscriptions') . ':</strong> ' . esc_html__('No shipping address set.', 'woocommerce-subscriptions') . '</p>'; } if (self::$shipping_fields) { foreach (self::$shipping_fields as $key => $field) { if (isset($field['show']) && false === $field['show']) { continue; } $field_name = 'shipping_' . $key; if (!empty($subscription->{$field_name})) { echo '<p><strong>' . esc_html($field['label']) . ':</strong> ' . wp_kses_post(make_clickable(esc_html($subscription->{$field_name}))) . '</p>'; } } } if (apply_filters('woocommerce_enable_order_notes_field', 'yes' == get_option('woocommerce_enable_order_comments', 'yes')) && $post->post_excerpt) { echo '<p><strong>' . esc_html__('Customer Note', 'woocommerce-subscriptions') . ':</strong> ' . wp_kses_post(nl2br($post->post_excerpt)) . '</p>'; } echo '</div>'; // Display form echo '<div class="edit_address">'; if (self::$shipping_fields) { foreach (self::$shipping_fields as $key => $field) { if (!isset($field['type'])) { $field['type'] = 'text'; } switch ($field['type']) { case 'select': woocommerce_wp_select(array('id' => '_shipping_' . $key, 'label' => $field['label'], 'options' => $field['options'])); break; default: woocommerce_wp_text_input(array('id' => '_shipping_' . $key, 'label' => $field['label'])); break; } } } if (apply_filters('woocommerce_enable_order_notes_field', 'yes' == get_option('woocommerce_enable_order_comments', 'yes'))) { ?> <p class="form-field form-field-wide"><label for="excerpt"><?php esc_html_e('Customer Note:', 'woocommerce-subscriptions'); ?> </label> <textarea rows="1" cols="40" name="excerpt" tabindex="6" id="excerpt" placeholder="<?php esc_attr_e('Customer\'s notes about the order', 'woocommerce-subscriptions'); ?> "><?php echo wp_kses_post($post->post_excerpt); ?> </textarea></p> <?php } echo '</div>'; do_action('woocommerce_admin_order_data_after_shipping_address', $subscription); ?> </div> </div> <div class="clear"></div> </div> </div> <?php }
/** * Filters and sorting handler * * @param array $vars * @return array */ public function request_query($vars) { global $typenow; if ('shop_subscription' === $typenow) { // Filter the orders by the posted customer. if (isset($_GET['_customer_user']) && $_GET['_customer_user'] > 0) { $vars['meta_query'][] = array('key' => '_customer_user', 'value' => (int) $_GET['_customer_user'], 'compare' => '='); } if (isset($_GET['_wcs_product']) && $_GET['_wcs_product'] > 0) { $subscription_ids = wcs_get_subscriptions_for_product($_GET['_wcs_product']); if (!empty($subscription_ids)) { $vars['post__in'] = $subscription_ids; } else { // no subscriptions contain this product, but we need to pass post__in an ID that no post will have because WP returns all posts when post__in is an empty array: https://core.trac.wordpress.org/ticket/28099 $vars['post__in'] = array(0); } } if (!empty($_GET['_payment_method'])) { $payment_gateway_filter = 'none' == $_GET['_payment_method'] ? '' : $_GET['_payment_method']; $query_vars = array('post_type' => 'shop_subscription', 'posts_per_page' => -1, 'post_status' => 'any', 'fields' => 'ids', 'meta_query' => array(array('key' => '_payment_method', 'value' => $payment_gateway_filter))); // If there are already set post restrictions (post__in) apply them to this query if (isset($vars['post__in'])) { $query_vars['post__in'] = $vars['post__in']; } $subscription_ids = get_posts($query_vars); if (!empty($subscription_ids)) { $vars['post__in'] = $subscription_ids; } else { $vars['post__in'] = array(0); } } // Sorting if (isset($vars['orderby'])) { switch ($vars['orderby']) { case 'order_total': $vars = array_merge($vars, array('meta_key' => '_order_total', 'orderby' => 'meta_value_num')); break; case 'last_payment_date': add_filter('posts_clauses', array($this, 'posts_clauses'), 10, 2); break; case 'trial_end_date': case 'next_payment_date': case 'end_date': $vars = array_merge($vars, array('meta_key' => sprintf('_schedule_%s', str_replace('_date', '', $vars['orderby'])), 'meta_type' => 'DATETIME', 'orderby' => 'meta_value')); break; } } // Status if (!isset($vars['post_status'])) { $vars['post_status'] = array_keys(wcs_get_subscription_statuses()); } } return $vars; }
/** * Updates status of the subscription * * @param string $new_status Status to change the order to. No internal wc- prefix is required. * @param string $note (default: '') Optional note to add */ public function update_status($new_status, $note = '', $manual = false) { if (!$this->id) { return; } // Standardise status names. $new_status = 'wc-' === substr($new_status, 0, 3) ? substr($new_status, 3) : $new_status; $new_status_key = 'wc-' . $new_status; $old_status = $this->get_status(); $old_status_key = $this->post_status; if ($new_status !== $old_status || !in_array($this->post_status, array_keys(wcs_get_subscription_statuses()))) { // Only update is possible if (!$this->can_be_updated_to($new_status)) { $message = sprintf(__('Unable to change subscription status to "%s".', 'woocommerce-subscriptions'), $new_status); $this->add_order_note($message); do_action('woocommerce_subscription_unable_to_update_status', $this, $new_status, $old_status); // Let plugins handle it if they tried to change to an invalid status throw new Exception($message); } try { wp_update_post(array('ID' => $this->id, 'post_status' => $new_status_key)); $this->post_status = $new_status_key; switch ($new_status) { case 'pending': // Nothing to do here break; case 'pending-cancel': $end_date = $this->calculate_date('end_of_prepaid_term'); // If there is no future payment and no expiration date set, the customer has no prepaid term (this shouldn't be possible as only active subscriptions can be set to pending cancellation and an active subscription always has either an end date or next payment) if (0 == $end_date) { $end_date = current_time('mysql', true); } $this->delete_date('trial_end'); $this->delete_date('next_payment'); $this->update_dates(array('end' => $end_date)); break; case 'completed': // core WC order status mapped internally to avoid exceptions // core WC order status mapped internally to avoid exceptions case 'active': // Recalculate and set next payment date $next_payment = $this->get_time('next_payment'); if ($next_payment < gmdate('U')) { // also accounts for a $next_payment of 0, meaning it's not set $next_payment = $this->calculate_date('next_payment'); if ($next_payment > 0) { $this->update_dates(array('next_payment' => $next_payment)); } } // Trial end date and end/expiration date don't change at all - they should be set when the subscription is first created wcs_make_user_active($this->customer_user); break; case 'failed': // core WC order status mapped internally to avoid exceptions // core WC order status mapped internally to avoid exceptions case 'on-hold': // Record date of suspension - 'post_modified' column? $this->update_suspension_count($this->suspension_count + 1); wcs_maybe_make_user_inactive($this->customer_user); break; case 'cancelled': case 'switched': case 'expired': $this->delete_date('trial_end'); $this->delete_date('next_payment'); $this->update_dates(array('end' => current_time('mysql', true))); wcs_maybe_make_user_inactive($this->customer_user); break; } $this->add_order_note(trim($note . ' ' . sprintf(__('Status changed from %s to %s.', 'woocommerce-subscriptions'), wcs_get_subscription_status_name($old_status), wcs_get_subscription_status_name($new_status))), 0, $manual); // dynamic hooks for convenience do_action('woocommerce_subscription_status_' . $new_status, $this); do_action('woocommerce_subscription_status_' . $old_status . '_to_' . $new_status, $this); // Trigger a hook with params we want do_action('woocommerce_subscription_status_updated', $this, $new_status, $old_status); // Trigger a hook with params matching WooCommerce's 'woocommerce_order_status_changed' hook so functions attached to it can be attached easily to subscription status changes do_action('woocommerce_subscription_status_changed', $this->id, $old_status, $new_status); } catch (Exception $e) { // Make sure the old status is restored wp_update_post(array('ID' => $this->id, 'post_status' => $old_status_key)); $this->post_status = $old_status_key; $this->add_order_note(sprintf(__('Unable to change subscription status to "%s".', 'woocommerce-subscriptions'), $new_status)); do_action('woocommerce_subscription_unable_to_update_status', $this, $new_status, $old_status); throw $e; } } }
/** * Returns an associative array with the structure 'status' => 'count' for all subscriptions on the site * and includes an "all" status, representing all subscriptions. * * @since 1.4 * @deprecated 2.0 */ public static function get_subscription_status_counts() { _deprecated_function(__METHOD__, '2.0'); $results = wp_count_posts('shop_subscription'); $count = array(); foreach ($results as $status => $count) { if (in_array($status, array_keys(wcs_get_subscription_statuses())) || in_array($status, array('trash', 'draft'))) { $counts[$status] = $count; } } // Order with 'all' at the beginning, then alphabetically ksort($counts); $counts = array('all' => array_sum($counts)) + $counts; return apply_filters('woocommerce_subscription_status_counts', $counts); }
/** * Filters and sorting handler * * @param array $vars * @return array */ public function request_query($vars) { global $typenow; if ('shop_subscription' === $typenow) { // Filter the orders by the posted customer. if (isset($_GET['_customer_user']) && $_GET['_customer_user'] > 0) { $vars['meta_key'] = '_customer_user'; $vars['meta_value'] = (int) $_GET['_customer_user']; } if (isset($_GET['_wcs_product']) && $_GET['_wcs_product'] > 0) { $subscription_ids = wcs_get_subscriptions_for_product($_GET['_wcs_product']); if (!empty($subscription_ids)) { $vars['post__in'] = $subscription_ids; } else { // no subscriptions contain this product, but we need to pass post__in an ID that no post will have because WP returns all posts when post__in is an empty array: https://core.trac.wordpress.org/ticket/28099 $vars['post__in'] = array(0); } } // Sorting if (isset($vars['orderby'])) { switch ($vars['orderby']) { case 'order_total': $vars = array_merge($vars, array('meta_key' => '_order_total', 'orderby' => 'meta_value_num')); break; case 'last_payment_date': add_filter('posts_clauses', array($this, 'posts_clauses'), 10, 2); break; case 'trial_end_date': case 'next_payment_date': case 'end_date': $vars = array_merge($vars, array('meta_key' => sprintf('_schedule_%s', str_replace('_date', '', $vars['orderby'])), 'meta_type' => 'DATETIME', 'orderby' => 'meta_value')); break; } } // Status if (!isset($vars['post_status'])) { $vars['post_status'] = array_keys(wcs_get_subscription_statuses()); } } return $vars; }
/** * Helper method to get order post objects * * We need to override WC_API_Orders::query_orders() because it uses wc_get_order_statuses() * for the query, but subscriptions use the values returned by wcs_get_subscription_statuses(). * * @since 2.0 * @param array $args request arguments for filtering query * @return WP_Query */ protected function query_orders($args) { // set base query arguments $query_args = array('fields' => 'ids', 'post_type' => $this->post_type, 'post_status' => array_keys(wcs_get_subscription_statuses())); // add status argument if (!empty($args['status'])) { $statuses = 'wc-' . str_replace(',', ',wc-', $args['status']); $statuses = explode(',', $statuses); $query_args['post_status'] = $statuses; unset($args['status']); } $query_args = $this->merge_query_args($query_args, $args); return new WP_Query($query_args); }