Skip to content

Instantly share code, notes, and snippets.

@RadGH
Last active June 5, 2020 15:19
Show Gist options
  • Save RadGH/8fb0c56eddb4975e43946ebea4d35476 to your computer and use it in GitHub Desktop.
Save RadGH/8fb0c56eddb4975e43946ebea4d35476 to your computer and use it in GitHub Desktop.
WordPress Plugin: Loops through all users on your site, spread out throughout the day for efficiency. Provides a hook to do any sort of maintenance on those users. Does not actually modify any users, this just an API of sorts.
<?php
/*
Plugin Name: RS Process Users Daily
Description: Provides an API action for developers which iterates all users once per day, based on cofigurable settings. Usage: <code class="code">add_action( 'aa_process_all_users_daily/user', 'example_process_user' );</code>
Author: Radley Sustaire
Version: 1.1.0
*/
/*
// EXAMPLE
// Add an action that will process a single user. Will be called numerous times throughout the day until all users have been processed.
function example_process_user( WP_User $user ) {
$checked_count = (int) get_user_meta( $user->ID, 'checked_count', true );
$checked_count += 1;
update_user_meta( $user->ID, 'checked_count', $checked_count );
}
add_action( 'aa_process_all_users_daily/user', 'example_process_user' );
// When all users are done for one iteration
add_action( 'aa_process_all_users_daily/iteration_complete', 'something' );
// When an entire batch is completed
add_action( 'aa_process_all_users_daily/restart_batch', 'something' );
*/
class AA_Process_All_Users_Daily {
// Interval settings
// The more frequent these schedules, the fewer users need to be queried at a time.
public $schedule = array(
'interval' => 5 * MINUTE_IN_SECONDS,
'key' => '5min',
'label' => '5 Minutes',
);
public function __construct() {
// Start and stop cron schedule when plugin is enabled or disabled
register_activation_hook( __FILE__, array( $this, 'start_cron' ) );
register_deactivation_hook( __FILE__, array( $this, 'clear_cron' ) );
// Add a 5 minute cron schedule
add_filter( 'cron_schedules', array( $this, 'add_custom_schedule' ) );
// Run the process on normal cron schedule
add_action( 'aa_process_all_users_daily/schedule', array( $this, 'process_users' ) );
// Run manually with: ?process_all_users_daily=1
// Start over with: ?process_all_users_daily=1&start_over=1
if ( !empty($_GET['process_all_users_daily']) ) {
add_action( 'init', array( $this, 'process_manually' ) );
}
}
/**
* Returns total number of users from the wp_users table
*
* @return int
*/
public function get_total_users() {
global $wpdb;
$sql = "SELECT COUNT(*) FROM {$wpdb->users};";
return (int) $wpdb->get_var( $sql );
}
/**
* Save details from the last batch process in the options, and optionally send an email.
*
* @param $debug
*/
public function maybe_stash_last_batch( $debug ) {
// Get the previous process
$last_process = get_option( 'aapaud-batch-settings' );
if ( empty($last_process) ) return;
$started = $last_process['start_time'];
$ended = time();
$user_index = (int) get_option( 'aapaud-user-index' );
$last_batch = array(
'started' => $started,
'ended' => $ended,
'time_diff' => $started ? human_time_diff( $started, $ended ) : "N/A first run",
'users_processed' => $user_index,
);
// save the details of the last batch
update_option( 'aapaud-last-batch', $last_batch, false );
}
/**
* Setup the process to start from the beginning. If a previous batch was in progress, save some information from that batch for review and debugging.
*
* @param $debug
*/
public function restart_process( $debug ) {
// Check if we should start a new batch now (option may be 0 first run, that's ok)
$next_batch_time = (int) get_option( 'aapaud-next-batch-time' );
if ( $next_batch_time > time() ) {
// Do not restart the process now, wait until next batch time has been reached.
if ( $debug ) {
echo 'Batch complete, will start next day\'s batch in '. human_time_diff( $next_batch_time, time() ) .'.<br><br>Last batch:<br>';
echo '<pre>';
var_dump(get_option('aapaud-last-batch'));
echo '</pre>';
exit;
}
return;
}
// Store details from the last batch before we reset all the settings.
$this->maybe_stash_last_batch( $debug );
// Clear previous settings
delete_option( 'aapaud-batch-settings' );
// Let plugins know a new batch is starting now
do_action( 'aa_process_all_users_daily/restart_batch' );
// Get the total number of users at this time.
$total_users = $this->get_total_users();
// How many queries will occur per day? Based on schedule interval (in seconds). Final values might look like:
// 5min = 288
// hourly = 24
// twicedaily = 2
// daily = 1
$queries_per_day = ceil(DAY_IN_SECONDS / $this->schedule['interval']);
// How many users should we get per query?
$users_per_query = ceil( $total_users / $queries_per_day );
// Save the settings, these will be used until the end of the entire batch.
$settings = array(
'total_users' => $total_users,
'users_per_query' => $users_per_query,
'start_time' => time(),
);
update_option( 'aapaud-batch-settings', $settings, false );
// reset user index
update_option( 'aapaud-user-index', 0, false );
// Save when the next batch is allowed to start (so we do not process too often)
update_option( 'aapaud-next-batch-time', strtotime('+24 hours'), false );
if ( $debug ) {
echo 'Batch complete. Ready to start a new batch, refresh the page to continue.<br><br>Last batch:<br>';
echo '<pre>';
var_dump(get_option('aapaud-last-batch'));
echo '</pre>';
exit;
}
}
/**
* Check expiration date for a set of users.
*
* @param bool $debug
*/
public function process_users( $debug = false ) {
// pluggable.php is required for get_user_count to work during cron
if ( !function_exists('wp_get_current_user')) {
include_once(ABSPATH . "wp-includes/pluggable.php");
}
// Get settings that we previously calculated
$settings = get_option( 'aapaud-batch-settings' );
if ( empty($settings['start_time']) ) {
// First run, or settings got reset
$this->restart_process( $debug );
return;
}else{
// Allow admins to manually restart a batch
if ( !empty($_GET['start_over']) && current_user_can('administrator') ) {
$this->restart_process( $debug );
return;
}
}
// Total users (at start of batch)
$total_users = $settings['total_users'];
// How many users to get per query
$users_per_query = $settings['users_per_query'];
// Get the user index -- our progress in the batch. An index of 15 means that we start with the 16th user, and get $users_per_query more
$user_index = (int) get_option( 'aapaud-user-index' );
if ( !$user_index ) $user_index = 0;
// start over if the user index exceeds the total number of users
if ( $user_index > $total_users ) {
$this->restart_process( $debug );
return;
}
// Get users
$args = array(
// how many
'number' => $users_per_query,
// starting at
'offset' => $user_index,
// order by id. this means new users don't cause repeats
'orderby' => 'ID',
'order' => 'ASC',
);
$users = new WP_User_Query( $args );
// if no users were returned, restart and skip the rest of this query
if ( empty($users->get_results()) ) {
$this->restart_process( $debug );
return;
}
// loop through found users
foreach( $users->get_results() as $user ) {
/**
* Hook into this action to process a single user. Will be called multiple times in one PHP session.
*
* @hook "aa_process_all_users_daily/user"
* @param WP_User $user
* @param int $user_index
* @param array $settings
*/
do_action( 'aa_process_all_users_daily/user', $user, $user_index, $settings );
// increase the user index
$user_index++;
}
// When complete
do_action( 'aa_process_all_users_daily/iteration_complete', $user_index, $settings );
// save the user index so we can continue from where we left off next cron event
update_option( 'aapaud-user-index', $user_index, false );
// echo debugging stuff
if ( $debug ) {
ob_start();
echo '<pre>';
echo '$total_users: ', $total_users, '<br>';
echo '$users_per_query: ', $users_per_query, '<br>';
echo '$user_index: ', $user_index, '<br>';
echo '% progress: ', round(100 * ($user_index / $total_users), 2), '%<br>';
echo '$args: <br>';
var_dump($args);
echo '$users this query: ', count($users->get_results()), '<br>';
echo '$users: <br>';
foreach( $users->get_results() as $u ) {
if ( !$u instanceof WP_User ) continue;
echo "\t", $u->get('display_name'), ' (#', $u->get('ID'), ')<br>';
}
echo 'this batch duration: <br>';
var_dump( human_time_diff( $settings['start_time'], time() ) );
echo 'last batch: <br>';
var_dump( get_option('aapaud-last-batch') );
echo '</pre>';
exit;
}
}
// Allow to run manually in the browser with debug info
// Run manually with: ?process_all_users_daily=1
// Start over with: ?process_all_users_daily=1&start_over=1
public function process_manually() {
if ( current_user_can('administrator') ) {
$this->process_users( true ); // true = debug mode
}else{
echo 'You must be an administrator to process all users manually.';
exit;
}
}
// Schedule a wp_cron event
public function start_cron() {
if ( !wp_next_scheduled ( 'aa_process_all_users_daily/schedule' ) ) {
wp_schedule_event(time(), $this->schedule['key'], 'aa_process_all_users_daily/schedule');
}
}
// Clear the scheduled event
public function clear_cron() {
wp_clear_scheduled_hook( 'aa_process_all_users_daily/schedule' );
}
// Add a custom cron event schedule
public function add_custom_schedule( $schedules ) {
$schedules[ $this->schedule['key'] ] = array(
'interval' => $this->schedule['interval'],
'display' => __( $this->schedule['label'] )
);
return $schedules;
}
}
global $AA_Process_All_Users_Daily;
$AA_Process_All_Users_Daily = new AA_Process_All_Users_Daily();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment