From c8bd79fcebc0c604a80f2ad614ebdfd893091589 Mon Sep 17 00:00:00 2001 From: Mykyta Synelnikov Date: Mon, 14 Apr 2025 23:12:29 +0300 Subject: [PATCH] Add scheduled user account status check and improve approval Introduce a new `Users` class to handle scheduled tasks for user status checks and batch processing. Refactor user approval functionality to allow silent operations and avoid sending notifications where unnecessary. Enhance user registration to prevent unfinished registrations from being processed in scheduled checks. --- includes/common/actions/class-users.php | 113 ++++++++++++++++++++++++ includes/common/class-init.php | 4 + includes/common/class-users.php | 99 +++++---------------- includes/core/um-actions-register.php | 7 ++ 4 files changed, 144 insertions(+), 79 deletions(-) create mode 100644 includes/common/actions/class-users.php diff --git a/includes/common/actions/class-users.php b/includes/common/actions/class-users.php new file mode 100644 index 00000000..dae0ce7d --- /dev/null +++ b/includes/common/actions/class-users.php @@ -0,0 +1,113 @@ +maybe_action_scheduler()->next_scheduled_action( self::SCHEDULE_ACTION ) ) { + return; + } + + UM()->maybe_action_scheduler()->schedule_recurring_action( + time() + 60, + self::INTERVAL, + self::SCHEDULE_ACTION + ); + } + + public function status_check() { + 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 ); + + if ( empty( $total_users ) ) { + return; + } + + for ( $offset = 0; $offset < $total_users; $offset += self::BATCH_SIZE ) { + UM()->maybe_action_scheduler()->enqueue_async_action( self::BATCH_ACTION, array( $offset ) ); + } + } + + public function batch_check( $offset ) { + $users = new WP_User_Query( + array( + 'number' => self::BATCH_SIZE, + 'offset' => $offset, + 'fields' => 'ids', + 'meta_query' => array( + 'relation' => 'AND', + array( + 'relation' => 'OR', + array( + 'key' => 'um_registration_in_progress', + 'compare' => 'NOT EXISTS', + ), + array( + 'key' => 'um_registration_in_progress', + 'value' => '1', + 'compare' => '!=', + ), + ), + array( + 'relation' => 'OR', + array( + 'key' => 'account_status', + 'compare' => 'NOT EXISTS', + ), + array( + 'key' => 'account_status', + 'value' => '', + 'compare' => '=', + ), + ), + ), + ) + ); + + $results = $users->get_results(); + + if ( ! empty( $results ) ) { + foreach ( $results as $user_id ) { + UM()->common()->users()->approve( $user_id, true, true ); + } + } + } + } +} diff --git a/includes/common/class-init.php b/includes/common/class-init.php index 73cf4805..2ceff24f 100644 --- a/includes/common/class-init.php +++ b/includes/common/class-init.php @@ -39,6 +39,10 @@ if ( ! class_exists( 'um\common\Init' ) ) { if ( empty( UM()->classes['um\common\actions\emails'] ) ) { UM()->classes['um\common\actions\emails'] = new actions\Emails(); } + + if ( empty( UM()->classes['um\common\actions\users'] ) ) { + UM()->classes['um\common\actions\users'] = new actions\Users(); + } // Other classes init here as soon as possible. } diff --git a/includes/common/class-users.php b/includes/common/class-users.php index a2c2ad83..8b2ee5aa 100644 --- a/includes/common/class-users.php +++ b/includes/common/class-users.php @@ -20,68 +20,6 @@ class Users { public function hooks() { add_filter( 'user_has_cap', array( &$this, 'map_caps_by_role' ), 10, 3 ); add_filter( 'editable_roles', array( &$this, 'restrict_roles' ) ); - - add_action( 'init', array( &$this, 'um_schedule_account_status_check' ) ); - add_action( 'um_schedule_account_status_check', array( &$this, 'status_check' ) ); - add_action( 'um_check_account_status_batch', array( &$this, 'batch_check' ), 10, 2 ); - } - - public function um_schedule_account_status_check() { - $interval = 3600; - - if ( ! as_next_scheduled_action( 'um_schedule_account_status_check' ) ) { - UM()->maybe_action_scheduler()->schedule_recurring_action( - time() + 60, - $interval, - 'um_schedule_account_status_check' - ); - } - } - - public function status_check() { - global $wpdb; - $batch_size = 50; - $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' - WHERE um.meta_value IS NULL OR um.meta_value = ''" - ); - - if ( absint( $total_users ) > 0 ) { - for ( $offset = 0; $offset < absint( $total_users ); $offset += $batch_size ) { - UM()->maybe_action_scheduler()->enqueue_async_action( 'um_check_account_status_batch', array( $offset, $batch_size ) ); - } - } - } - - public function batch_check( $offset, $limit ) { - $users = new WP_User_Query( - array( - 'number' => $limit, - 'offset' => $offset, - 'fields' => array( 'ID' ), - 'meta_query' => array( - 'relation' => 'OR', - array( - 'key' => 'account_status', - 'compare' => 'NOT EXISTS', - ), - array( - 'key' => 'account_status', - 'value' => '', - 'compare' => '=', - ), - ), - ) - ); - $results = $users->get_results(); - - if ( ! empty( $results ) ) { - foreach ( $results as $user ) { - update_user_meta( $user->ID, 'account_status', 'approved' ); - } - } } /** @@ -721,11 +659,12 @@ class Users { * * @param int $user_id User ID. * @param bool $force If true - ignore current user condition. + * @param bool $silent If true - don't send email notification. E.g. case when user already exists, but doesn't have a status. * * @return bool `true` if the user has been approved * `false` on failure or if the user already has approved status. */ - public function approve( $user_id, $force = false ) { + public function approve( $user_id, $force = false, $silent = false ) { if ( ! $this->can_be_approved( $user_id, $force ) ) { return false; } @@ -746,25 +685,27 @@ class Users { // It's `false` on failure or if the user already has approved status. if ( false !== $result ) { - $userdata = get_userdata( $user_id ); + if ( false === $silent ) { + $userdata = get_userdata( $user_id ); - $this->reset_activation_link( $user_id ); + $this->reset_activation_link( $user_id ); - $email_slug = 'welcome_email'; - if ( 'awaiting_admin_review' === $old_status ) { - $email_slug = 'approved_email'; - $this->maybe_generate_password_reset_key( $userdata ); + $email_slug = 'welcome_email'; + if ( 'awaiting_admin_review' === $old_status ) { + $email_slug = 'approved_email'; + $this->maybe_generate_password_reset_key( $userdata ); + } + + $current_user_id = get_current_user_id(); + um_fetch_user( $user_id ); + + add_filter( 'um_template_tags_patterns_hook', array( UM()->password(), 'add_placeholder' ) ); + add_filter( 'um_template_tags_replaces_hook', array( UM()->password(), 'add_replace_placeholder' ) ); + + UM()->maybe_action_scheduler()->enqueue_async_action( 'um_dispatch_email', array( $userdata->user_email, $email_slug, array( 'fetch_user_id' => $user_id ) ) ); + + um_fetch_user( $current_user_id ); } - - $current_user_id = get_current_user_id(); - um_fetch_user( $user_id ); - - add_filter( 'um_template_tags_patterns_hook', array( UM()->password(), 'add_placeholder' ) ); - add_filter( 'um_template_tags_replaces_hook', array( UM()->password(), 'add_replace_placeholder' ) ); - - UM()->maybe_action_scheduler()->enqueue_async_action( 'um_dispatch_email', array( $userdata->user_email, $email_slug, array( 'fetch_user_id' => $user_id ) ) ); - - um_fetch_user( $current_user_id ); /** * Fires after User has been approved. * diff --git a/includes/core/um-actions-register.php b/includes/core/um-actions-register.php index 0516d902..2459d56c 100644 --- a/includes/core/um-actions-register.php +++ b/includes/core/um-actions-register.php @@ -169,6 +169,8 @@ function um_check_user_status( $user_id, $args, $form_data = null ) { */ do_action( "um_post_registration_{$registration_status}_hook", $user_id, $args, $form_data ); + delete_user_meta( $user_id, 'um_registration_in_progress' ); // Status is set. We can delete this marker. + if ( is_null( $form_data ) || is_admin() ) { return; } @@ -502,6 +504,11 @@ function um_submit_form_register( $args, $form_data ) { 'user_pass' => $user_password, 'user_email' => $user_email, 'role' => $user_role, + 'meta_input' => array( + // It's used to ignore users who cannot finish the registration process in the scheduled tasks + // to set 'approved' status to the users without `account_status` meta. + 'um_registration_in_progress' => true, + ), ); $user_id = wp_insert_user( $userdata );