File: /var/www/html/wpmuhibbah_err/wp-content/plugins/give/includes/class-give-background-updater.php
<?php
use Give\Framework\WordPressLibraries\WPBackgroundProcess;
/**
* Background Updater
*
* Uses https://github.com/A5hleyRich/wp-background-processing to handle DB
* updates in the background.
*
* @package Give/Classes
*
* @class Give_Background_Updater
* @version 2.0.0
* @category Class
* @author GiveWP
*
* @since 2.32.0 updated to extend WPBackgroundProcess
*/
if ( ! defined('ABSPATH')) {
exit;
}
/**
* Give_Background_Updater Class.
*/
class Give_Background_Updater extends WPBackgroundProcess
{
/**
* @var string
*/
protected $action = 'give_db_updater';
/**
* Dispatch updater.
*
* Updater will still run via cron job if this fails for any reason.
*/
public function dispatch()
{
if (give_test_ajax_works()) {
parent::dispatch();
} elseif (wp_doing_ajax()) {
$this->maybe_handle();
}
}
/**
* Get all batches.
*
* @since 2.0
* @access public
* @return stdClass
*/
public function get_all_batch()
{
return $this->get_batch();
}
/**
* Is queue empty
*
* @since 2.0.3
*
* @return bool
*/
public function has_queue()
{
return ( ! $this->is_queue_empty());
}
/**
* Lock process
*
* 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.
*
* @since 2.0.3
*/
protected function lock_process()
{
// Check if admin want to pause upgrade.
if (get_option('give_pause_upgrade')) {
self::flush_cache();
delete_option('give_paused_batches');
Give_Updates::get_instance()->__pause_db_update(true);
delete_option('give_pause_upgrade');
/**
* Fire action when pause db updates
*
* @since 2.0.1
*/
do_action('give_pause_db_upgrade', Give_Updates::get_instance());
wp_die();
}
$this->start_time = time(); // Set start time of current process.
$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);
set_transient($this->identifier . '_process_lock', microtime(), $lock_duration);
}
/**
* 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() || $this->is_paused_process()) {
// Background process already running.
return;
}
if ($this->is_queue_empty()) {
// No data to process.
$this->clear_scheduled_event();
return;
}
$this->handle();
}
/**
* Schedule fallback event.
*/
protected function schedule_event()
{
if ( ! wp_next_scheduled($this->cron_hook_identifier) && ! $this->is_paused_process()) {
wp_schedule_event(time() + 10, $this->cron_interval_identifier, $this->cron_hook_identifier);
}
}
/**
* Is queue empty
*
* @since 2.4.5
*
* @return bool
*/
protected function is_queue_empty()
{
global $wpdb;
$table = $wpdb->options;
$column = 'option_name';
$key = $wpdb->esc_like($this->identifier . '_batch_') . '%';
$count = $wpdb->get_var(
$wpdb->prepare(
"
SELECT COUNT(*)
FROM {$table}
WHERE {$column} LIKE %s
",
$key
)
);
return ! ($count > 0);
}
/**
* Get batch
*
* @since 2.4.5
*
* @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';
$key = $wpdb->esc_like($this->identifier . '_batch_') . '%';
$query = $wpdb->get_row(
$wpdb->prepare(
"
SELECT *
FROM {$table}
WHERE {$column} LIKE %s
ORDER BY {$key_column} ASC
LIMIT 1
",
$key
)
);
$batch = new stdClass();
$batch->key = $query->$column;
$batch->data = maybe_unserialize($query->$value_column);
return $batch;
}
/**
* Save queue
*
* @since 2.4.5
*
* @return $this
*/
public function save()
{
$key = $this->generate_key();
if ( ! empty($this->data)) {
update_option($key, $this->data);
}
return $this;
}
/**
* Update queue
*
* @since 2.4.5
*
* @param string $key Key.
* @param array $data Data.
*
* @return $this
*/
public function update($key, $data)
{
if ( ! empty($data)) {
update_option($key, $data);
}
return $this;
}
/**
* Delete queue
*
* @since 2.4.5
*
* @param string $key Key.
*
* @return $this
*/
public function delete($key)
{
delete_option($key);
return $this;
}
/**
* Is process running
*
* @since 2.4.5
*
* Check whether the current process is already running
* in a background process.
*/
public function is_process_running()
{
if (get_transient($this->identifier . '_process_lock')) {
// Process already running.
return true;
}
return false;
}
/**
* Unlock process
*
* Unlock the process so that other instances can spawn.
*
* @since 2.4.5
*
* @return $this
*/
protected function unlock_process()
{
delete_transient($this->identifier . '_process_lock');
return $this;
}
/**
* 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 array $update Update info
*
* @return mixed
*/
protected function task($update)
{
// Pause upgrade immediately if admin pausing upgrades.
if ($this->is_paused_process()) {
wp_die();
}
if (empty($update)) {
return false;
}
// Delete cache.
self::flush_cache();
/* @var Give_Updates $give_updates */
$give_updates = Give_Updates::get_instance();
$resume_update = get_option(
'give_doing_upgrade',
// Default update.
[
'update_info' => $update,
'step' => 1,
'update' => 1,
'heading' => sprintf('Update %s of {update_count}', 1),
'percentage' => $give_updates->percentage,
'total_percentage' => 0,
]
);
// Continuously skip update if previous update does not complete yet.
if (
$resume_update['update_info']['id'] !== $update['id'] &&
! give_has_upgrade_completed($resume_update['update_info']['id'])
) {
return $update;
}
// Set params.
$resume_update['update_info'] = $update;
$give_updates->step = absint($resume_update['step']);
$give_updates->update = absint($resume_update['update']);
$is_parent_update_completed = $give_updates->is_parent_updates_completed($update);
// Skip update if dependency update does not complete yet.
if (empty($is_parent_update_completed)) {
// @todo: set error when you have only one update with invalid dependency
if ( ! is_null($is_parent_update_completed)) {
return $update;
}
return false;
}
// Pause upgrade immediately if found following:
// 1. Running update number greater then total update count
// 2. Processing percentage greater then 100%
if ((
101 < $resume_update['total_percentage']) ||
($give_updates->get_total_db_update_count() < $resume_update['update']) ||
! in_array($resume_update['update_info']['id'], $give_updates->get_update_ids())
) {
if ( ! $this->is_paused_process()) {
$give_updates->__pause_db_update(true);
}
update_option('give_upgrade_error', 1, false);
$log_data = 'Update Task' . "\n";
$log_data .= "Total update count: {$give_updates->get_total_db_update_count()}\n";
$log_data .= 'Update IDs: ' . print_r($give_updates->get_update_ids(), true);
$log_data .= 'Update: ' . print_r($resume_update, true);
Give()->logs->add('Update Error', $log_data, 0, 'update');
wp_die();
}
// Disable cache.
Give_Cache::disable();
try {
// Run update.
if (is_array($update['callback'])) {
$object = $update['callback'][0];
$method_name = $update['callback'][1];
$object->$method_name();
} else {
$update['callback']();
}
} catch (Exception $e) {
if ( ! $this->is_paused_process()) {
$give_updates->__pause_db_update(true);
}
$log_data = 'Update Task' . "\n";
$log_data .= print_r($resume_update, true) . "\n\n";
$log_data .= "Error\n {$e->getMessage()}";
Give()->logs->add('Update Error', $log_data, 0, 'update');
update_option('give_upgrade_error', 1, false);
wp_die();
}
// Set update info.
$doing_upgrade_args = [
'update_info' => $update,
'step' => ++$give_updates->step,
'update' => $give_updates->update,
'heading' => sprintf('Update %s of %s', $give_updates->update, get_option('give_db_update_count')),
'percentage' => $give_updates->percentage,
'total_percentage' => $give_updates->get_db_update_processing_percentage(),
];
// Cache upgrade.
update_option('give_doing_upgrade', $doing_upgrade_args, false);
// Enable cache.
Give_Cache::enable();
// Check if current update completed or not.
if (give_has_upgrade_completed($update['id'])) {
return false;
}
return $update;
}
/**
* Complete
*
* Override if applicable, but ensure that the below actions are
* performed, or, call parent::complete().
*/
public function complete()
{
if ($this->is_paused_process()) {
return false;
}
parent::complete();
delete_option('give_pause_upgrade');
delete_option('give_upgrade_error');
delete_option('give_db_update_count');
delete_option('give_doing_upgrade');
add_option('give_show_db_upgrade_complete_notice', 1, '', false);
// Flush cache.
Give_Cache::flush_cache(true);
if ($cache_keys = Give_Cache::get_options_like('')) {
Give_Cache::delete($cache_keys);
}
}
/**
* 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';
}
if ( ! $memory_limit || '-1' === $memory_limit) {
// Unlimited, set to 32GB.
$memory_limit = '32G';
}
return give_let_to_num($memory_limit);
}
/**
* Maybe process queue
*
* Checks whether data exists within the queue and that
* the process is not already running.
*/
public function maybe_handle()
{
// Don't lock up other requests while processing
session_write_close();
if ($this->is_process_running() || $this->is_paused_process()) {
// Background process already running.
wp_die();
}
if ($this->is_queue_empty()) {
// No data to process.
wp_die();
}
check_ajax_referer($this->identifier, 'nonce');
$this->handle();
wp_die();
}
/**
* Handle
*
* Pass each queue item to the task handler, while remaining
* within server memory and time limit constraints.
*/
protected function handle()
{
$this->lock_process();
do {
$batch = $this->get_batch();
foreach ($batch->data as $key => $value) {
$task = $this->task($value);
if (false !== $task) {
$batch->data[$key] = $task;
} else {
unset($batch->data[$key]);
}
if ($this->time_exceeded() || $this->memory_exceeded()) {
// Batch limits reached.
break;
}
}
// Update or delete current batch.
if ( ! empty($batch->data)) {
$this->update($batch->key, $batch->data);
} else {
$this->delete($batch->key);
}
} while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty());
$this->unlock_process();
// Start next batch or complete process.
if ( ! $this->is_queue_empty()) {
// Dispatch only if ajax works.
if (give_test_ajax_works()) {
$this->dispatch();
}
} else {
$this->complete();
}
wp_die();
}
/**
* Check if backgound upgrade paused or not.
*
* @since 2.0
* @access public
* @return bool
*/
public function is_paused_process()
{
// Delete cache.
wp_cache_delete('give_paused_batches', 'options');
$paused_batches = Give_Cache_Setting::get_option('give_paused_batches');
return ! empty($paused_batches);
}
/**
* Get identifier
*
* @since 2.0
* @access public
* @return mixed|string
*/
public function get_identifier()
{
return $this->identifier;
}
/**
* Get cron identifier
*
* @since 2.0
* @access public
* @return mixed|string
*/
public function get_cron_identifier()
{
return $this->cron_hook_identifier;
}
/**
* Flush background update related cache to prevent task to go to stalled state.
*
* @since 2.0.3
*/
public static function flush_cache()
{
$options = [
'give_completed_upgrades',
'give_doing_upgrade',
'give_paused_batches',
'give_upgrade_error',
'give_db_update_count',
'give_pause_upgrade',
'give_show_db_upgrade_complete_notice',
];
foreach ($options as $option) {
wp_cache_delete($option, 'options');
}
}
}