2018-01-26 12:17:41 +00:00
< ? php // @codingStandardsIgnoreLine.
2016-06-09 10:48:50 +00:00
/**
2017-02-16 11:46:01 +00:00
* Abstract WP_Background_Process class .
2016-06-09 10:48:50 +00:00
*
* @ package WP - Background - Processing
2017-02-16 11:46:01 +00:00
* @ extends WP_Async_Request
2016-06-09 10:48:50 +00:00
*/
2018-01-26 12:17:41 +00:00
defined ( 'ABSPATH' ) || exit ;
/**
* WP_Background_Process class .
*/
2017-02-16 11:46:01 +00:00
abstract class WP_Background_Process extends WP_Async_Request {
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
/**
* Action
*
* ( default value : 'background_process' )
*
* @ var string
* @ access protected
*/
protected $action = 'background_process' ;
2016-06-09 10:48:50 +00:00
/**
2017-02-16 11:46:01 +00:00
* Start time of current process .
2016-06-09 10:48:50 +00:00
*
2017-02-16 11:46:01 +00:00
* ( default value : 0 )
*
* @ var int
* @ access protected
2016-06-09 10:48:50 +00:00
*/
2017-02-16 11:46:01 +00:00
protected $start_time = 0 ;
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
/**
* Cron_hook_identifier
*
* @ var mixed
* @ access protected
*/
protected $cron_hook_identifier ;
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
/**
* Cron_interval_identifier
*
* @ var mixed
* @ access protected
*/
protected $cron_interval_identifier ;
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
/**
* Initiate new background process
*/
public function __construct () {
parent :: __construct ();
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
$this -> cron_hook_identifier = $this -> identifier . '_cron' ;
$this -> cron_interval_identifier = $this -> identifier . '_cron_interval' ;
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
add_action ( $this -> cron_hook_identifier , array ( $this , 'handle_cron_healthcheck' ) );
2018-01-26 12:17:41 +00:00
add_filter ( 'cron_schedules' , array ( $this , 'schedule_cron_healthcheck' ) ); // @codingStandardsIgnoreLine.
2017-02-16 11:46:01 +00:00
}
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
/**
2018-01-26 12:17:41 +00:00
* Dispatch .
2017-02-16 11:46:01 +00:00
*/
public function dispatch () {
// Schedule the cron healthcheck.
$this -> schedule_event ();
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
// Perform remote post.
return parent :: dispatch ();
}
/**
* Push to queue
*
* @ param mixed $data Data .
*
* @ return $this
*/
public function push_to_queue ( $data ) {
$this -> data [] = $data ;
return $this ;
}
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
/**
* Save queue
*
* @ return $this
*/
public function save () {
$key = $this -> generate_key ();
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
if ( ! empty ( $this -> data ) ) {
update_site_option ( $key , $this -> data );
2016-05-11 11:43:54 +00:00
}
2017-02-16 11:46:01 +00:00
return $this ;
}
/**
* Update queue
*
* @ param string $key Key .
* @ param array $data Data .
*
* @ return $this
*/
public function update ( $key , $data ) {
if ( ! empty ( $data ) ) {
update_site_option ( $key , $data );
2016-05-11 11:43:54 +00:00
}
2017-02-16 11:46:01 +00:00
return $this ;
}
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
/**
* Delete queue
*
2018-01-26 14:31:27 +00:00
* @ param string $key Key . Leave blank to delete all batches .
2017-02-16 11:46:01 +00:00
* @ return $this
*/
2018-01-26 14:31:27 +00:00
public function delete ( $key = '' ) {
if ( $key ) {
delete_site_option ( $key );
} else {
global $wpdb ;
$table = $wpdb -> options ;
$column = 'option_name' ;
if ( is_multisite () ) {
$table = $wpdb -> sitemeta ;
$column = 'meta_key' ;
}
$key = $wpdb -> esc_like ( $this -> identifier . '_batch_' ) . '%' ;
$wpdb -> query ( $wpdb -> prepare ( " DELETE FROM { $table } WHERE { $column } LIKE %s " , $key ) ); // @codingStandardsIgnoreLine.
}
2017-02-16 11:46:01 +00:00
return $this ;
}
/**
* Generate key
*
* Generates a unique key based on microtime . Queue items are
* given a unique key so that they can be merged upon save .
*
* @ param int $length Length .
*
* @ return string
*/
protected function generate_key ( $length = 64 ) {
$unique = md5 ( microtime () . rand () );
$prepend = $this -> identifier . '_batch_' ;
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
return substr ( $prepend . $unique , 0 , $length );
}
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
/**
2018-01-26 12:17:41 +00:00
* Maybe process queue .
2017-02-16 11:46:01 +00:00
*
* Checks whether data exists within the queue and that
* the process is not already running .
*/
public function maybe_handle () {
2018-01-26 12:17:41 +00:00
// Don't lock up other requests while processing.
session_write_close (); // @codingStandardsIgnoreLine.
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
if ( $this -> is_process_running () ) {
// Background process already running.
2016-05-11 11:43:54 +00:00
wp_die ();
}
2017-02-16 11:46:01 +00:00
if ( $this -> is_queue_empty () ) {
// No data to process.
wp_die ();
}
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
check_ajax_referer ( $this -> identifier , 'nonce' );
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
$this -> handle ();
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
wp_die ();
}
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
/**
* Is queue empty
*
* @ return bool
*/
protected function is_queue_empty () {
global $wpdb ;
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
$table = $wpdb -> options ;
$column = 'option_name' ;
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
if ( is_multisite () ) {
$table = $wpdb -> sitemeta ;
$column = 'meta_key' ;
2016-05-11 11:43:54 +00:00
}
2017-09-20 11:46:23 +00:00
$key = $wpdb -> esc_like ( $this -> identifier . '_batch_' ) . '%' ;
2016-05-11 11:43:54 +00:00
2018-01-26 12:17:41 +00:00
$count = $wpdb -> get_var ( $wpdb -> prepare ( " SELECT COUNT(*) FROM { $table } WHERE { $column } LIKE %s " , $key ) ); // @codingStandardsIgnoreLine.
2017-02-16 11:46:01 +00:00
return ( $count > 0 ) ? false : true ;
}
/**
* Is process running
*
* Check whether the current process is already running
* in a background process .
*/
protected function is_process_running () {
if ( get_site_transient ( $this -> identifier . '_process_lock' ) ) {
// Process already running.
return true ;
2016-05-11 11:43:54 +00:00
}
2017-02-16 11:46:01 +00:00
return false ;
}
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
/**
2018-01-26 12:36:07 +00:00
* Lock process .
2017-02-16 11:46:01 +00:00
*
* Lock the process so that multiple instances can ' t run simultaneously .
* Override if applicable , but the duration should be greater than that
* defined in the time_exceeded () method .
*/
protected function lock_process () {
$this -> start_time = time (); // Set start time of current process.
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
$lock_duration = ( property_exists ( $this , 'queue_lock_time' ) ) ? $this -> queue_lock_time : 60 ; // 1 minute
$lock_duration = apply_filters ( $this -> identifier . '_queue_lock_time' , $lock_duration );
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
set_site_transient ( $this -> identifier . '_process_lock' , microtime (), $lock_duration );
}
/**
* Unlock process
*
* Unlock the process so that other instances can spawn .
*
* @ return $this
*/
protected function unlock_process () {
delete_site_transient ( $this -> identifier . '_process_lock' );
return $this ;
}
/**
* Get batch
*
* @ return stdClass Return the first batch from the queue
*/
protected function get_batch () {
global $wpdb ;
$table = $wpdb -> options ;
$column = 'option_name' ;
$key_column = 'option_id' ;
$value_column = 'option_value' ;
if ( is_multisite () ) {
$table = $wpdb -> sitemeta ;
$column = 'meta_key' ;
$key_column = 'meta_id' ;
$value_column = 'meta_value' ;
2016-05-11 11:43:54 +00:00
}
2017-09-20 11:46:23 +00:00
$key = $wpdb -> esc_like ( $this -> identifier . '_batch_' ) . '%' ;
2016-05-11 11:43:54 +00:00
2018-01-26 12:17:41 +00:00
$query = $wpdb -> get_row ( $wpdb -> prepare ( " SELECT * FROM { $table } WHERE { $column } LIKE %s ORDER BY { $key_column } ASC LIMIT 1 " , $key ) ); // @codingStandardsIgnoreLine.
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
$batch = new stdClass ();
$batch -> key = $query -> $column ;
$batch -> data = maybe_unserialize ( $query -> $value_column );
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
return $batch ;
}
2016-05-11 11:43:54 +00:00
2018-01-26 14:31:27 +00:00
/**
* See if the batch limit has been exceeded .
*
* @ return bool
*/
protected function batch_limit_exceeded () {
return $this -> time_exceeded () || $this -> memory_exceeded ();
}
2017-02-16 11:46:01 +00:00
/**
* Handle
*
* Pass each queue item to the task handler , while remaining
* within server memory and time limit constraints .
*/
protected function handle () {
$this -> lock_process ();
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
do {
$batch = $this -> get_batch ();
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
foreach ( $batch -> data as $key => $value ) {
$task = $this -> task ( $value );
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
if ( false !== $task ) {
$batch -> data [ $key ] = $task ;
2016-05-11 11:43:54 +00:00
} else {
2017-02-16 11:46:01 +00:00
unset ( $batch -> data [ $key ] );
2016-05-11 11:43:54 +00:00
}
2018-01-26 14:31:27 +00:00
if ( $this -> batch_limit_exceeded () ) {
2017-02-16 11:46:01 +00:00
// Batch limits reached.
break ;
}
}
2016-05-11 11:43:54 +00:00
2018-01-26 14:31:27 +00:00
// Update or delete current batch.
if ( ! empty ( $batch -> data ) ) {
$this -> update ( $batch -> key , $batch -> data );
} else {
2017-02-16 11:46:01 +00:00
$this -> delete ( $batch -> key );
2016-05-11 11:43:54 +00:00
}
2018-01-26 14:31:27 +00:00
} while ( ! $this -> batch_limit_exceeded () && ! $this -> is_queue_empty () );
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
$this -> unlock_process ();
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
// Start next batch or complete process.
if ( ! $this -> is_queue_empty () ) {
$this -> dispatch ();
} else {
$this -> complete ();
2016-05-11 11:43:54 +00:00
}
2017-02-16 11:46:01 +00:00
}
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
/**
* Memory exceeded
*
* Ensures the batch process never exceeds 90 %
* of the maximum WordPress memory .
*
* @ return bool
*/
protected function memory_exceeded () {
$memory_limit = $this -> get_memory_limit () * 0.9 ; // 90% of max memory
$current_memory = memory_get_usage ( true );
$return = false ;
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
if ( $current_memory >= $memory_limit ) {
$return = true ;
2016-05-11 11:43:54 +00:00
}
2017-02-16 11:46:01 +00:00
return apply_filters ( $this -> identifier . '_memory_exceeded' , $return );
}
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
/**
* Get memory limit
*
* @ return int
*/
protected function get_memory_limit () {
if ( function_exists ( 'ini_get' ) ) {
$memory_limit = ini_get ( 'memory_limit' );
} else {
// Sensible default.
$memory_limit = '128M' ;
2016-05-11 11:43:54 +00:00
}
2017-06-14 17:16:39 +00:00
if ( ! $memory_limit || - 1 === intval ( $memory_limit ) ) {
2017-02-16 11:46:01 +00:00
// Unlimited, set to 32GB.
$memory_limit = '32000M' ;
2016-05-11 11:43:54 +00:00
}
2017-02-16 11:46:01 +00:00
return intval ( $memory_limit ) * 1024 * 1024 ;
}
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
/**
* Time exceeded .
*
* Ensures the batch never exceeds a sensible time limit .
* A timeout limit of 30 s is common on shared hosting .
*
* @ return bool
*/
protected function time_exceeded () {
$finish = $this -> start_time + apply_filters ( $this -> identifier . '_default_time_limit' , 20 ); // 20 seconds
$return = false ;
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
if ( time () >= $finish ) {
$return = true ;
2016-05-11 11:43:54 +00:00
}
2017-02-16 11:46:01 +00:00
return apply_filters ( $this -> identifier . '_time_exceeded' , $return );
}
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
/**
* Complete .
*
* Override if applicable , but ensure that the below actions are
* performed , or , call parent :: complete () .
*/
protected function complete () {
// Unschedule the cron healthcheck.
$this -> clear_scheduled_event ();
}
/**
* Schedule cron healthcheck
*
* @ access public
* @ param mixed $schedules Schedules .
* @ return mixed
*/
public function schedule_cron_healthcheck ( $schedules ) {
$interval = apply_filters ( $this -> identifier . '_cron_interval' , 5 );
if ( property_exists ( $this , 'cron_interval' ) ) {
$interval = apply_filters ( $this -> identifier . '_cron_interval' , $this -> cron_interval_identifier );
}
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
// Adds every 5 minutes to the existing schedules.
$schedules [ $this -> identifier . '_cron_interval' ] = array (
'interval' => MINUTE_IN_SECONDS * $interval ,
2018-01-26 12:17:41 +00:00
/* translators: %d: interval */
2017-02-16 11:46:01 +00:00
'display' => sprintf ( __ ( 'Every %d minutes' , 'woocommerce' ), $interval ),
);
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
return $schedules ;
}
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
/**
* Handle cron healthcheck
*
* Restart the background process if not already running
* and data exists in the queue .
*/
public function handle_cron_healthcheck () {
if ( $this -> is_process_running () ) {
// Background process already running.
2016-05-11 11:43:54 +00:00
exit ;
}
2017-02-16 11:46:01 +00:00
if ( $this -> is_queue_empty () ) {
// No data to process.
$this -> clear_scheduled_event ();
exit ;
2016-05-11 11:43:54 +00:00
}
2017-02-16 11:46:01 +00:00
$this -> handle ();
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
exit ;
}
/**
* Schedule event
*/
protected function schedule_event () {
if ( ! wp_next_scheduled ( $this -> cron_hook_identifier ) ) {
wp_schedule_event ( time (), $this -> cron_interval_identifier , $this -> cron_hook_identifier );
2016-05-11 11:43:54 +00:00
}
2017-02-16 11:46:01 +00:00
}
2016-05-11 11:43:54 +00:00
2017-02-16 11:46:01 +00:00
/**
* Clear scheduled event
*/
protected function clear_scheduled_event () {
$timestamp = wp_next_scheduled ( $this -> cron_hook_identifier );
2016-06-09 10:48:50 +00:00
2017-02-16 11:46:01 +00:00
if ( $timestamp ) {
wp_unschedule_event ( $timestamp , $this -> cron_hook_identifier );
}
}
2016-06-09 10:48:50 +00:00
2017-02-16 11:46:01 +00:00
/**
* Cancel Process
*
* Stop processing queue items , clear cronjob and delete batch .
*/
public function cancel_process () {
if ( ! $this -> is_queue_empty () ) {
2018-01-26 14:31:27 +00:00
$this -> delete ();
2017-02-16 11:46:01 +00:00
wp_clear_scheduled_hook ( $this -> cron_hook_identifier );
}
2016-05-11 11:43:54 +00:00
}
2017-02-16 11:46:01 +00:00
/**
* Task
*
* Override this method to perform any actions required on each
* queue item . Return the modified item for further processing
* in the next pass through . Or , return false to remove the
* item from the queue .
*
* @ param mixed $item Queue item to iterate over .
*
* @ return mixed
*/
abstract protected function task ( $item );
2016-05-13 17:39:55 +00:00
}