OwlCyberSecurity - MANAGER
Edit File: 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'); } } }