Implement batch processing for users with empty account statuses

Introduced a new batch process to handle users lacking an `account_status` meta efficiently. Refactored legacy methods, added async scheduling, and created helper functions to manage and track progress. These changes improve performance and reliability for large user bases.
This commit is contained in:
Mykyta Synelnikov
2025-04-15 14:27:59 +03:00
parent bdd973c14c
commit 5356148cc4
8 changed files with 230 additions and 110 deletions
-3
View File
@@ -350,9 +350,6 @@ if ( ! class_exists( 'um\admin\Users_Columns' ) ) {
return; return;
} }
// Set default statuses if not already done.
UM()->setup()->set_default_user_status();
$id = 'um_user_status'; $id = 'um_user_status';
// need to add there additional nonce field because WordPress native _wpnonce field isn't visible on the users.php screen then custom actions // need to add there additional nonce field because WordPress native _wpnonce field isn't visible on the users.php screen then custom actions
+109 -55
View File
@@ -47,6 +47,8 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {
$this->lock_registration(); $this->lock_registration();
$this->empty_status_users();
$this->extensions_page(); $this->extensions_page();
$this->child_theme_required(); $this->child_theme_required();
@@ -204,17 +206,17 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {
ob_start(); ?> ob_start(); ?>
<div class="<?php echo esc_attr( $class ) ?> um-admin-notice notice <?php echo $dismissible ? 'is-dismissible' : '' ?>" data-key="<?php echo esc_attr( $key ) ?>"> <div class="<?php echo esc_attr( $class ); ?> um-admin-notice notice <?php echo $dismissible ? 'is-dismissible' : ''; ?>" data-key="<?php echo esc_attr( $key ); ?>">
<?php echo ! empty( $notice_data['message'] ) ? $notice_data['message'] : '' ?> <?php echo ! empty( $notice_data['message'] ) ? $notice_data['message'] : ''; ?>
</div> </div>
<?php $notice = ob_get_clean(); <?php
$notice = ob_get_clean();
if ( $echo ) { if ( $echo ) {
echo $notice; echo $notice;
return; return;
} else {
return $notice;
} }
return $notice;
} }
@@ -246,6 +248,37 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {
); );
} }
/**
* Checking if the "Membership - Anyone can register" WordPress general setting is active
*/
public function empty_status_users() {
$empty_status_users = get_option( '_um_log_empty_status_users', array( 0, 0 ) );
if ( ! is_array( $empty_status_users ) ) {
$empty_status_users = array( 0, 0 );
}
if ( array( 0, 0 ) === $empty_status_users ) {
return;
}
$allowed_html = array(
'a' => array(
'href' => array(),
),
'strong' => array(),
);
$this->add_notice(
'empty_status_users',
array(
'class' => 'info',
// translators: %1$d: Background update for users is complete; %2$d: Total users for background update.
'message' => '<p>' . wp_kses( sprintf( __( 'Background process is running: Setting user statuses %1$d/%2$d.', 'ultimate-member' ), $empty_status_users[0], $empty_status_users[1] ), $allowed_html ) . '</p>',
'dismissible' => false,
)
);
}
/** /**
* Checking if the "Membership - Anyone can register" WordPress general setting is active * Checking if the "Membership - Anyone can register" WordPress general setting is active
*/ */
@@ -256,11 +289,11 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {
?> ?>
<p> <p>
<?php _e( '<strong>All Access Pass</strong> Get access to all Ultimate Member extensions at a significant discount with our All Access Pass.', 'ultimate-member' ) ?> <?php _e( '<strong>All Access Pass</strong> Get access to all Ultimate Member extensions at a significant discount with our All Access Pass.', 'ultimate-member' ); ?>
</p> </p>
<p> <p>
<a href="https://ultimatemember.com/pricing/" class="button button-primary" target="_blank"> <a href="https://ultimatemember.com/pricing/" class="button button-primary" target="_blank">
<?php _e( 'View Pricing', 'ultimate-member' ) ?> <?php esc_html_e( 'View Pricing', 'ultimate-member' ); ?>
</a> </a>
</p> </p>
@@ -290,7 +323,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {
$path = str_replace( '//', '/', $path ); $path = str_replace( '//', '/', $path );
if ( ! file_exists( $path ) ) { if ( ! file_exists( $path ) ) {
$old = umask(0); $old = umask( 0 );
@mkdir( $path, 0777, true ); @mkdir( $path, 0777, true );
umask( $old ); umask( $old );
} }
@@ -326,9 +359,12 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {
'woocommerce', 'woocommerce',
); );
$slugs = array_map( function( $item ) { $slugs = array_map(
return 'um-' . $item . '/um-' . $item . '.php'; function ( $item ) {
}, $old_extensions ); return 'um-' . $item . '/um-' . $item . '.php';
},
$old_extensions
);
$active_plugins = UM()->dependencies()->get_active_plugins(); $active_plugins = UM()->dependencies()->get_active_plugins();
foreach ( $slugs as $slug ) { foreach ( $slugs as $slug ) {
@@ -622,7 +658,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {
$arr_inactive_license_keys[] = $license->item_name; $arr_inactive_license_keys[] = $license->item_name;
} }
$invalid_license++; ++$invalid_license;
} }
if ( ! empty( $arr_inactive_license_keys ) ) { if ( ! empty( $arr_inactive_license_keys ) ) {
@@ -676,34 +712,41 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {
</p> </p>
<p> <p>
<a href="<?php echo esc_url( $url ) ?>" class="button button-primary"><?php esc_html_e( 'Visit Upgrade Page', 'ultimate-member' ); ?></a> <a href="<?php echo esc_url( $url ); ?>" class="button button-primary"><?php esc_html_e( 'Visit Upgrade Page', 'ultimate-member' ); ?></a>
&nbsp; &nbsp;
</p> </p>
<?php $message = ob_get_clean(); <?php
$message = ob_get_clean();
$this->add_notice( 'upgrade', array( $this->add_notice(
'class' => 'error', 'upgrade',
'message' => $message, array(
), 4 ); 'class' => 'error',
} else { 'message' => $message,
if ( isset( $_GET['msg'] ) && 'updated' === sanitize_key( $_GET['msg'] ) ) { ),
if ( isset( $_GET['page'] ) && 'um_options' === sanitize_key( $_GET['page'] ) ) { 4
$this->add_notice( 'settings_upgrade', array( );
'class' => 'updated', } elseif ( isset( $_GET['msg'] ) && 'updated' === sanitize_key( $_GET['msg'] ) ) {
'message' => '<p>' . __( 'Settings successfully upgraded', 'ultimate-member' ) . '</p>', if ( isset( $_GET['page'] ) && 'um_options' === sanitize_key( $_GET['page'] ) ) {
), 4 ); $this->add_notice(
} else { 'settings_upgrade',
$this->add_notice( array(
'upgrade', 'class' => 'updated',
array( 'message' => '<p>' . __( 'Settings successfully upgraded', 'ultimate-member' ) . '</p>',
'class' => 'updated', ),
// translators: %1$s is a plugin name title; %2$s is a plugin version. 4
'message' => '<p>' . sprintf( __( '<strong>%1$s %2$s</strong> Successfully Upgraded', 'ultimate-member' ), UM_PLUGIN_NAME, UM_VERSION ) . '</p>', );
), } else {
4 $this->add_notice(
); 'upgrade',
} array(
'class' => 'updated',
// translators: %1$s is a plugin name title; %2$s is a plugin version.
'message' => '<p>' . sprintf( __( '<strong>%1$s %2$s</strong> Successfully Upgraded', 'ultimate-member' ), UM_PLUGIN_NAME, UM_VERSION ) . '</p>',
),
4
);
} }
} }
} }
@@ -734,18 +777,18 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {
?> ?>
</p> </p>
<p> <p>
<a href="javascript:void(0);" id="um_add_review_love"><?php _e( 'I love it!', 'ultimate-member' ) ?></a>&nbsp;|&nbsp; <a href="javascript:void(0);" id="um_add_review_love"><?php _e( 'I love it!', 'ultimate-member' ); ?></a>&nbsp;|&nbsp;
<a href="javascript:void(0);" id="um_add_review_good"><?php _e('It\'s good but could be better', 'ultimate-member' ) ?></a>&nbsp;|&nbsp; <a href="javascript:void(0);" id="um_add_review_good"><?php _e( 'It\'s good but could be better', 'ultimate-member' ); ?></a>&nbsp;|&nbsp;
<a href="javascript:void(0);" id="um_add_review_bad"><?php _e('I don\'t like the plugin', 'ultimate-member' ) ?></a> <a href="javascript:void(0);" id="um_add_review_bad"><?php _e( 'I don\'t like the plugin', 'ultimate-member' ); ?></a>
</p> </p>
</div> </div>
<div class="um-hidden-notice" data-key="love"> <div class="um-hidden-notice" data-key="love">
<p> <p>
<?php printf( __( 'Great! We\'re happy to hear that you love the plugin. It would be amazing if you could let others know why you like %s by leaving a review of the plugin. This will help %s to grow and become more popular and would be massively appreciated by us!' ), UM_PLUGIN_NAME, UM_PLUGIN_NAME ); ?> <?php printf( __( 'Great! We\'re happy to hear that you love the plugin. It would be amazing if you could let others know why you like %1$s by leaving a review of the plugin. This will help %2$s to grow and become more popular and would be massively appreciated by us!' ), UM_PLUGIN_NAME, UM_PLUGIN_NAME ); ?>
</p> </p>
<p> <p>
<a href="https://wordpress.org/support/plugin/ultimate-member/reviews/?rate=5#new-post" target="_blank" class="button button-primary um_review_link"><?php _e( 'Leave Review', 'ultimate-member' ) ?></a> <a href="https://wordpress.org/support/plugin/ultimate-member/reviews/?rate=5#new-post" target="_blank" class="button button-primary um_review_link"><?php _e( 'Leave Review', 'ultimate-member' ); ?></a>
</p> </p>
</div> </div>
<div class="um-hidden-notice" data-key="good"> <div class="um-hidden-notice" data-key="good">
@@ -754,7 +797,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {
</p> </p>
<p> <p>
<a href="https://ultimatemember.com/feedback/" target="_blank" class="button button-primary um_review_link"><?php _e( 'Provide Feedback', 'ultimate-member' ) ?></a> <a href="https://ultimatemember.com/feedback/" target="_blank" class="button button-primary um_review_link"><?php _e( 'Provide Feedback', 'ultimate-member' ); ?></a>
</p> </p>
</div> </div>
<div class="um-hidden-notice" data-key="bad"> <div class="um-hidden-notice" data-key="bad">
@@ -763,17 +806,22 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {
</p> </p>
<p> <p>
<a href="https://ultimatemember.com/feedback/" target="_blank" class="button button-primary um_review_link"><?php _e( 'Provide Feedback', 'ultimate-member' ) ?></a> <a href="https://ultimatemember.com/feedback/" target="_blank" class="button button-primary um_review_link"><?php _e( 'Provide Feedback', 'ultimate-member' ); ?></a>
</p> </p>
</div> </div>
<?php $message = ob_get_clean(); <?php
$message = ob_get_clean();
$this->add_notice( 'reviews_notice', array( $this->add_notice(
'class' => 'updated', 'reviews_notice',
'message' => $message, array(
'dismissible' => true 'class' => 'updated',
), 1 ); 'message' => $message,
'dismissible' => true,
),
1
);
} }
@@ -782,7 +830,8 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {
*/ */
function future_changed() { function future_changed() {
ob_start(); ?> ob_start();
?>
<p> <p>
<?php <?php
@@ -791,12 +840,17 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {
?> ?>
</p> </p>
<?php $message = ob_get_clean(); <?php
$message = ob_get_clean();
$this->add_notice( 'future_changes', array( $this->add_notice(
'class' => 'updated', 'future_changes',
'message' => $message, array(
), 2 ); 'class' => 'updated',
'message' => $message,
),
2
);
} }
/** /**
+49 -20
View File
@@ -20,16 +20,16 @@ if ( ! class_exists( 'um\common\actions\Users' ) ) {
const INTERVAL = 3600; const INTERVAL = 3600;
const SCHEDULE_ACTION = 'um_schedule_account_status_check'; const SCHEDULE_ACTION = 'um_schedule_empty_account_status_check';
const BATCH_SIZE = 50; const BATCH_SIZE = 50;
const BATCH_ACTION = 'um_check_account_status_batch'; const BATCH_ACTION = 'um_set_default_account_status';
public function __construct() { public function __construct() {
add_action( 'init', array( &$this, 'add_recurring_action' ) ); add_action( 'init', array( &$this, 'add_recurring_action' ) );
add_action( self::SCHEDULE_ACTION, array( &$this, 'status_check' ) ); add_action( self::SCHEDULE_ACTION, array( &$this, 'status_check' ) );
add_action( self::BATCH_ACTION, array( &$this, 'batch_check' ) ); add_action( self::BATCH_ACTION, array( &$this, 'batch_check' ), 10, 3 );
} }
public function add_recurring_action() { public function add_recurring_action() {
@@ -45,31 +45,34 @@ if ( ! class_exists( 'um\common\actions\Users' ) ) {
} }
public function status_check() { public function status_check() {
global $wpdb; $total_users = UM()->common()->users()::get_empty_status_users();
$total_users = $wpdb->get_var(
"SELECT COUNT(u.ID)
FROM {$wpdb->users} u
LEFT JOIN {$wpdb->usermeta} um ON u.ID = um.user_id AND um.meta_key = 'account_status'
LEFT JOIN {$wpdb->usermeta} um2 ON u.ID = um2.user_id AND um2.meta_key = 'um_registration_in_progress'
WHERE ( um.meta_value IS NULL OR um.meta_value = '' ) AND
um2.meta_value IS NULL OR um2.meta_value != '1'"
);
$total_users = absint( $total_users );
if ( empty( $total_users ) ) { if ( empty( $total_users ) ) {
return; return;
} }
for ( $offset = 0; $offset < $total_users; $offset += self::BATCH_SIZE ) { UM()->maybe_action_scheduler()->enqueue_async_action(
UM()->maybe_action_scheduler()->enqueue_async_action( self::BATCH_ACTION, array( $offset ) ); self::BATCH_ACTION,
} array(
'page' => 1,
'total' => $total_users,
'pages' => ceil( $total_users / self::BATCH_SIZE ),
)
);
} }
public function batch_check( $offset ) { /**
* Perform batch checking for users based on specific conditions.
* Ignore users with `um_registration_in_progress` that can be in the process of the registration.
* Get users with empty `account_status` meta.
*
* @param int $page The current page number.
* @param int $total The total number of users to process.
* @param int $pages The total number of pages to process.
*/
public function batch_check( $page, $total, $pages ) {
$users = new WP_User_Query( $users = new WP_User_Query(
array( array(
'number' => self::BATCH_SIZE, 'number' => self::BATCH_SIZE,
'offset' => $offset,
'fields' => 'ids', 'fields' => 'ids',
'meta_query' => array( 'meta_query' => array(
'relation' => 'AND', 'relation' => 'AND',
@@ -104,8 +107,34 @@ if ( ! class_exists( 'um\common\actions\Users' ) ) {
$results = $users->get_results(); $results = $users->get_results();
if ( ! empty( $results ) ) { if ( ! empty( $results ) ) {
$um_empty_status_users = get_option( '_um_log_empty_status_users', array( 0, 0 ) );
if ( ! is_array( $um_empty_status_users ) ) {
$um_empty_status_users = array( 0, count( $results ) );
}
foreach ( $results as $user_id ) { foreach ( $results as $user_id ) {
UM()->common()->users()->approve( $user_id, true, true ); $res = UM()->common()->users()->approve( $user_id, true, true );
if ( $res ) {
++$um_empty_status_users[0];
}
}
if ( $um_empty_status_users[0] < $um_empty_status_users[1] ) {
update_option( '_um_log_empty_status_users', $um_empty_status_users );
} else {
delete_option( '_um_log_empty_status_users' );
}
$next_page = $page + 1;
if ( $next_page <= $pages ) {
UM()->maybe_action_scheduler()->enqueue_async_action(
self::BATCH_ACTION,
array(
'page' => $next_page,
'total' => $total,
'pages' => $pages,
)
);
} }
} }
} }
+25
View File
@@ -830,4 +830,29 @@ class Users {
$user = WP_Session_Tokens::get_instance( $user_id ); $user = WP_Session_Tokens::get_instance( $user_id );
$user->destroy_all(); $user->destroy_all();
} }
/**
* Retrieve the number of users with empty `account_status` usermeta.
*
* @return int
*/
public static function get_empty_status_users() {
global $wpdb;
$total_users = $wpdb->get_var(
"SELECT COUNT(u.ID)
FROM {$wpdb->users} u
LEFT JOIN {$wpdb->usermeta} um ON u.ID = um.user_id AND um.meta_key = 'account_status'
LEFT JOIN {$wpdb->usermeta} um2 ON u.ID = um2.user_id AND um2.meta_key = 'um_registration_in_progress'
WHERE ( um.meta_value IS NULL OR um.meta_value = '' ) AND
um2.meta_value IS NULL OR um2.meta_value != '1'"
);
$total_users = absint( $total_users );
// In WordPress, an underscore prefix before the option name (e.g., _my_option_name) is commonly used to indicate that the option is private.
// This option has a format: {updated_users}/{total_users_for_update}.
update_option( '_um_log_empty_status_users', array( 0, $total_users ) );
return $total_users;
}
} }
+12 -28
View File
@@ -310,37 +310,21 @@ KEY meta_value_indx (um_value(191))
* @since 2.4.2 * @since 2.4.2
*/ */
public function set_default_user_status() { public function set_default_user_status() {
$result = get_transient( 'um_count_users_unassigned' ); $total_users = UM()->common()->users()::get_empty_status_users();
if ( false === $result ) { if ( empty( $total_users ) ) {
$args = array(
'fields' => 'ids',
'number' => 0,
'meta_query' => array(
array(
'key' => 'account_status',
'compare' => 'NOT EXISTS',
),
),
'um_custom_user_query' => true,
);
$users = new WP_User_Query( $args );
if ( empty( $users ) || is_wp_error( $users ) ) {
$result = array();
} else {
$result = $users->get_results();
}
set_transient( 'um_count_users_unassigned', $result, DAY_IN_SECONDS );
}
if ( empty( $result ) ) {
return; return;
} }
foreach ( $result as $user_id ) { // If there are some users without `account_status` then run the first async batch for update.
update_user_meta( $user_id, 'account_status', 'approved' ); $batch_size = 50; // See the class constant value `\um\common\actions\Users::BATCH_ACTION`.
} UM()->maybe_action_scheduler()->enqueue_async_action(
$batch_size,
array(
'page' => 1,
'total' => $total_users,
'pages' => ceil( $total_users / $batch_size ),
)
);
} }
/** /**
-3
View File
@@ -640,7 +640,6 @@ if ( ! class_exists( 'um\core\User' ) ) {
UM()->files()->remove_dir( UM()->files()->upload_temp ); UM()->files()->remove_dir( UM()->files()->upload_temp );
UM()->files()->remove_dir( UM()->uploader()->get_upload_base_dir() . um_user( 'ID' ) . DIRECTORY_SEPARATOR ); UM()->files()->remove_dir( UM()->uploader()->get_upload_base_dir() . um_user( 'ID' ) . DIRECTORY_SEPARATOR );
delete_transient( 'um_count_users_unassigned' );
delete_transient( 'um_count_users_pending_dot' ); delete_transient( 'um_count_users_pending_dot' );
} }
@@ -959,8 +958,6 @@ if ( ! class_exists( 'um\core\User' ) ) {
/** This action is documented in ultimate-member/includes/common/um-actions-register.php */ /** This action is documented in ultimate-member/includes/common/um-actions-register.php */
do_action( 'um_user_register', $user_id, $_POST, null ); do_action( 'um_user_register', $user_id, $_POST, null );
} }
delete_transient( 'um_count_users_unassigned' );
} }
+34
View File
@@ -0,0 +1,34 @@
<?php
require_once '../../../../wp-load.php';
function um_test_generate_random_string( $length = 10 ) {
return substr( str_shuffle( str_repeat( $x = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', ceil( $length / strlen( $x ) ) ) ), 1, $length );
}
for ( $i = 0; $i < 1000; $i++ ) {
$random_user_name = um_test_generate_random_string( 8 ); // Generate a random user name
$random_user_email = $random_user_name . '@example.com'; // Append the user name to a dummy email domain
$random_user_pass = um_test_generate_random_string( 12 ); // Generate a random user password
$random_first_name = um_test_generate_random_string( 5 ); // Generate a random first name
$random_last_name = um_test_generate_random_string( 8 ); // Generate a random last name
$userdata = array(
'user_login' => $random_user_name,
'user_pass' => 'q1q2q1q2',
'user_email' => $random_user_email,
'first_name' => $random_first_name,
'last_name' => $random_last_name,
'role' => 'subscriber',
);
$user_id = wp_insert_user( $userdata );
if ( is_wp_error( $user_id ) ) {
// Something went wrong, handle the error
var_dump( 'User creation failed: ' . $user_id->get_error_message() );
} else {
var_dump( 'User creation complete: ID:' . $user_id . ' Username:' . $userdata['user_login'] );
}
}
exit;
+1 -1
View File
@@ -127,7 +127,7 @@ if ( ! empty( $delete_options ) ) {
delete_transient( "um_count_users_{$status}" ); delete_transient( "um_count_users_{$status}" );
} }
delete_transient( 'um_count_users_pending_dot' ); delete_transient( 'um_count_users_pending_dot' );
delete_transient( 'um_count_users_unassigned' ); delete_transient( 'um_count_users_unassigned' ); // legacy but still need to delete while uninstall.
//remove all users cache //remove all users cache
UM()->user()->remove_cache_all_users(); UM()->user()->remove_cache_all_users();