Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: hourly cron job to check for prematurely expired memberships #3060

Merged
merged 4 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 89 additions & 1 deletion includes/plugins/wc-memberships/class-memberships.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
*/
class Memberships {

const GATE_CPT = 'np_memberships_gate';
const GATE_CPT = 'np_memberships_gate';
const CRON_HOOK = 'np_memberships_fix_expired_memberships';

/**
* Whether the gate has been rendered in this execution.
Expand Down Expand Up @@ -64,6 +65,10 @@ public static function init() {
add_filter( 'newspack_gate_content', 'wp_replace_insecure_home_url' );
add_filter( 'newspack_gate_content', 'do_shortcode', 11 ); // AFTER wpautop().

// Scheduled fix for prematurely expired memberships.
add_action( 'init', [ __CLASS__, 'cron_init' ] );
add_action( self::CRON_HOOK, [ __CLASS__, 'fix_expired_memberships_for_active_subscriptions' ] );

include __DIR__ . '/class-block-patterns.php';
include __DIR__ . '/class-metering.php';
}
Expand Down Expand Up @@ -869,5 +874,88 @@ private static function user_has_content_access_from_rules( $user_id, array $rul

return $has_access;
}

/**
* Deactivate the cron job.
*/
public static function cron_deactivate() {
\wp_clear_scheduled_hook( self::CRON_HOOK );
}

/**
* Schedule an hourly cron job to check for and fix expired memberships linked to active subscriptions.
*/
public static function cron_init() {
\register_deactivation_hook( NEWSPACK_PLUGIN_FILE, [ __CLASS__, 'cron_deactivate' ] );

if ( ! wp_next_scheduled( self::CRON_HOOK ) ) {
\wp_schedule_event( time(), 'hourly', self::CRON_HOOK );
}
}

/**
* Ensure that memberships tied to active subscriptions haven't expired prematurely.
* Will loop through all active subscriptions on the site and check all memberships associated with it.
*/
public static function fix_expired_memberships_for_active_subscriptions() {
if ( ! function_exists( 'wc_memberships' ) ) {
return;
}


$max_per_batch = 100;
$processed_batches = 0;
$reactivated_memberships = 0;
$active_subscriptions = WooCommerce_Connection::get_batch_of_active_subscriptions( $max_per_batch, $processed_batches );
if ( empty( $active_subscriptions ) ) {
return;
}

Logger::log( __( 'Checking for expired memberships linked to active subscriptions...', 'newspack-plugin' ) );

$active_membership_statuses = \wc_memberships()->get_user_memberships_instance()->get_active_access_membership_statuses();
while ( ! empty( $active_subscriptions ) ) {
$subscription = array_shift( $active_subscriptions );
try {
$memberships = \wc_memberships_get_memberships_from_subscription( $subscription );
if ( $memberships ) {
foreach ( $memberships as $membership ) {
// If the membership is not active and has an end date in the past, reactivate it.
if ( $membership && ! $membership->has_status( $active_membership_statuses ) && $membership->has_end_date() && $membership->get_end_date( 'timestamp' ) < time() ) {
$membership->set_end_date(); // Clear the end date.
$membership->update_status( 'active' ); // Reactivate the membership.
$reactivated_memberships++;
}
}
}
} catch ( Exception $e ) {
// Log the error.
Logger::log(
sprintf(
// Translators: %1$d is the membership ID, %2$s is the subscription ID.
__( 'Failed to reactivate membership %1$d linked to active subscription %2$s.', 'newspack-plugin' ),
$membership->get_id(),
$subscription->get_id()
)
);
}

// Get the next batch of active subscriptions.
if ( empty( $active_subscriptions ) ) {
$processed_batches++;
$active_subscriptions = WooCommerce_Connection::get_batch_of_active_subscriptions( $max_per_batch, $processed_batches * $max_per_batch );
}
}

if ( 0 < $reactivated_memberships ) {
Logger::log(
sprintf(
// Translators: %d is the number of reactivated memberships.
__( 'Reactivated %d memberships linked to active subscriptions.', 'newspack-plugin' ),
$reactivated_memberships
)
);
}
}
}
Memberships::init();
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,30 @@ public static function order_paid( $order_id ) {
\do_action( 'newspack_donation_order_processed', $order_id, $product_id );
}

/**
* Get a batch of active Woo Subscriptions.
*
* @param int $batch_size Max number of subscriptions to get.
* @param int $offset Number to skip.
*
* @return array|false Array of subscription objects, or false if no more to fetch.
*/
public static function get_batch_of_active_subscriptions( $batch_size = 100, $offset = 0 ) {
if ( ! function_exists( 'wcs_get_subscriptions' ) ) {
return false;
}

$subscriptions = \wcs_get_subscriptions(
[
'status' => [ 'active', 'pending', 'pending-cancel' ],
'subscriptions_per_page' => $batch_size,
'offset' => $offset,
]
);

return ! empty( $subscriptions ) ? array_values( $subscriptions ) : false;
}

/**
* Get the last successful order for a given customer.
*
Expand Down