/** * When a new order is inserted, add the subscriptions period to the order. * * It's important that the period is tied to the order so that changing the products * period does not change the past. * * @since 1.0 */ public static function add_order_meta($order_id) { global $woocommerce; if (WC_Subscriptions_Order::order_contains_subscription($order_id)) { $order = new WC_Order($order_id); $order_subscription_periods = array(); $order_subscription_intervals = array(); $order_subscription_lengths = array(); $order_subscription_trial_lengths = array(); foreach ($order->get_items() as $item) { $period = WC_Subscriptions_Product::get_period($item['id']); if (!empty($period)) { $order_subscription_periods[$item['id']] = $period; } $interval = WC_Subscriptions_Product::get_interval($item['id']); if (!empty($interval)) { $order_subscription_intervals[$item['id']] = $interval; } $length = WC_Subscriptions_Product::get_length($item['id']); if (!empty($length)) { $order_subscription_lengths[$item['id']] = $length; } $trial_length = WC_Subscriptions_Product::get_trial_length($item['id']); if (!empty($trial_length)) { $order_subscription_trial_lengths[$item['id']] = $trial_length; } } update_post_meta($order_id, '_order_subscription_periods', $order_subscription_periods); update_post_meta($order_id, '_order_subscription_intervals', $order_subscription_intervals); update_post_meta($order_id, '_order_subscription_lengths', $order_subscription_lengths); update_post_meta($order_id, '_order_subscription_trial_lengths', $order_subscription_trial_lengths); // Store sign-up fee details foreach (WC_Subscriptions_Cart::get_sign_up_fee_fields() as $field_name) { update_post_meta($order_id, "_{$field_name}", $woocommerce->cart->{$field_name}); } // Prepare sign up fee taxes to store in same format as order taxes $sign_up_fee_taxes = array(); foreach (array_keys($woocommerce->cart->sign_up_fee_taxes) as $key) { $is_compound = $woocommerce->cart->tax->is_compound($key) ? 1 : 0; $sign_up_fee_taxes[] = array('label' => $woocommerce->cart->tax->get_rate_label($key), 'compound' => $is_compound, 'cart_tax' => number_format($woocommerce->cart->sign_up_fee_taxes[$key], 2, '.', '')); } update_post_meta($order_id, '_sign_up_fee_taxes', $sign_up_fee_taxes); } }
/** * Tells if a product is a subscription, provided that Subs is installed. * * @param mixed $product_id product or id to check * @return boolean true if Subs exists and product is a Sub */ public function is_subscription($product_id) { if (!class_exists('WC_Subscriptions')) { return false; } return WC_Subscriptions_Product::is_subscription($product_id); }
/** * Apply sign up fee or recurring fee discount before tax is calculated * * * @since 1.2 */ public static function apply_subscription_discount_before_tax($original_price, $product, $cart) { global $woocommerce; if (!WC_Subscriptions_Product::is_subscription($product['product_id'])) { return $original_price; } $price = $original_price; if (!empty($cart->applied_coupons)) { foreach ($cart->applied_coupons as $code) { $coupon = new WC_Coupon($code); if ($coupon->apply_before_tax() && $coupon->is_valid()) { // Sign up fee discount if ('sign_up_fee' == WC_Subscriptions_Cart::get_recalculation_type() && 'sign_up_fee' == $coupon->type || 'base_recurring_fee' == WC_Subscriptions_Cart::get_recalculation_type() && 'recurring_fee' == $coupon->type || 0 == WC_Subscriptions_Cart::get_cart_subscription_sign_up_fee() && 'recurring_fee' == $coupon->type) { if ($original_price < $coupon->amount) { $discount_amount = $original_price; } else { $discount_amount = $coupon->amount; } $price = $original_price - $coupon->amount; if ($price < 0) { $price = 0; } // add to discount totals $woocommerce->cart->discount_cart = $woocommerce->cart->discount_cart + $discount_amount * $product['quantity']; } } } } return $price; }
/** * Returns the recurring price component of a subscription product. * * @param WC_Product $product * @return string */ public static function get_recurring_price_html_component($product) { $sync_date = $product->subscription_payment_sync_date; $product->subscription_payment_sync_date = 0; $sub_price_html = WC_Subscriptions_Product::get_price_string($product, array('price' => '%s', 'sign_up_fee' => false)); $product->subscription_payment_sync_date = $sync_date; return $sub_price_html; }
/** * Determine the maximum monthly payment required for subscriptions currently in cart. * This is an approximation based on 31 day months. * * @return float */ protected function calculate_potential_monthly_maximum_payment_in_cart() { $potential_monthly_max = 0; foreach (WC()->cart->cart_contents as $cart_item) { $product_subscription = $cart_item['data']; $item_quantity = absint($cart_item['quantity']); if (WC_Subscriptions_Product::is_subscription($product_subscription)) { $subscription_price = (double) $product_subscription->subscription_price; $subscription_interval = (double) $product_subscription->subscription_period_interval; $subscription_monthly_max = $item_quantity * $subscription_price; if ('day' === $product_subscription->subscription_period) { $subscription_monthly_max = $subscription_price * (31 / $subscription_interval); } else { if ('week' === $product_subscription->subscription_period) { $subscription_monthly_max = $subscription_price * ceil(4 / $subscription_interval); } } $potential_monthly_max += $subscription_monthly_max; } } return $potential_monthly_max; }
/** * Save subscription details when edit product page is submitted for a subscription product type * or the bulk edit product is saved. * * The $_REQUEST global is used to account for both $_GET submission from Bulk Edit page and * $_POST submission from Edit Product page. * * @param array Array of Product types & their labels, excluding the Subscription product type. * @return array Array of Product types & their labels, including the Subscription product type. * @since 1.0 */ public static function save_subscription_meta($post_id) { if (!WC_Subscriptions_Product::is_subscription($post_id)) { return; } if (isset($_REQUEST['_subscription_price'])) { update_post_meta($post_id, '_subscription_price', stripslashes($_REQUEST['_subscription_price'])); update_post_meta($post_id, '_price', stripslashes($_REQUEST['_subscription_price'])); } else { // Handle bulk edit, where _subscription_price field is not available if (isset($_REQUEST['_regular_price']) && !empty($_REQUEST['_regular_price'])) { update_post_meta($post_id, '_subscription_price', stripslashes($_REQUEST['_regular_price'])); update_post_meta($post_id, '_price', stripslashes($_REQUEST['_regular_price'])); } elseif (isset($_REQUEST['_sale_price']) && !empty($_REQUEST['_sale_price'])) { update_post_meta($post_id, '_subscription_price', stripslashes($_REQUEST['_sale_price'])); update_post_meta($post_id, '_price', stripslashes($_REQUEST['_sale_price'])); } } $subscription_fields = array('_subscription_sign_up_fee', '_subscription_period', '_subscription_period_interval', '_subscription_length', '_subscription_trial_length'); foreach ($subscription_fields as $field_name) { if (isset($_REQUEST[$field_name])) { update_post_meta($post_id, $field_name, stripslashes($_REQUEST[$field_name])); } } }
/** * Return the sign-up fee for this product * * @return string */ public function get_sign_up_fee() { return WC_Subscriptions_Product::get_sign_up_fee($this); }
/** * Subscriptions are individual items so override the WC_Product is_sold_individually function * to reflect this. * * @since 1.0 */ public static function is_sold_individually($is_individual, $product) { // Sold individually if downloadable, virtual, and the option is enabled if (WC_Subscriptions_Product::is_subscription($product)) { $is_individual = true; } return $is_individual; }
/** * Clear all subscriptions from a user's account for a given order. * * @param $order WC_Order The order for which subscriptions should be cleared. * @since 1.0 */ public static function maybe_trash_subscription($order) { if (!is_object($order)) { $order = new WC_Order($order); } if (WC_Subscriptions_Order::order_contains_subscription($order)) { foreach ($order->get_items() as $order_item) { if (WC_Subscriptions_Product::is_subscription($order_item['id'])) { self::trash_subscription($order->customer_user, self::get_subscription_key($order->id, $order_item['id'])); } } } }
/** * Override the WooCommerce "Add to Cart" text with "Sign Up Now" * * @since 1.0 */ public static function add_to_cart_text($button_text) { global $product; if (WC_Subscriptions_Product::is_subscription($product)) { $button_text = get_option(WC_Subscriptions_Admin::$option_prefix . '_add_to_cart_button_text', __('Sign Up Now', self::$text_domain)); } return $button_text; }
/** * Returns subscription schemes for cart-level options. * Cart-level options will be displayed only if no cart-item is found with its own product-level subscription scheme. * This means that subscription options defined at product-level and "legacy" subscription-type products will "block" the display of cart-level options. * * In this case, cart-level options will be displayed at cart-item level. * * Must be called after all cart session data has been loaded. * * @return array|boolean */ public static function get_cart_subscription_schemes() { $wcs_prefix = WC_Subscriptions_Admin::$option_prefix; $cart_level_subs_active = get_option($wcs_prefix . '_enable_cart_subscriptions', 'no'); $cart_level_schemes = array(); $cart_level_schemes_keys = array(); if ($cart_level_subs_active === 'yes') { $cart_level_schemes = get_option($wcs_prefix . '_subscribe_to_cart_schemes', array()); } if (empty($cart_level_schemes)) { return false; } foreach ($cart_level_schemes as $cart_level_scheme) { $cart_level_schemes_keys[] = $cart_level_scheme['id']; } foreach (WC()->cart->cart_contents as $cart_item) { if (!WCS_ATT_Cart::is_supported_product_type($cart_item)) { return false; } if ($cart_item_level_schemes = self::get_subscription_schemes($cart_item, 'cart-item')) { return false; } if (WC_Subscriptions_Product::is_subscription($cart_item['product_id'])) { return false; } } return $cart_level_schemes; }
/** * Add subscription price string info to products with attached subscription schemes. * * @param string $price * @param WC_Product $product * @return string */ public static function filter_price_html($price, $product) { if (self::$bypass_price_html_filter) { return $price; } $subscription_schemes = WCS_ATT_Schemes::get_product_subscription_schemes($product); $has_product_level_schemes = empty($subscription_schemes) ? false : true; if ($has_product_level_schemes) { $force_subscription = get_post_meta($product->id, '_wcsatt_force_subscription', true); if ($force_subscription === 'yes') { $subscription_scheme = current($subscription_schemes); $overridden_prices = WCS_ATT_Schemes::get_subscription_scheme_prices($product, $subscription_scheme); $suffix = ''; $_cloned = clone $product; $_cloned->is_converted_to_sub = 'yes'; $_cloned->subscription_period = $subscription_scheme['subscription_period']; $_cloned->subscription_period_interval = $subscription_scheme['subscription_period_interval']; $_cloned->subscription_length = $subscription_scheme['subscription_length']; if (!empty($overridden_prices)) { $_cloned->regular_price = $overridden_prices['regular_price']; $_cloned->price = $overridden_prices['price']; $_cloned->sale_price = $overridden_prices['sale_price']; $_cloned->subscription_price = $overridden_prices['price']; } self::$bypass_price_html_filter = true; $price = $_cloned->get_price_html(); self::$bypass_price_html_filter = false; $price = WC_Subscriptions_Product::get_price_string($_cloned, array('price' => $price)); if (count($subscription_schemes) > 1 && false === strpos($price, $_cloned->get_price_html_from_text())) { $price = sprintf(_x('%1$s%2$s', 'Price range: from', WCS_ATT::TEXT_DOMAIN), $_cloned->get_price_html_from_text(), $price); } } else { $price_overrides_exist = WCS_ATT_Schemes::subscription_price_overrides_exist($subscription_schemes); $lowest_scheme_price = $product->price; $lowest_scheme_sale_price = $product->sale_price; $lowest_scheme_regular_price = $product->regular_price; $lowest_scheme_price_html = ''; $from_price = ''; if ($price_overrides_exist) { foreach ($subscription_schemes as $subscription_scheme) { $overridden_prices = WCS_ATT_Schemes::get_subscription_scheme_prices($product, $subscription_scheme); if (!empty($overridden_prices)) { if ($overridden_prices['price'] < $lowest_scheme_price) { $lowest_scheme_price = $overridden_prices['price']; $lowest_scheme_sale_price = $overridden_prices['sale_price']; $lowest_scheme_regular_price = $overridden_prices['regular_price']; } } } if ($lowest_scheme_price < $product->price) { $_cloned = clone $product; $_cloned->is_converted_to_sub = 'yes'; $_cloned->subscription_period = $subscription_scheme['subscription_period']; $_cloned->subscription_period_interval = $subscription_scheme['subscription_period_interval']; $_cloned->subscription_length = $subscription_scheme['subscription_length']; $_cloned->price = $lowest_scheme_price; $_cloned->sale_price = $lowest_scheme_price; $_cloned->regular_price = $lowest_scheme_regular_price; self::$bypass_price_html_filter = true; $lowest_scheme_price_html = $_cloned->get_price_html(); $lowest_scheme_price_html = WC_Subscriptions_Product::get_price_string($_cloned, array('price' => $lowest_scheme_price_html)); self::$bypass_price_html_filter = false; if (count($subscription_schemes) > 1) { $from_price = sprintf(_x('%1$s%2$s', 'Price range: from', WCS_ATT::TEXT_DOMAIN), _x('<span class="from">from </span>', 'min-price: 1 plan available', WCS_ATT::TEXT_DOMAIN), $lowest_scheme_price_html); } else { $from_price = sprintf(_x('%1$s%2$s', 'Price range: from', WCS_ATT::TEXT_DOMAIN), _x('<span class="for">for </span>', 'min-price: multiple plans available', WCS_ATT::TEXT_DOMAIN), $lowest_scheme_price_html); } } } if ($price_overrides_exist) { $suffix = ' <small class="wcsatt-sub-options">' . sprintf(_n('– or subscribe %s', '– or subscribe %s', count($subscription_schemes), WCS_ATT::TEXT_DOMAIN), $from_price) . '</small>'; } else { $suffix = ' <small class="wcsatt-sub-options">' . sprintf(_n('– subscription plan available', '– subscription plans available', count($subscription_schemes), WCS_ATT::TEXT_DOMAIN), $from_price) . '</small>'; } $price = sprintf(__('%1$s%2$s', 'price html sub options suffix', WCS_ATT::TEXT_DOMAIN), $price, $suffix); } } return $price; }
/** * 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. * @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()) { 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' => '', 'expiry_date' => '')); $subscription_key = self::get_subscription_key($order->id, $product_id); // In case the subscription exists already $subscription = self::get_subscription($subscription_key); if (!empty($subscription['variation_id'])) { $product_id = $subscription['variation_id']; } elseif (!empty($subscription['product_id'])) { $product_id = $subscription['product_id']; } // Adding a new subscription so set the start date/time to now if (!empty($args['start_date'])) { if (is_numeric($args['start_date'])) { $args['start_date'] = date('Y-m-d H:i:s', $args['start_date']); } $start_date = $args['start_date']; } else { $start_date = !empty($subscription['start_date']) ? $subscription['start_date'] : gmdate('Y-m-d H:i:s'); } // 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 = !empty($subscription['expiry_date']) ? $subscription['expiry_date'] : WC_Subscriptions_Product::get_expiration_date($product_id, $start_date); } // Adding a new subscription so set the expiry date/time from the order date $trial_expiration = !empty($subscription['trial_expiry_date']) ? $subscription['trial_expiry_date'] : WC_Subscriptions_Product::get_trial_expiration_date($product_id, $start_date); $failed_payments = !empty($subscription['failed_payments']) ? $subscription['failed_payments'] : 0; $completed_payments = !empty($subscription['completed_payments']) ? $subscription['completed_payments'] : array(); $order_item_id = WC_Subscriptions_Order::get_item_id_by_subscription_key($subscription_key); // Store the subscription details in item meta woocommerce_add_order_item_meta($order_item_id, '_subscription_start_date', $start_date, true); woocommerce_add_order_item_meta($order_item_id, '_subscription_expiry_date', $expiration, true); woocommerce_add_order_item_meta($order_item_id, '_subscription_trial_expiry_date', $trial_expiration, true); woocommerce_add_order_item_meta($order_item_id, '_subscription_failed_payments', $failed_payments, true); woocommerce_add_order_item_meta($order_item_id, '_subscription_completed_payments', $completed_payments, true); woocommerce_add_order_item_meta($order_item_id, '_subscription_status', 'pending', true); woocommerce_add_order_item_meta($order_item_id, '_subscription_end_date', 0, true); woocommerce_add_order_item_meta($order_item_id, '_subscription_suspension_count', 0, true); $product = WC_Subscriptions::get_product($product_id); // Set subscription status to active and log activation $order->add_order_note(sprintf(__('Pending subscription created for "%s".', 'woocommerce-subscriptions'), $product->get_title())); do_action('pending_subscription_created_for_order', $order, $product_id); }
/** * Calculate recurring line taxes when a store manager clicks the "Calc Line Tax" button on the "Edit Order" page. * * Based on the @see woocommerce_calc_line_taxes() function. * @since 1.2.4 * @return void */ public static function calculate_recurring_line_taxes() { global $woocommerce, $wpdb; check_ajax_referer('woocommerce-subscriptions', 'security'); $tax = new WC_Tax(); $taxes = $tax_rows = $item_taxes = $shipping_taxes = $return = array(); $item_tax = 0; $order_id = absint($_POST['order_id']); $country = strtoupper(esc_attr($_POST['country'])); $state = strtoupper(esc_attr($_POST['state'])); $postcode = strtoupper(esc_attr($_POST['postcode'])); $tax_class = esc_attr($_POST['tax_class']); if (isset($_POST['city'])) { $city = sanitize_title(esc_attr($_POST['city'])); } $shipping = $_POST['shipping']; $line_subtotal = isset($_POST['line_subtotal']) ? esc_attr($_POST['line_subtotal']) : 0; $line_total = isset($_POST['line_total']) ? esc_attr($_POST['line_total']) : 0; $product_id = ''; if (isset($_POST['order_item_id'])) { $product_id = woocommerce_get_order_item_meta($_POST['order_item_id'], '_product_id'); } elseif (isset($_POST['product_id'])) { $product_id = esc_attr($_POST['product_id']); } if (!empty($product_id) && WC_Subscriptions_Product::is_subscription($product_id)) { // Get product details $product = WC_Subscriptions::get_product($product_id); $item_tax_status = $product->get_tax_status(); if ($item_tax_status == 'taxable') { $tax_rates = $tax->find_rates(array('country' => $country, 'state' => $state, 'postcode' => $postcode, 'city' => $city, 'tax_class' => $tax_class)); $line_subtotal_taxes = $tax->calc_tax($line_subtotal, $tax_rates, false); $line_taxes = $tax->calc_tax($line_total, $tax_rates, false); $line_subtotal_tax = $tax->round(array_sum($line_subtotal_taxes)); $line_tax = $tax->round(array_sum($line_taxes)); if ($line_subtotal_tax < 0) { $line_subtotal_tax = 0; } if ($line_tax < 0) { $line_tax = 0; } $return = array('recurring_line_subtotal_tax' => $line_subtotal_tax, 'recurring_line_tax' => $line_tax); // Sum the item taxes foreach (array_keys($taxes + $line_taxes) as $key) { $taxes[$key] = (isset($line_taxes[$key]) ? $line_taxes[$key] : 0) + (isset($taxes[$key]) ? $taxes[$key] : 0); } } // Now calculate shipping tax $matched_tax_rates = array(); $tax_rates = $tax->find_rates(array('country' => $country, 'state' => $state, 'postcode' => $postcode, 'city' => $city, 'tax_class' => '')); if ($tax_rates) { foreach ($tax_rates as $key => $rate) { if (isset($rate['shipping']) && $rate['shipping'] == 'yes') { $matched_tax_rates[$key] = $rate; } } } $shipping_taxes = $tax->calc_shipping_tax($shipping, $matched_tax_rates); $shipping_tax = $tax->round(array_sum($shipping_taxes)); $return['recurring_shipping_tax'] = $shipping_tax; // Remove old tax rows $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = 'recurring_tax' )", $order_id)); $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = 'recurring_tax'", $order_id)); // Get tax rates $rates = $wpdb->get_results("SELECT tax_rate_id, tax_rate_country, tax_rate_state, tax_rate_name, tax_rate_priority FROM {$wpdb->prefix}woocommerce_tax_rates ORDER BY tax_rate_name"); $tax_codes = array(); foreach ($rates as $rate) { $code = array(); $code[] = $rate->tax_rate_country; $code[] = $rate->tax_rate_state; $code[] = $rate->tax_rate_name ? sanitize_title($rate->tax_rate_name) : 'TAX'; $code[] = absint($rate->tax_rate_priority); $tax_codes[$rate->tax_rate_id] = strtoupper(implode('-', array_filter($code))); } // Now merge to keep tax rows ob_start(); foreach (array_keys($taxes + $shipping_taxes) as $key) { $item = array(); $item['rate_id'] = $key; $item['name'] = $tax_codes[$key]; $item['label'] = $tax->get_rate_label($key); $item['compound'] = $tax->is_compound($key) ? 1 : 0; $item['tax_amount'] = $tax->round(isset($taxes[$key]) ? $taxes[$key] : 0); $item['shipping_tax_amount'] = $tax->round(isset($shipping_taxes[$key]) ? $shipping_taxes[$key] : 0); if (!$item['label']) { $item['label'] = $woocommerce->countries->tax_or_vat(); } // Add line item $item_id = woocommerce_add_order_item($order_id, array('order_item_name' => $item['name'], 'order_item_type' => 'recurring_tax')); // Add line item meta if ($item_id) { woocommerce_add_order_item_meta($item_id, 'rate_id', $item['rate_id']); woocommerce_add_order_item_meta($item_id, 'label', $item['label']); woocommerce_add_order_item_meta($item_id, 'compound', $item['compound']); woocommerce_add_order_item_meta($item_id, 'tax_amount', $item['tax_amount']); woocommerce_add_order_item_meta($item_id, 'shipping_tax_amount', $item['shipping_tax_amount']); } include plugin_dir_path(WC_Subscriptions::$plugin_file) . 'templates/admin/post-types/writepanels/order-tax-html.php'; } $return['tax_row_html'] = ob_get_clean(); echo json_encode($return); } die; }
/** * Adjust the product ID that grants access to a membership plan on purchase * * Subscription products take priority over all other products * * @since 1.0.0 * @param int $product_id Product ID * @param array $access_granting_product_ids Array of product IDs * @param WC_Memberships_Membership_Plan $plan * @return int Product ID, adjusted if necessary */ public function adjust_access_granting_product_id($product_id, $access_granting_product_ids, WC_Memberships_Membership_Plan $plan) { // Check if more than one products may grant access, and if the plan even // allows access while subscription is active if (count($access_granting_product_ids) > 1 && $this->plan_grants_access_while_subscription_active($plan->get_id())) { // First, find all subscription products that grant access $access_granting_subscription_product_ids = array(); foreach ($access_granting_product_ids as $_product_id) { $product = wc_get_product($_product_id); if ($product->is_type(array('subscription', 'subscription_variation', 'variable-subscription'))) { $access_granting_subscription_product_ids[] = $product->id; } } // If there are any, decide which one actually gets to grant access if (!empty($access_granting_subscription_product_ids)) { // Only one subscription grants access, short-circuit it as the winner if (count($access_granting_subscription_product_ids) == 1) { $product_id = $access_granting_subscription_product_ids[0]; } else { $longest_expiration_date = 0; foreach ($access_granting_subscription_product_ids as $_subscription_product_id) { $expiration_date = WC_Subscriptions_Product::get_expiration_date($_subscription_product_id); // No expiration date? Ladies and gentlemen - we've got a winner! if (!$expiration_date) { $product_id = $_subscription_product_id; break; } // This one beats the previous sub! Out of the way, you sub-optimal sucker, you! if (strtotime($expiration_date) > $longest_expiration_date) { $product_id = $_subscription_product_id; $longest_expiration_date = strtotime($expiration_date); } } } } } return $product_id; }
/** * Removes the "set_subscription_prices_for_calculation" filter from the WC Product's woocommerce_get_price hook once * * @since 1.5.10 */ public static function set_prorated_price_for_calculation($price, $product) { if (WC_Subscriptions_Product::is_subscription($product) && self::is_product_prorated($product) && in_array(WC_Subscriptions_Cart::get_calculation_type(), array('combined_total', 'none'))) { $next_payment_date = self::calculate_first_payment_date($product, 'timestamp'); if (self::is_today($next_payment_date)) { return $price; } switch ($product->subscription_period) { case 'week': $days_in_cycle = 7 * $product->subscription_period_interval; break; case 'month': $days_in_cycle = date('t') * $product->subscription_period_interval; break; case 'year': $days_in_cycle = (365 + date('L')) * $product->subscription_period_interval; break; } $days_until_next_payment = ceil(($next_payment_date - gmdate('U')) / (60 * 60 * 24)); if ('combined_total' == WC_Subscriptions_Cart::get_calculation_type()) { $sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee($product); if ($sign_up_fee > 0 && 0 == WC_Subscriptions_Product::get_trial_length($product)) { $price = $sign_up_fee + $days_until_next_payment * (($price - $sign_up_fee) / $days_in_cycle); } } elseif ('none' == WC_Subscriptions_Cart::get_calculation_type()) { $price = $days_until_next_payment * ($price / $days_in_cycle); } } return $price; }
/** * If a product is being marked as not purchasable because it is limited and the customer has a subscription, * but the current request is to switch the subscription, then mark it as purchasable. * * @since 1.4.4 * @return bool */ public static function is_purchasable($is_purchasable, $product) { global $woocommerce; if (false === $is_purchasable && WC_Subscriptions_Product::is_subscription($product->id) && 'yes' === $product->limit_subscriptions && WC_Subscriptions_Manager::user_has_subscription(0, $product->id, 'active')) { // Adding to cart from the product page if (isset($_GET['switch-subscription'])) { $is_purchasable = true; // Validating when restring cart from session } elseif (self::cart_contains_subscription_switch()) { $is_purchasable = true; // Restoring cart from session, so need to check the cart in the session (self::cart_contains_subscription_switch() only checks the cart) } elseif (isset($woocommerce->session->cart)) { foreach ($woocommerce->session->cart as $cart_item_key => $cart_item) { if (isset($cart_item['subscription_switch'])) { $is_purchasable = true; break; } } } } return $is_purchasable; }
/** * Apply sign up fee or recurring fee discount * * @since 1.2 */ public static function apply_subscription_discount($original_price, $cart_item, $cart) { $product_id = $cart_item['data']->is_type(array('subscription_variation')) ? $cart_item['data']->variation_id : $cart_item['data']->id; if (!WC_Subscriptions_Product::is_subscription($product_id)) { return $original_price; } $price = $calculation_price = $original_price; $calculation_type = WC_Subscriptions_Cart::get_calculation_type(); if (!empty($cart->applied_coupons)) { foreach ($cart->applied_coupons as $code) { $coupon = new WC_Coupon($code); if ($coupon->apply_before_tax() && $coupon->is_valid()) { $apply_recurring_coupon = $apply_recurring_percent_coupon = $apply_initial_coupon = $apply_initial_percent_coupon = false; // Apply recurring fee discounts to recurring total calculations if ('recurring_total' == $calculation_type) { $apply_recurring_coupon = 'recurring_fee' == $coupon->type ? true : false; $apply_recurring_percent_coupon = 'recurring_percent' == $coupon->type ? true : false; } if ('none' == $calculation_type) { // If all items have a free trial we don't need to apply recurring coupons to the initial total if (!WC_Subscriptions_Cart::all_cart_items_have_free_trial()) { if ('recurring_fee' == $coupon->type) { $apply_initial_coupon = true; } if ('recurring_percent' == $coupon->type) { $apply_initial_percent_coupon = true; } } // Apply sign-up discounts to initial total if (!empty($cart_item['data']->subscription_sign_up_fee)) { if ('sign_up_fee' == $coupon->type) { $apply_initial_coupon = true; } if ('sign_up_fee_percent' == $coupon->type) { $apply_initial_percent_coupon = true; } $calculation_price = $cart_item['data']->subscription_sign_up_fee; } } if ($apply_recurring_coupon || $apply_initial_coupon) { $discount_amount = $calculation_price < $coupon->amount ? $calculation_price : $coupon->amount; // Recurring coupons only apply when there is no free trial (carts can have a mix of free trial and non free trial items) if ($apply_initial_coupon && 'recurring_fee' == $coupon->type && !empty($cart_item['data']->subscription_trial_length)) { $discount_amount = 0; } $cart->discount_cart = $cart->discount_cart + $discount_amount * $cart_item['quantity']; $cart = self::increase_coupon_discount_amount($cart, $coupon->code, $discount_amount * $cart_item['quantity']); $price = $price - $discount_amount; } elseif ($apply_recurring_percent_coupon) { $discount_amount = round($calculation_price / 100 * $coupon->amount, WC()->cart->dp); $cart->discount_cart = $cart->discount_cart + $discount_amount * $cart_item['quantity']; $cart = self::increase_coupon_discount_amount($cart, $coupon->code, $discount_amount * $cart_item['quantity']); $price = $price - $discount_amount; } elseif ($apply_initial_percent_coupon) { // Recurring coupons only apply when there is no free trial (carts can have a mix of free trial and non free trial items) if ('recurring_percent' == $coupon->type && empty($cart_item['data']->subscription_trial_length)) { $amount_to_discount = $cart_item['data']->subscription_price; } else { $amount_to_discount = 0; } // Sign up fee coupons only apply to sign up fees if ('sign_up_fee_percent' == $coupon->type) { $amount_to_discount = $cart_item['data']->subscription_sign_up_fee; } $discount_amount = round($amount_to_discount / 100 * $coupon->amount, WC()->cart->dp); $cart->discount_cart = $cart->discount_cart + $discount_amount * $cart_item['quantity']; $cart = self::increase_coupon_discount_amount($cart, $coupon->code, $discount_amount * $cart_item['quantity']); $price = $price - $discount_amount; } } } if ($price < 0) { $price = 0; } } return $price; }
/** * Save a variable subscription's details when the edit product page is submitted for a variable * subscription product type (or the bulk edit product is saved). * * @param $post_id int ID of the parent WC_Product_Variable_Subscription * @return null * @since 1.3 */ public static function process_product_meta_variable_subscription($post_id) { if (!WC_Subscriptions_Product::is_subscription($post_id)) { return; } // Make sure WooCommerce calculates correct prices $_POST['variable_regular_price'] = $_POST['variable_subscription_price']; // Run WooCommerce core saving routine process_product_meta_variable($post_id); if (!isset($_REQUEST['_subscription_limit'])) { $_REQUEST['_subscription_limit'] = 'no'; } update_post_meta($post_id, '_subscription_limit', stripslashes($_REQUEST['_subscription_limit'])); if (!isset($_REQUEST['variable_post_id'])) { return; } $variable_post_ids = $_POST['variable_post_id']; $max_loop = max(array_keys($variable_post_ids)); // Save each variations details for ($i = 0; $i <= $max_loop; $i++) { if (!isset($variable_post_ids[$i])) { continue; } $variation_id = absint($variable_post_ids[$i]); if (isset($_POST['variable_subscription_price']) && is_array($_POST['variable_subscription_price'])) { $subscription_price = self::clean_number(woocommerce_clean($_POST['variable_subscription_price'][$i])); update_post_meta($variation_id, '_subscription_price', $subscription_price); update_post_meta($variation_id, '_regular_price', $subscription_price); } // Make sure trial period is within allowable range $subscription_ranges = WC_Subscriptions_Manager::get_subscription_ranges(); $max_trial_length = count($subscription_ranges[$_POST['variable_subscription_trial_period'][$i]]) - 1; $_POST['variable_subscription_trial_length'][$i] = absint($_POST['variable_subscription_trial_length'][$i]); if ($_POST['variable_subscription_trial_length'][$i] > $max_trial_length) { $_POST['variable_subscription_trial_length'][$i] = $max_trial_length; } $subscription_fields = array('_subscription_sign_up_fee', '_subscription_period', '_subscription_period_interval', '_subscription_length', '_subscription_trial_period', '_subscription_trial_length'); foreach ($subscription_fields as $field_name) { if (isset($_POST['variable' . $field_name][$i])) { update_post_meta($variation_id, $field_name, woocommerce_clean($_POST['variable' . $field_name][$i])); } } } // Now that all the varation's meta is saved, sync the min variation price $variable_subscription = get_product($post_id); $variable_subscription->variable_product_sync(); }
/** * Save a variable subscription's details when the edit product page is submitted for a variable * subscription product type (or the bulk edit product is saved). * * @param int $post_id ID of the parent WC_Product_Variable_Subscription * @return null * @since 1.3 */ public static function process_product_meta_variable_subscription($post_id) { if (!WC_Subscriptions_Product::is_subscription($post_id) || empty($_POST['_wcsnonce_save_variations']) || !wp_verify_nonce($_POST['_wcsnonce_save_variations'], 'wcs_subscription_variations')) { return; } // Make sure WooCommerce calculates correct prices $_POST['variable_regular_price'] = isset($_POST['variable_subscription_price']) ? $_POST['variable_subscription_price'] : 0; // Run WooCommerce core saving routine for WC < 2.4 if (!is_ajax()) { WC_Meta_Box_Product_Data::save_variations($post_id, get_post($post_id)); } if (!isset($_REQUEST['variable_post_id'])) { return; } $variable_post_ids = $_POST['variable_post_id']; $max_loop = max(array_keys($variable_post_ids)); // Save each variations details for ($i = 0; $i <= $max_loop; $i++) { if (!isset($variable_post_ids[$i])) { continue; } $variation_id = absint($variable_post_ids[$i]); if (isset($_POST['variable_subscription_price']) && is_array($_POST['variable_subscription_price'])) { $subscription_price = wc_format_decimal($_POST['variable_subscription_price'][$i]); update_post_meta($variation_id, '_subscription_price', $subscription_price); update_post_meta($variation_id, '_regular_price', $subscription_price); } // Make sure trial period is within allowable range $subscription_ranges = wcs_get_subscription_ranges(); $max_trial_length = count($subscription_ranges[$_POST['variable_subscription_trial_period'][$i]]) - 1; $_POST['variable_subscription_trial_length'][$i] = absint($_POST['variable_subscription_trial_length'][$i]); if ($_POST['variable_subscription_trial_length'][$i] > $max_trial_length) { $_POST['variable_subscription_trial_length'][$i] = $max_trial_length; } // Work around a WPML bug which means 'variable_subscription_trial_period' is not set when using "Edit Product" as the product translation interface if ($_POST['variable_subscription_trial_length'][$i] < 0) { $_POST['variable_subscription_trial_length'][$i] = 0; } $subscription_fields = array('_subscription_sign_up_fee', '_subscription_period', '_subscription_period_interval', '_subscription_length', '_subscription_trial_period', '_subscription_trial_length'); foreach ($subscription_fields as $field_name) { if (isset($_POST['variable' . $field_name][$i])) { update_post_meta($variation_id, $field_name, wc_clean($_POST['variable' . $field_name][$i])); } } } // Now that all the variation's meta is saved, sync the min variation price $variable_subscription = wc_get_product($post_id); $variable_subscription->variable_product_sync(); }
/** * Gets the subscription sign up fee for the cart and returns it * * Currently short-circuits to return just the sign-up fee of the first subscription, because only * one subscription can be purchased at a time. * * @since 1.0 */ public static function get_cart_subscription_sign_up_fee() { global $woocommerce; $sign_up_fee = 0; if (!self::cart_contains_subscription_renewal()) { foreach ($woocommerce->cart->cart_contents as $cart_item) { $item_id = empty($cart_item['variation_id']) ? $cart_item['product_id'] : $cart_item['variation_id']; if (isset($cart_item['data']->subscription_sign_up_fee)) { $sign_up_fee = $cart_item['data']->subscription_sign_up_fee; break; } elseif (WC_Subscriptions_Product::is_subscription($item_id)) { $sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee($item_id); break; } } } return apply_filters('woocommerce_subscriptions_cart_sign_up_fee', $sign_up_fee); }
/** * Apply sign up fee or recurring fee discount after tax is calculated * * @since 1.2 * @version 1.3.6 */ public static function apply_subscription_discount_after_tax($coupon, $cart_item, $price) { global $woocommerce; $calculation_type = WC_Subscriptions_Cart::get_calculation_type(); if (sizeof($woocommerce->cart->cart_contents) > 0) { foreach ($woocommerce->cart->cart_contents as $cart_item_key => $cart_item) { if (!WC_Subscriptions_Product::is_subscription($cart_item['product_id'])) { continue; } if (!$coupon->apply_before_tax() && $coupon->is_valid() && self::is_subscription_discountable($cart_item, $coupon)) { $apply_sign_up_coupon = $apply_sign_up_percent_coupon = $apply_recurring_coupon = $apply_recurring_percent_coupon = $apply_initial_coupon = $apply_initial_percent_coupon = false; if ('sign_up_fee_total' == $calculation_type) { $apply_sign_up_coupon = 'sign_up_fee' == $coupon->type ? true : false; $apply_sign_up_percent_coupon = 'sign_up_fee_percent' == $coupon->type ? true : false; } elseif ('recurring_total' == $calculation_type) { $apply_recurring_coupon = 'recurring_fee' == $coupon->type ? true : false; $apply_recurring_percent_coupon = 'recurring_percent' == $coupon->type ? true : false; } if (in_array($calculation_type, array('combined_total', 'none'))) { if (!WC_Subscriptions_Cart::cart_contains_free_trial()) { // Apply recurring discounts to initial total if ('recurring_fee' == $coupon->type) { $apply_initial_coupon = true; } if ('recurring_percent' == $coupon->type) { $apply_initial_percent_coupon = true; } } if (WC_Subscriptions_Cart::get_cart_subscription_sign_up_fee() > 0) { // Apply sign-up discounts to initial total if ('sign_up_fee' == $coupon->type) { $apply_initial_coupon = true; } if ('sign_up_fee_percent' == $coupon->type) { $apply_initial_percent_coupon = true; } } } // Deduct coupon amounts if ($apply_sign_up_coupon || $apply_recurring_coupon || $apply_initial_coupon) { if ($price < $coupon->amount) { $discount_amount = $price; } else { $discount_amount = $coupon->amount; } $woocommerce->cart->discount_total = $woocommerce->cart->discount_total + $discount_amount * $cart_item['quantity']; WC_Subscriptions_Cart::increase_coupon_discount_amount($coupon->code, $discount_amount * $cart_item['quantity']); // Deduct coupon % discounts from relevant total } elseif ($apply_sign_up_percent_coupon || $apply_recurring_percent_coupon) { $woocommerce->cart->discount_total = $woocommerce->cart->discount_total + round($price / 100 * $coupon->amount, $woocommerce->cart->dp); WC_Subscriptions_Cart::increase_coupon_discount_amount($coupon->code, round($price / 100 * $coupon->amount, $woocommerce->cart->dp)); // Deduct coupon % discounts from combined total (we need to calculate percent from base price) } elseif ($apply_initial_percent_coupon) { $product_id = $cart_item['data']->is_type(array('subscription_variation')) ? $cart_item['data']->variation_id : $cart_item['data']->id; // We need to calculate the right amount to discount when the price is the combined sign-up fee and recurring amount if ('combined_total' == $calculation_type && !WC_Subscriptions_Cart::cart_contains_free_trial() && isset($woocommerce->cart->base_sign_up_fees[$product_id]) && $woocommerce->cart->base_sign_up_fees[$product_id] > 0) { $base_total = $woocommerce->cart->base_sign_up_fees[$product_id] + $woocommerce->cart->base_recurring_prices[$product_id]; if ('recurring_percent' == $coupon->type) { $portion_of_total = $woocommerce->cart->base_recurring_prices[$product_id] / $base_total; } if ('sign_up_fee_percent' == $coupon->type) { $portion_of_total = $woocommerce->cart->base_sign_up_fees[$product_id] / $base_total; } $amount_to_discount = WC_Subscriptions_Manager::get_amount_from_proportion($price, $portion_of_total); } else { $amount_to_discount = $price; } $discount_amount = round($amount_to_discount / 100 * $coupon->amount, $woocommerce->cart->dp); $woocommerce->cart->discount_total = $woocommerce->cart->discount_total + $discount_amount; WC_Subscriptions_Cart::increase_coupon_discount_amount($coupon->code, $discount_amount); } } } } }
private function processSubscriptions() { global $wpdb; // check wether subscriptions addon is activated if (class_exists('WC_Subscriptions_Order') && WC_Subscriptions_Order::order_contains_subscription($this->order)) { $products = $this->order->get_items(); foreach ($products as $product) { if (is_array($product) && isset($product['product_id']) && intval($product['product_id']) > 0 && isset($product['subscription_period']) && $product['subscription_period'] != '') { // product is a subscription? $woo_sub_key = WC_Subscriptions_Manager::get_subscription_key($this->order_id, $product['product_id']); // required vars $amount = floatval(WC_Subscriptions_Order::get_recurring_total($this->order)) * 100; $currency = get_woocommerce_currency(); $interval = intval($product['subscription_interval']); $period = strtoupper($product['subscription_period']); $length = strtoupper($product['subscription_length']); if ($length > 0) { $periodOfValidity = $length . ' ' . $period; } else { $periodOfValidity = false; } $trial_end = strtotime(WC_Subscriptions_Product::get_trial_expiration_date($product['product_id'], get_gmt_from_date($this->order->order_date))); if ($trial_end === false) { $trial_time = 0; } else { $datediff = $trial_end - time(); $trial_time = ceil($datediff / (60 * 60 * 24)); } // md5 name $woo_sub_md5 = md5($amount . $currency . $interval . $trial_time); // get offer $name = 'woo_' . $product['product_id'] . '_' . $woo_sub_md5; $offer = $this->subscriptions->offerGetDetailByName($name); // check wether offer exists in paymill if ($offer === false) { // offer does not exist in paymill yet, create it $params = array('amount' => $amount, 'currency' => $currency, 'interval' => $interval . ' ' . $period, 'name' => $name, 'trial_period_days' => intval($trial_time)); $offer = $this->subscriptions->offerCreate($params); if ($GLOBALS['paymill_loader']->paymill_errors->status()) { $GLOBALS['paymill_loader']->paymill_errors->getErrors(); return false; } } // create user subscription $user_sub = $this->subscriptions->create($this->clientClass->getCurrentClientID(), $offer, $this->paymentClass->getPaymentID(), isset($_POST['paymill_delivery_date']) ? $_POST['paymill_delivery_date'] : false, $periodOfValidity); if ($GLOBALS['paymill_loader']->paymill_errors->status()) { //maybe offer cache is outdated, recache and try again $GLOBALS['paymill_loader']->paymill_errors->reset(); // reset error status $this->subscriptions->offerGetList(true); $params = array('amount' => $amount, 'currency' => $currency, 'interval' => $interval . ' ' . $period, 'name' => $name, 'trial_period_days' => intval($trial_time)); $offer = $this->subscriptions->offerCreate($params); if ($GLOBALS['paymill_loader']->paymill_errors->status()) { $GLOBALS['paymill_loader']->paymill_errors->getErrors(); return false; } $user_sub = $this->subscriptions->create($this->clientClass->getCurrentClientID(), $offer, $this->paymentClass->getPaymentID(), isset($_POST['paymill_delivery_date']) ? $_POST['paymill_delivery_date'] : false, $periodOfValidity); if ($GLOBALS['paymill_loader']->paymill_errors->status()) { $GLOBALS['paymill_loader']->paymill_errors->getErrors(); return false; } } $wpdb->query($wpdb->prepare('INSERT INTO ' . $wpdb->prefix . 'paymill_subscriptions (paymill_sub_id, woo_user_id, woo_offer_id) VALUES (%s, %s, %s)', array($user_sub, get_current_user_id(), $woo_sub_key))); // subscription successful do_action('paymill_woocommerce_subscription_created', array('product_id' => $product['product_id'], 'offer_id' => $offer)); return true; } } } else { return true; } }
/** * Groups tab content. */ public static function product_write_panels() { global $post, $wpdb, $woocommerce; echo '<div id="woocommerce_groups" class="panel woocommerce_options_panel" style="padding: 1em;">'; if (class_exists('WC_Subscriptions_Product') && WC_Subscriptions_Product::is_subscription($post->ID)) { echo '<p>' . __('The customer will be a member of the selected groups as long as the subscription is active. The customer will be removed from the selected groups once the subscription is active.', GROUPS_WS_PLUGIN_DOMAIN) . '</p>'; } else { echo '<p>' . __('The customer will be added to or removed from the selected groups when purchasing this product.', GROUPS_WS_PLUGIN_DOMAIN) . '</p>'; } $product_groups = get_post_meta($post->ID, '_groups_groups', false); $product_groups_remove = get_post_meta($post->ID, '_groups_groups_remove', false); $group_table = _groups_get_tablename("group"); $groups = $wpdb->get_results("SELECT * FROM {$group_table} ORDER BY name"); $n = 0; if (count($groups) > 0) { echo '<table class="widefat" style="width:50%">'; echo '<thead>'; echo '<tr>'; echo '<th style="width:50%">' . __('Group', GROUPS_WS_PLUGIN_DOMAIN) . '</th>'; echo '<th style="width:25%">' . __('Add', GROUPS_WS_PLUGIN_DOMAIN) . '</th>'; echo '<th style="width:25%">' . __('Remove', GROUPS_WS_PLUGIN_DOMAIN) . '</th>'; echo '</tr>'; echo '</thead>'; echo '<tbody>'; foreach ($groups as $group) { if ($group->name !== Groups_Registered::REGISTERED_GROUP_NAME) { echo '<tr>'; echo '<th>' . wp_filter_nohtml_kses($group->name) . '</th>'; echo '<td>'; woocommerce_wp_checkbox(array('id' => '_groups_groups-' . esc_attr($group->group_id), 'label' => '', 'value' => in_array($group->group_id, $product_groups) ? 'yes' : '')); echo '</td>'; echo '<td>'; woocommerce_wp_checkbox(array('id' => '_groups_groups_remove-' . esc_attr($group->group_id), 'label' => '', 'value' => in_array($group->group_id, $product_groups_remove) ? 'yes' : '')); echo '</td>'; echo '</tr>'; $n++; } } echo '</tbody>'; echo '</table>'; if (!class_exists('WC_Subscriptions_Product') || !WC_Subscriptions_Product::is_subscription($post->ID)) { $duration = get_post_meta($post->ID, '_groups_duration', true); $duration_uom = get_post_meta($post->ID, '_groups_duration_uom', true); if (empty($duration_uom)) { $duration_uom = 'month'; } switch ($duration_uom) { case 'second': $duration_uom_label = _n('Second', 'Seconds', $duration, GROUPS_WS_PLUGIN_DOMAIN); break; case 'minute': $duration_uom_label = _n('Minute', 'Minutes', $duration, GROUPS_WS_PLUGIN_DOMAIN); break; case 'hour': $duration_uom_label = _n('Hour', 'Hours', $duration, GROUPS_WS_PLUGIN_DOMAIN); break; case 'day': $duration_uom_label = _n('Day', 'Days', $duration, GROUPS_WS_PLUGIN_DOMAIN); break; case 'week': $duration_uom_label = _n('Week', 'Weeks', $duration, GROUPS_WS_PLUGIN_DOMAIN); break; case 'year': $duration_uom_label = _n('Year', 'Years', $duration, GROUPS_WS_PLUGIN_DOMAIN); break; default: $duration_uom_label = _n('Month', 'Months', $duration, GROUPS_WS_PLUGIN_DOMAIN); break; } $duration_help = __('Leave the duration empty unless you want memberships to end after a certain amount of time.', GROUPS_WS_PLUGIN_DOMAIN) . ' ' . __('If the duration is empty, the customer will remain a member of the selected groups forever, unless removed explicitly.', GROUPS_WS_PLUGIN_DOMAIN) . ' ' . __('If the duration is set, the customer will only belong to the selected groups during the specified time, based on the <em>Duration</em> and the <em>Time unit</em>.', GROUPS_WS_PLUGIN_DOMAIN); $duration_help_icon = '<img class="help_tip" data-tip="' . esc_attr($duration_help) . '" src="' . $woocommerce->plugin_url() . '/assets/images/help.png" />'; woocommerce_wp_text_input(array('id' => '_groups_duration', 'label' => sprintf(__('Duration %s', GROUPS_WS_PLUGIN_DOMAIN), $duration_help_icon), 'value' => $duration, 'description' => sprintf(__('%s (as chosen under <em>Time unit</em>)', GROUPS_WS_PLUGIN_DOMAIN), $duration_uom_label), 'placeholder' => __('unlimited', GROUPS_WS_PLUGIN_DOMAIN))); woocommerce_wp_select(array('id' => '_groups_duration_uom', 'label' => __('Time unit', GROUPS_WS_PLUGIN_DOMAIN), 'value' => $duration_uom, 'options' => array('second' => __('Seconds', GROUPS_WS_PLUGIN_DOMAIN), 'minute' => __('Minutes', GROUPS_WS_PLUGIN_DOMAIN), 'hour' => __('Hours', GROUPS_WS_PLUGIN_DOMAIN), 'day' => __('Days', GROUPS_WS_PLUGIN_DOMAIN), 'week' => __('Weeks', GROUPS_WS_PLUGIN_DOMAIN), 'month' => __('Months', GROUPS_WS_PLUGIN_DOMAIN), 'year' => __('Years', GROUPS_WS_PLUGIN_DOMAIN)))); echo '<noscript>' . '<p>' . $duration_help . '</p>' . '</noscript>'; } } if ($n == 0) { echo '<p>' . __('There are no groups available to select. At least one group (other than <em>Registered</em>) must be created.', GROUPS_WS_PLUGIN_DOMAIN) . '</p>'; } echo '<p>' . __('Note that all users belong to the <em>Registered</em> group automatically.', GROUPS_WS_PLUGIN_DOMAIN) . '</p>'; echo '<br/>'; echo '</div>'; }
* @since 1.0 * @deprecated 1.5.18 */ public static function get_price_html($price, $product) { _deprecated_function(__METHOD__, '1.5.18', __CLASS__ . '::get_price_string()'); if (self::is_subscription($product)) { $price = self::get_price_string($product, array('price' => $price)); } return $price; } /** * Deprecated in favour of native get_price_html() method on the Subscription Product classes (e.g. WC_Product_Subscription) * * Set the subscription string for products which have a $0 recurring fee, but a sign-up fee * * @since 1.3.4 * @deprecated 1.5.18 */ public static function get_free_price_html($price, $product) { _deprecated_function(__METHOD__, '1.5.18', __CLASS__ . '::get_price_string()'); // Check if it has a sign-up fee (we already know it has no recurring fee) if (self::is_subscription($product) && self::get_sign_up_fee($product) > 0) { $price = self::get_price_string($product, array('price' => $price)); } return $price; } } WC_Subscriptions_Product::init();
/** * Save subscription details when edit product page is submitted for a subscription product type * or the bulk edit product is saved. * * The $_REQUEST global is used to account for both $_GET submission from Bulk Edit page and * $_POST submission from Edit Product page. * * @param array Array of Product types & their labels, excluding the Subscription product type. * @return array Array of Product types & their labels, including the Subscription product type. * @since 1.0 */ public static function save_subscription_meta($post_id) { if (!WC_Subscriptions_Product::is_subscription($post_id)) { return; } if (isset($_REQUEST['_subscription_price'])) { update_post_meta($post_id, '_subscription_price', stripslashes($_REQUEST['_subscription_price'])); // Set sale details - these are ignored by WC core for the subscription product type update_post_meta($post_id, '_regular_price', stripslashes($_POST['_subscription_price'])); update_post_meta($post_id, '_sale_price', stripslashes($_POST['_sale_price'])); $date_from = isset($_POST['_sale_price_dates_from']) ? strtotime($_POST['_sale_price_dates_from']) : ''; $date_to = isset($_POST['_sale_price_dates_to']) ? strtotime($_POST['_sale_price_dates_to']) : ''; $now = strtotime('NOW', current_time('timestamp')); if (!empty($date_to) && empty($date_from)) { $date_from = $now; } update_post_meta($post_id, '_sale_price_dates_from', $date_from); update_post_meta($post_id, '_sale_price_dates_to', $date_to); // Update price if on sale if (!empty($_POST['_sale_price']) && (empty($date_to) && empty($date_from) || $date_from < $now && (empty($date_to) || $date_to > $now))) { $price = $_POST['_sale_price']; } else { $price = $_POST['_subscription_price']; } update_post_meta($post_id, '_price', stripslashes($price)); // Make sure trial period is within allowable range $subscription_ranges = WC_Subscriptions_Manager::get_subscription_ranges(); $max_trial_length = count($subscription_ranges[$_POST['_subscription_trial_period']]) - 1; $_POST['_subscription_trial_length'] = absint($_POST['_subscription_trial_length']); if ($_POST['_subscription_trial_length'] > $max_trial_length) { $_POST['_subscription_trial_length'] = $max_trial_length; } update_post_meta($post_id, '_subscription_trial_length', $_POST['_subscription_trial_length']); } else { // Handle bulk edit, where _subscription_price field is not available if (isset($_REQUEST['_regular_price']) && !empty($_REQUEST['_regular_price'])) { update_post_meta($post_id, '_subscription_price', stripslashes($_REQUEST['_regular_price'])); update_post_meta($post_id, '_price', stripslashes($_REQUEST['_regular_price'])); } } $subscription_fields = array('_subscription_sign_up_fee', '_subscription_period', '_subscription_period_interval', '_subscription_length', '_subscription_trial_period'); foreach ($subscription_fields as $field_name) { if (isset($_REQUEST[$field_name])) { update_post_meta($post_id, $field_name, stripslashes($_REQUEST[$field_name])); } } }
/** * When an order is added or updated from the admin interface, check if a new subscription product * has been manually added to the order, and if one has, create a new subscription. * * @param $post_id int The ID of the post which is the WC_Order object. * @param $post Object The post object of the order. * @since 1.1 */ public static function maybe_manually_change_subscriptions($post_id, $post) { $order = new WC_Order($post_id); // Check if all the subscription products on the order have associated subscriptions on the user's account, and if not, add a new one foreach ($_POST['item_id'] as $item_id) { if (!WC_Subscriptions_Product::is_subscription($item_id)) { continue; } $subscription_key = WC_Subscriptions_Manager::get_subscription_key($post_id, $item_id); $subscription = array(); // If order customer changed, move the subscription from the old customer's account to the new customer if (!empty($order->customer_user) && $order->customer_user != (int) $_POST['customer_user']) { $subscription = WC_Subscriptions_Manager::remove_users_subscription($order->customer_user, $subscription_key); $subscriptions = WC_Subscriptions_Manager::get_users_subscriptions((int) $_POST['customer_user']); if (!empty($subscription)) { $subscriptions[$subscription_key] = $subscription; WC_Subscriptions_Manager::update_users_subscriptions((int) $_POST['customer_user'], $subscriptions); } } // In case it's a new order or the customer has changed $order->customer_user = $order->user_id = (int) $_POST['customer_user']; $subscription = WC_Subscriptions_Manager::get_users_subscription($order->customer_user, $subscription_key); if (empty($subscription)) { // Add a new subscription // The order doesn't may not exist yet, so we need to set a few things ourselves $order->order_key = uniqid('order_'); add_post_meta($post_id, '_order_key', $order->order_key, true); WC_Subscriptions_Manager::create_pending_subscription_for_order($order, $item_id); // Add the subscription meta for this item to the order $functions_and_meta = array('get_period' => '_order_subscription_periods', 'get_interval' => '_order_subscription_intervals', 'get_length' => '_order_subscription_lengths'); foreach ($functions_and_meta as $function_name => $meta_key) { $subscription_meta = self::get_meta($order, $meta_key, array()); $subscription_meta[$item_id] = WC_Subscriptions_Product::$function_name($item_id); update_post_meta($order->id, $meta_key, $subscription_meta); } // Set the subscription's status if it should be something other than pending switch ($order->status) { case 'completed': case 'processing': WC_Subscriptions_Manager::activate_subscription($order->customer_user, $subscription_key); break; case 'refunded': case 'cancelled': WC_Subscriptions_Manager::cancel_subscription($order->customer_user, $subscription_key); break; case 'failed': WC_Subscriptions_Manager::failed_subscription_signup($order->customer_user, $subscription_key); break; } } } }
/** * Checks if the store manager has requested the current product be limited to one purchase * per customer, and if so, checks whether the customer already has an active subscription to * the product. * * @access public * @return bool */ function is_purchasable() { $purchasable = parent::is_purchasable(); if (true === $purchasable && false === WC_Subscriptions_Product::is_purchasable($purchasable, $this)) { $purchasable = false; } return apply_filters('woocommerce_subscription_is_purchasable', $purchasable, $this); }
/** * 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 $order mixed int | WC_Order The order ID or WC_Order object to create the subscription from. * @param $product_id int The ID of the subscription product on the order. * @param $args array 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()) { 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' => '', 'expiry_date' => '')); $subscription_key = self::get_subscription_key($order->id, $product_id); // In case the subscription exists already $subscription = self::get_users_subscription($order->customer_user, $subscription_key); // Adding a new subscription so set the start date/time to now if (!empty($args['start_date'])) { if (is_numeric($args['start_date'])) { $args['start_date'] = date('Y-m-d H:i:s', $args['start_date']); } $start_date = $args['start_date']; } else { $start_date = isset($subscription['start_date']) ? $subscription['start_date'] : gmdate('Y-m-d H:i:s'); } // 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 = isset($subscription['expiry_date']) ? $subscription['expiry_date'] : WC_Subscriptions_Product::get_expiration_date($product_id, $start_date); } // Adding a new subscription so set the expiry date/time from the order date $trial_expiration = isset($subscription['trial_expiry_date']) ? $subscription['trial_expiry_date'] : WC_Subscriptions_Product::get_trial_expiration_date($product_id, $start_date); $failed_payments = isset($subscription['failed_payments']) ? $subscription['failed_payments'] : 0; $completed_payments = isset($subscription['completed_payments']) ? $subscription['completed_payments'] : array(); $subscriptions[$subscription_key] = array('product_id' => $product_id, 'order_key' => $order->order_key, 'order_id' => $order->id, 'start_date' => $start_date, 'expiry_date' => $expiration, 'end_date' => 0, 'status' => 'pending', 'trial_expiry_date' => $trial_expiration, 'failed_payments' => $failed_payments, 'completed_payments' => $completed_payments); self::update_users_subscriptions($order->customer_user, $subscriptions); $product = WC_Subscriptions::get_product($product_id); // Set subscription status to active and log activation $order->add_order_note(sprintf(__('Pending subscription created for "%s".', WC_Subscriptions::$text_domain), $product->get_title())); do_action('pending_subscription_created_for_order', $order, $product_id); }
/** * 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); }