weekday in some places protected static $weekdays = array( 1 => 'Monday', 2 => 'Tuesday', 3 => 'Wednesday', 4 => 'Thursday', 5 => 'Friday', 6 => 'Saturday', 7 => 'Sunday', ); /** * Bootstraps the class and hooks required actions & filters. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.5 */ public static function init() { self::$setting_id = WC_Subscriptions_Admin::$option_prefix . '_sync_payments'; self::$setting_id_proration = WC_Subscriptions_Admin::$option_prefix . '_prorate_synced_payments'; self::$setting_id_days_no_fee = WC_Subscriptions_Admin::$option_prefix . '_days_no_fee'; self::$sync_field_label = __( 'Synchronise renewals', 'woocommerce-subscriptions' ); self::$sync_description = __( 'Align the payment date for all customers who purchase this subscription to a specific day of the week or month.', 'woocommerce-subscriptions' ); // translators: placeholder is a year (e.g. "2016") self::$sync_description_year = sprintf( _x( 'Align the payment date for this subscription to a specific day of the year. If the date has already taken place this year, the first payment will be processed in %s. Set the day to 0 to disable payment syncing for this product.', 'used in subscription product edit screen', 'woocommerce-subscriptions' ), gmdate( 'Y', wcs_date_to_time( '+1 year' ) ) ); // Add the settings to control whether syncing is enabled and how it will behave add_filter( 'woocommerce_subscription_settings', __CLASS__ . '::add_settings' ); // When enabled, add the sync selection fields to the Edit Product screen add_action( 'woocommerce_subscriptions_product_options_pricing', __CLASS__ . '::subscription_product_fields' ); add_action( 'woocommerce_variable_subscription_pricing', __CLASS__ . '::variable_subscription_product_fields', 10, 3 ); // Add the translated fields to the Subscriptions admin script add_filter( 'woocommerce_subscriptions_admin_script_parameters', __CLASS__ . '::admin_script_parameters', 10 ); // Save sync options when a subscription product is saved add_action( 'woocommerce_process_product_meta_subscription', __CLASS__ . '::save_subscription_meta', 10 ); // Save sync options when a variable subscription product is saved add_action( 'woocommerce_process_product_meta_variable-subscription', __CLASS__ . '::process_product_meta_variable_subscription' ); add_action( 'woocommerce_save_product_variation', __CLASS__ . '::save_product_variation', 20, 2 ); // Make sure the expiration dates are calculated from the synced start date add_filter( 'woocommerce_subscriptions_product_trial_expiration_date', __CLASS__ . '::recalculate_product_trial_expiration_date', 10, 2 ); add_filter( 'woocommerce_subscriptions_product_expiration_date', __CLASS__ . '::recalculate_product_expiration_date', 10, 3 ); // Display a product's first payment date on the product's page to make sure it's obvious to the customer when payments will start add_action( 'woocommerce_single_product_summary', __CLASS__ . '::products_first_payment_date', 31 ); // Display a product's first payment date on the product's page to make sure it's obvious to the customer when payments will start add_action( 'woocommerce_subscriptions_product_first_renewal_payment_time', __CLASS__ . '::products_first_renewal_payment_time', 10, 4 ); // Maybe mock a free trial on the product for calculating totals and displaying correct shipping costs add_action( 'woocommerce_before_calculate_totals', __CLASS__ . '::maybe_set_free_trial', 0, 1 ); add_action( 'woocommerce_subscription_cart_before_grouping', __CLASS__ . '::maybe_unset_free_trial' ); add_action( 'woocommerce_subscription_cart_after_grouping', __CLASS__ . '::maybe_set_free_trial' ); add_filter( 'wcs_recurring_cart_start_date', __CLASS__ . '::maybe_unset_free_trial', 0, 1 ); add_filter( 'wcs_recurring_cart_end_date', __CLASS__ . '::maybe_set_free_trial', 100, 1 ); add_filter( 'woocommerce_subscriptions_calculated_total', __CLASS__ . '::maybe_unset_free_trial', 10000, 1 ); add_action( 'woocommerce_cart_totals_before_shipping', __CLASS__ . '::maybe_set_free_trial' ); add_action( 'woocommerce_cart_totals_after_shipping', __CLASS__ . '::maybe_unset_free_trial' ); add_action( 'woocommerce_review_order_before_shipping', __CLASS__ . '::maybe_set_free_trial' ); add_action( 'woocommerce_review_order_after_shipping', __CLASS__ . '::maybe_unset_free_trial' ); // Set prorated initial amount when calculating initial total add_filter( 'woocommerce_subscriptions_cart_get_price', __CLASS__ . '::set_prorated_price_for_calculation', 10, 2 ); // When creating a subscription check if it contains a synced product and make sure the correct meta is set on the subscription add_action( 'woocommerce_new_subscription', __CLASS__ . '::maybe_add_subscription_meta', 10, 1 ); // When adding an item to a subscription, check if it is for a synced product to make sure the sync meta is set on the subscription. We can't attach to just the 'woocommerce_new_order_item' here because the '_product_id' and '_variation_id' meta are not set before it fires add_action( 'woocommerce_ajax_add_order_item_meta', __CLASS__ . '::ajax_maybe_add_meta_for_item', 10, 2 ); if ( wcs_is_woocommerce_pre( '3.0' ) ) { add_action( 'woocommerce_order_add_product', __CLASS__ . '::maybe_add_meta_for_new_product', 10, 3 ); add_action( 'woocommerce_add_order_item_meta', array( __CLASS__, 'maybe_add_order_item_meta' ), 10, 2 ); } else { add_action( 'woocommerce_new_order_item', __CLASS__ . '::maybe_add_meta_for_new_line_item', 10, 3 ); add_action( 'woocommerce_checkout_create_order_line_item', array( __CLASS__, 'maybe_add_line_item_meta' ), 10, 3 ); } // Make sure the sign-up fee for a synchronised subscription is correct add_filter( 'woocommerce_subscriptions_sign_up_fee', __CLASS__ . '::get_synced_sign_up_fee', 1, 3 ); // If it's an initial sync order and the total is zero, and nothing needs to be shipped, do not reduce stock add_filter( 'woocommerce_order_item_quantity', __CLASS__ . '::maybe_do_not_reduce_stock', 10, 3 ); add_filter( 'woocommerce_subscriptions_recurring_cart_key', __CLASS__ . '::add_to_recurring_product_grouping_key', 10, 2 ); add_filter( 'woocommerce_subscriptions_item_grouping_key', __CLASS__ . '::add_to_recurring_product_grouping_key', 10, 2 ); // Add defaults for our options. add_filter( 'default_option_' . self::$setting_id_days_no_fee, array( __CLASS__, 'option_default' ), 10, 3 ); // Sanitize options when saving. add_filter( 'woocommerce_admin_settings_sanitize_option_' . self::$setting_id_days_no_fee, array( __CLASS__, 'sanitize_option' ), 10, 2 ); // Ensure options are the proper type. add_filter( 'option_' . self::$setting_id_days_no_fee, 'intval' ); // Don't display migrated order item meta on the Edit Order screen add_filter( 'woocommerce_hidden_order_itemmeta', array( __CLASS__, 'hide_order_itemmeta' ) ); } /** * Set default value of 'no' for our options. * * This only sets the default * * @author Jeremy Pry * * @param mixed $default The default value for the option. * @param string $option The option name. * @param bool $passed_default Whether get_option() was passed a default value. * * @return mixed The default option value. */ public static function option_default( $default, $option, $passed_default = null ) { switch ( $option ) { case self::$setting_id_days_no_fee: $default = $passed_default ? $default : 0; break; } return $default; } /** * Sanitize our options when they are saved in the admin area. * * @author Jeremy Pry * * @param mixed $value The value being saved. * @param array $option The option data array. * * @return mixed The sanitized option value. */ public static function sanitize_option( $value, $option ) { switch ( $option['id'] ) { case self::$setting_id_days_no_fee: $value = absint( $value ); break; } return $value; } /** * Check if payment syncing is enabled on the store. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.5 */ public static function is_syncing_enabled() { return 'yes' === get_option( self::$setting_id, 'no' ); } /** * Check if payments can be prorated on the store. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.5 */ public static function is_sync_proration_enabled() { return 'no' !== get_option( self::$setting_id_proration, 'no' ); } /** * Add sync settings to the Subscription's settings page. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.5 */ public static function add_settings( $settings ) { $synchronisation_settings = array( array( 'name' => __( 'Synchronisation', 'woocommerce-subscriptions' ), 'type' => 'title', // translators: placeholders are opening and closing link tags 'desc' => sprintf( _x( 'Align subscription renewal to a specific day of the week, month or year. For example, the first day of the month. %1$sLearn more%2$s.', 'used in the general subscription options page', 'woocommerce-subscriptions' ), '', '' ), 'id' => self::$setting_id . '_title', ), array( 'name' => self::$sync_field_label, 'desc' => __( 'Align Subscription Renewal Day', 'woocommerce-subscriptions' ), 'id' => self::$setting_id, 'default' => 'no', 'type' => 'checkbox', ), array( 'name' => __( 'Prorate First Renewal', 'woocommerce-subscriptions' ), 'desc' => __( 'If a subscription is synchronised to a specific day of the week, month or year, charge a prorated amount for the subscription at the time of sign up.', 'woocommerce-subscriptions' ), 'id' => self::$setting_id_proration, 'css' => 'min-width:150px;', 'default' => 'no', 'type' => 'select', 'class' => 'wc-enhanced-select', 'options' => array( 'no' => _x( 'Never (do not charge any recurring amount)', 'when to prorate first payment / subscription length', 'woocommerce-subscriptions' ), 'recurring' => _x( 'Never (charge the full recurring amount at sign-up)', 'when to prorate first payment / subscription length', 'woocommerce-subscriptions' ), 'virtual' => _x( 'For Virtual Subscription Products Only', 'when to prorate first payment / subscription length', 'woocommerce-subscriptions' ), 'yes' => _x( 'For All Subscription Products', 'when to prorate first payment / subscription length', 'woocommerce-subscriptions' ), ), 'desc_tip' => true, ), array( 'name' => __( 'Sign-up grace period', 'woocommerce-subscriptions' ), 'desc' => _x( 'days prior to Renewal Day', "there's a number immediately in front of this text", 'woocommerce-subscriptions' ), 'id' => self::$setting_id_days_no_fee, 'default' => 0, 'type' => 'number', 'desc_tip' => __( 'Subscriptions created within this many days prior to the Renewal Day will not be charged at sign-up. Set to zero for all new Subscriptions to be charged the full recurring amount. Must be a positive number.', 'woocommerce-subscriptions' ), ), array( 'type' => 'sectionend', 'id' => self::$setting_id . '_title', ), ); // Insert the switch settings in after the Roles section otherwise add the settings to the end. if ( ! WC_Subscriptions_Admin::insert_setting_after( $settings, WC_Subscriptions_Admin::$option_prefix . '_role_options', $synchronisation_settings, 'multiple-settings', 'sectionend' ) ) { $settings = array_merge( $settings, $synchronisation_settings ); } return $settings; } /** * Add the sync setting fields to the Edit Product screen * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.5 */ public static function subscription_product_fields() { global $post, $wp_locale; if ( self::is_syncing_enabled() ) { // Set month as the default billing period $subscription_period = get_post_meta( $post->ID, '_subscription_period', true ); if ( ! $subscription_period ) { $subscription_period = 'month'; } // Determine whether to display the week/month sync fields or the annual sync fields $display_week_month_select = ( ! in_array( $subscription_period, array( 'month', 'week' ) ) ) ? 'display: none;' : ''; $display_annual_select = ( 'year' != $subscription_period ) ? 'display: none;' : ''; $payment_day = self::get_products_payment_day( $post->ID ); // An annual sync date is already set in the form: array( 'day' => 'nn', 'month' => 'nn' ), create a MySQL string from those values (year and time are irrelvent as they are ignored) if ( is_array( $payment_day ) ) { $payment_month = ( 0 === (int) $payment_day['day'] ) ? 0 : $payment_day['month']; $payment_day = $payment_day['day']; } else { $payment_month = 0; } echo '