Last active
November 30, 2021 07:36
-
-
Save mattallan/e2ffe7d4234222601ee3 to your computer and use it in GitHub Desktop.
sync end date fixer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Plugin Name: Fix incorrect end dates calculated on Synced Subscriptions | |
* Plugin URI: | |
* Description: Add `&sync_date_fixer=log` to the URL to log any incorrect end dates (see log file: wcs_sync_fixer_test). Add `&sync_date_fixer=update` to the URL to log and update any incorrect end dates (see log file: wcs_sync_fixer_updated to find which subscriptions were incorrect and have been updated.) | |
* Version: 1.0 | |
* Author: Matt Allan | |
* Author URI: http://prospress.com | |
* License: GPLv2 | |
*/ | |
/** | |
* Log and/or update the incorrect end dates on a synced subscription. | |
* | |
* - To just log and not update add `&sync_date_fixer=log` to the URL | |
* - To update end dates and log them as well add `&sync_date_fixer=update` | |
*/ | |
function wcs_sync_end_date_fixer() { | |
global $wpdb; | |
// get all subscriptions that have an end date attached and also contain a synced subscription | |
$results = $wpdb->get_results( "SELECT post_id FROM {$wpdb->prefix}postmeta WHERE meta_key = '_contains_synced_subscription' AND post_id IN (SELECT post_id FROM {$wpdb->prefix}postmeta WHERE meta_key = '_schedule_end' AND meta_value != 0 )" ); | |
$log = new WC_Logger(); | |
foreach ( $results as $result ) { | |
$subscription = wcs_get_subscription( $result->post_id ); | |
$current_end = $subscription->get_date( 'end' ); | |
$start = $subscription->get_date( 'start' ); | |
if ( $subscription ) { | |
foreach ( $subscription->get_items() as $item ) { | |
$product_id = wcs_get_canonical_product_id( $item ); | |
$product = wc_get_product( $product_id ); | |
$new_end_date = 0; | |
if ( WC_Subscriptions_Synchroniser::is_product_synced( $product ) ) { | |
$from_date = temp_wcs_calculate_sync_date_in_past( $product, 'mysql', $start ); | |
remove_filter( 'woocommerce_subscriptions_product_expiration_date', 'WC_Subscriptions_Synchroniser::recalculate_product_expiration_date' ); | |
$new_end_date = WC_Subscriptions_Product::get_expiration_date( $product_id, $from_date ); | |
add_filter( 'woocommerce_subscriptions_product_expiration_date', 'WC_Subscriptions_Synchroniser::recalculate_product_expiration_date', 10, 3 ); | |
} | |
if ( 0 != $new_end_date && $current_end != $new_end_date ) { | |
$line = '[SUB #' . $result->post_id . '] first sync payment: ' . $from_date . ', start: ' . $start . ', old end date: ' . $current_end . ', new end date: ' . $new_end_date; | |
if ( isset( $_GET['sync_date_fixer'] ) ) { | |
if ( 'update' == $_GET['sync_date_fixer'] ) { | |
$subscription->update_dates( array( 'end' => $new_end_date ) ); | |
$log->add( 'wcs_sync_fixer_updated', $line ); | |
} elseif ( 'log' == $_GET['sync_date_fixer'] ) { | |
$log->add( 'wcs_sync_fixer_test', $line ); | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
add_action( 'admin_footer', 'wcs_sync_end_date_fixer' ); | |
/** | |
* A copy of WC_Subscriptions_Synchroniser::calculate_first_payment_date() except | |
* this function does not require the first payment to be in the future. | |
*/ | |
function temp_wcs_calculate_sync_date_in_past( $product, $type = 'mysql', $from_date = '' ) { | |
$period = WC_Subscriptions_Product::get_period( $product ); | |
$trial_period = WC_Subscriptions_Product::get_trial_period( $product ); | |
$trial_length = WC_Subscriptions_Product::get_trial_length( $product ); | |
$from_date_param = $from_date; | |
if ( empty( $from_date ) ) { | |
$from_date = gmdate( 'Y-m-d H:i:s' ); | |
} | |
// If the subscription has a free trial period, the first payment should be synced to a day after the free trial | |
if ( $trial_length > 0 ) { | |
$from_date = WC_Subscriptions_Product::get_trial_expiration_date( $product, $from_date ); | |
} | |
$from_timestamp = strtotime( $from_date ) + ( get_option( 'gmt_offset' ) * 3600 ); // Site time | |
$payment_day = WC_Subscriptions_Synchroniser::get_products_payment_day( $product ); | |
if ( 'week' == $period ) { | |
// strtotime() will figure out if the day is in the future or today (see: https://gist.github.com/thenbrent/9698083) | |
$first_payment_timestamp = strtotime( WC_Subscriptions_Synchroniser::$weekdays[ $payment_day ], $from_timestamp ); | |
} elseif ( 'month' == $period ) { | |
// strtotime() needs to know the month, so we need to determine if the specified day has occured this month yet or if we want the last day of the month (see: https://gist.github.com/thenbrent/9698083) | |
if ( $payment_day > 27 ) { // we actually want the last day of the month | |
$payment_day = gmdate( 't', $from_timestamp ); | |
$month = gmdate( 'F', $from_timestamp ); | |
} elseif ( gmdate( 'j', $from_timestamp ) > $payment_day ) { // today is later than specified day in the from date, we need the next month | |
$month = date( 'F', wcs_add_months( $from_timestamp, 1 ) ); | |
} else { // specified day is either today or still to come in the month of the from date | |
$month = gmdate( 'F', $from_timestamp ); | |
} | |
$first_payment_timestamp = strtotime( "{$payment_day} {$month}", $from_timestamp ); | |
} elseif ( 'year' == $period ) { | |
// We can't use $wp_locale here because it is translated | |
switch ( $payment_day['month'] ) { | |
case 1 : | |
$month = 'January'; | |
break; | |
case 2 : | |
$month = 'February'; | |
break; | |
case 3 : | |
$month = 'March'; | |
break; | |
case 4 : | |
$month = 'April'; | |
break; | |
case 5 : | |
$month = 'May'; | |
break; | |
case 6 : | |
$month = 'June'; | |
break; | |
case 7 : | |
$month = 'July'; | |
break; | |
case 8 : | |
$month = 'August'; | |
break; | |
case 9 : | |
$month = 'September'; | |
break; | |
case 10 : | |
$month = 'October'; | |
break; | |
case 11 : | |
$month = 'November'; | |
break; | |
case 12 : | |
$month = 'December'; | |
break; | |
} | |
$first_payment_timestamp = strtotime( "{$payment_day['day']} {$month}", $from_timestamp ); | |
} | |
// We calculated a timestamp for midnight on the specific day in the site's timezone, let's push it to 3am to account for any daylight savings changes | |
$first_payment_timestamp += 3 * HOUR_IN_SECONDS; | |
// And convert it to the UTC equivalent of 3am on that day | |
$first_payment_timestamp -= ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ); | |
return ( 'mysql' == $type && 0 != $first_payment_timestamp ) ? date( 'Y-m-d H:i:s', $first_payment_timestamp ) : $first_payment_timestamp; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment