get_subscription_statuses();
$placeholders = implode( ', ', array_fill( 0, count( $allowed_statuses ), '%s' ) );
if ( wcs_is_custom_order_tables_usage_enabled() ) {
$total_subscriptions = $wpdb->get_var(
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
// phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
$wpdb->prepare(
"SELECT
COUNT(id)
FROM {$wpdb->prefix}wc_orders
WHERE type='shop_subscription'
AND status IN ($placeholders)
",
...$allowed_statuses
)
);
} else {
$total_subscriptions = $wpdb->get_var(
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
// phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
$wpdb->prepare(
"SELECT
COUNT(ID)
FROM {$wpdb->prefix}posts
WHERE post_type='shop_subscription'
AND post_status IN ($placeholders)
",
...$allowed_statuses
)
);
}
$state = $this->get_tool_state();
if ( isset( $state['last_offset'] ) ) {
$total_subscriptions -= (int) $state['last_offset'];
}
return $total_subscriptions;
}
/**
* Returns the next batch of items that need to be processed.
*
* A batch item can be anything needed to identify the actual processing to be done,
* but whenever possible items should be numbers (e.g. database record ids)
* or at least strings, to ease troubleshooting and logging in case of problems.
*
* The size of the batch returned can be less than $size if there aren't that
* many items pending processing (and it can be zero if there isn't anything to process),
* but the size should always be consistent with what 'get_total_pending_count' returns
* (i.e. the size of the returned batch shouldn't be larger than the pending items count).
*
* @param int $size Maximum size of the batch to be returned.
*
* @return array Batch of items to process, containing $size or less items.
*/
public function get_next_batch_to_process( int $size ): array {
global $wpdb;
$allowed_statuses = $this->get_subscription_statuses();
$placeholders = implode( ', ', array_fill( 0, count( $allowed_statuses ), '%s' ) );
$state = $this->get_tool_state();
$offset = isset( $state['last_offset'] ) ? (int) $state['last_offset'] : 0;
$args = array_merge(
$allowed_statuses,
array( $size ),
array( $offset )
);
if ( wcs_is_custom_order_tables_usage_enabled() ) {
$subscriptions_to_process = $wpdb->get_col(
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
$wpdb->prepare(
"SELECT
id
FROM {$wpdb->prefix}wc_orders
WHERE type='shop_subscription'
AND status IN ($placeholders)
ORDER BY id ASC
LIMIT %d
OFFSET %d",
...$args
)
);
} else {
$subscriptions_to_process = $wpdb->get_col(
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
$wpdb->prepare(
"SELECT
ID
FROM {$wpdb->prefix}posts
WHERE post_type='shop_subscription'
AND post_status IN ($placeholders)
ORDER BY ID ASC
LIMIT %d
OFFSET %d",
...$args
)
);
}
// Reset the tool state if there are no more subscriptions to process.
if ( empty( $subscriptions_to_process ) ) {
$this->delete_tool_state();
}
return $subscriptions_to_process;
}
/**
* Process data for the supplied batch.
*
* This method should be prepared to receive items that don't actually need processing
* (because they have been processed before) and ignore them, but if at least
* one of the batch items that actually need processing can't be processed, an exception should be thrown.
*
* Once an item has been processed it shouldn't be counted in 'get_total_pending_count'
* nor included in 'get_next_batch_to_process' anymore (unless something happens that causes it
* to actually require further processing).
*
* @throw \Exception Something went wrong while processing the batch.
*
* @param array $batch Batch to process, as returned by 'get_next_batch_to_process'.
*/
public function process_batch( array $batch ): void {
$subscriptions_notifications = WC_Subscriptions_Core_Plugin::instance()->notifications_scheduler;
foreach ( $batch as $subscription_id ) {
$subscription = wcs_get_subscription( $subscription_id );
if ( ! $subscription ) {
continue;
}
if ( WC_Subscriptions_Email_Notifications::notifications_globally_enabled() ) {
$subscriptions_notifications->update_status( $subscription, $subscription->get_status(), null );
} else {
$subscriptions_notifications->unschedule_all_notifications( $subscription );
}
// Update the subscription's update time to mark it as updated.
$subscription->set_date_modified( time() );
$subscription->save();
}
// Update tool state.
$state = $this->get_tool_state();
$state['last_offset'] = isset( $state['last_offset'] ) ? absint( $state['last_offset'] ) + count( $batch ) : count( $batch );
$this->update_tool_state( $state );
}
/**
* Default (preferred) batch size to pass to 'get_next_batch_to_process'.
* The controller will pass this size unless it's externally configured
* to use a different size.
*
* @return int Default batch size.
*/
public function get_default_batch_size(): int {
return 20;
}
/**
* Start the background process for batch processing subscription notifications updates.
*
* @return string Informative string to show after the tool is triggered in UI.
*/
public function enqueue(): string {
$batch_processor = WCS_Batch_Processing_Controller::instance();
if ( $batch_processor->is_enqueued( self::class ) ) {
return __( 'Background process for updating subscription notifications already started, nothing done.', 'woocommerce-subscriptions' );
}
$batch_processor->enqueue_processor( self::class );
return __( 'Background process for updating subscription notifications started', 'woocommerce-subscriptions' );
}
/**
* Stop the background process for batch processing subscription notifications updates.
*
* @return string Informative string to show after the tool is triggered in UI.
*/
public function dequeue(): string {
$batch_processor = WCS_Batch_Processing_Controller::instance();
if ( ! $batch_processor->is_enqueued( self::class ) ) {
return __( 'Background process for updating subscription notifications not started, nothing done.', 'woocommerce-subscriptions' );
}
$batch_processor->remove_processor( self::class );
return __( 'Background process for updating subscription notifications stopped', 'woocommerce-subscriptions' );
}
/**
* Add the tool to start or stop the background process that manages notification batch processing.
*
* @param array $tools Old tools array.
* @return array Updated tools array.
*/
public function handle_woocommerce_debug_tools( array $tools ): array {
if ( ! WC_Subscriptions_Email_Notifications::notifications_globally_enabled() ) {
$tools['start_add_subscription_notifications'] = array(
'name' => __( 'Regenerate subscription notifications', 'woocommerce-subscriptions' ),
'button' => __( 'Regenerate notifications', 'woocommerce-subscriptions' ),
'disabled' => true,
'desc' => sprintf(
'%1$s
%2$s %3$s %5$s',
__( 'This tool will add notifications to pending, active, and on-hold subscriptions. These updates will occur gradually in the background using Action Scheduler.', 'woocommerce-subscriptions' ),
__( 'Note:', 'woocommerce-subscriptions' ),
__( 'Notifications are currently turned off. To activate them, check the "Enable customer renewal reminder notification emails." option (via WooCommerce > Settings > Subscriptions > Customer Notifications).', 'woocommerce-subscriptions' ),
esc_url( admin_url( 'admin.php?page=wc-settings&tab=subscriptions' ) ),
__( 'Manage settings.', 'woocommerce-subscriptions' )
),
'requires_refresh' => true,
);
return $tools;
}
$batch_processor = WCS_Batch_Processing_Controller::instance();
if ( $batch_processor->is_enqueued( self::class ) ) {
$pending_count = $this->get_total_pending_count();
$tools['stop_add_subscription_notifications'] = array(
'name' => __( 'Regenerate subscription notifications', 'woocommerce-subscriptions' ),
'button' => __( 'Stop regenerating notifications', 'woocommerce-subscriptions' ),
'desc' =>
/* translators: %1$d=count of total entries needing conversion */
sprintf( __( 'Stopping this will halt the background process that adds notifications to pending, active, and on-hold subscriptions. %1$d subscriptions remain to be processed.', 'woocommerce-subscriptions' ), $pending_count ),
'callback' => array( $this, 'dequeue' ),
'requires_refresh' => true,
);
} else {
$tools['start_add_subscription_notifications'] = array(
'name' => __( 'Regenerate subscription notifications', 'woocommerce-subscriptions' ),
'button' => __( 'Regenerate notifications', 'woocommerce-subscriptions' ),
'desc' => __( 'This tool will regenerate notifications to pending, active, and on-hold subscriptions. These updates will occur gradually in the background using Action Scheduler.', 'woocommerce-subscriptions' ),
'callback' => array( $this, 'enqueue' ),
'requires_refresh' => true,
);
}
return $tools;
}
}