From 6bb1971eb9d03fc74ec26320837ef06cdbf226b1 Mon Sep 17 00:00:00 2001 From: Joshua Dinh <75056371+JoshuaHungDinh@users.noreply.github.com> Date: Mon, 20 Nov 2023 10:34:47 +0900 Subject: [PATCH] Fix: remove and replace deprecated WP_Background_Process & WP_Async_Request (#7099) --- includes/class-give-async-process.php | 5 +- includes/libraries/wp-async-request.php | 164 ------ includes/libraries/wp-background-process.php | 507 ------------------ .../WordPressLibraries/WPAsyncRequest.php | 6 +- .../WPBackgroundProcess.php | 4 + .../LegacyServiceProvider.php | 18 +- 6 files changed, 17 insertions(+), 687 deletions(-) delete mode 100644 includes/libraries/wp-async-request.php delete mode 100644 includes/libraries/wp-background-process.php diff --git a/includes/class-give-async-process.php b/includes/class-give-async-process.php index b8cca519a2..06195d13fc 100644 --- a/includes/class-give-async-process.php +++ b/includes/class-give-async-process.php @@ -1,4 +1,6 @@ identifier = $this->prefix . '_' . $this->action; - - add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) ); - add_action( 'wp_ajax_nopriv_' . $this->identifier, array( $this, 'maybe_handle' ) ); - } - - /** - * Set data used during the request - * - * @param array $data Data. - * - * @return $this - */ - public function data( $data ) { - $this->data = $data; - - return $this; - } - - /** - * Dispatch the async request - * - * @return array|WP_Error - */ - public function dispatch() { - $url = add_query_arg( $this->get_query_args(), $this->get_query_url() ); - $args = $this->get_post_args(); - - return wp_remote_post( esc_url_raw( $url ), $args ); - } - - /** - * Get query args - * - * @return array - */ - protected function get_query_args() { - if ( property_exists( $this, 'query_args' ) ) { - return $this->query_args; - } - - return array( - 'action' => $this->identifier, - 'nonce' => wp_create_nonce( $this->identifier ), - ); - } - - /** - * Get query URL - * - * @return string - */ - protected function get_query_url() { - if ( property_exists( $this, 'query_url' ) ) { - return $this->query_url; - } - - return admin_url( 'admin-ajax.php' ); - } - - /** - * Get post args - * - * @return array - */ - protected function get_post_args() { - if ( property_exists( $this, 'post_args' ) ) { - return $this->post_args; - } - - return array( - 'timeout' => 0.01, - 'blocking' => false, - 'body' => $this->data, - 'cookies' => $_COOKIE, - 'sslverify' => apply_filters( 'https_local_ssl_verify', false ), - ); - } - - /** - * Maybe handle - * - * Check for correct nonce and pass to handler. - */ - public function maybe_handle() { - // Don't lock up other requests while processing - session_write_close(); - - check_ajax_referer( $this->identifier, 'nonce' ); - - $this->handle(); - - wp_die(); - } - - /** - * Handle - * - * Override this method to perform any actions required - * during the async request. - */ - abstract protected function handle(); - - } -} diff --git a/includes/libraries/wp-background-process.php b/includes/libraries/wp-background-process.php deleted file mode 100644 index d01c068bbf..0000000000 --- a/includes/libraries/wp-background-process.php +++ /dev/null @@ -1,507 +0,0 @@ -cron_hook_identifier = $this->identifier . '_cron'; - $this->cron_interval_identifier = $this->identifier . '_cron_interval'; - - add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) ); - add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) ); - } - - /** - * Dispatch - * - * @access public - * @return void - */ - public function dispatch() { - // Schedule the cron healthcheck. - $this->schedule_event(); - - // 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; - } - - /** - * Save queue - * - * @return $this - */ - public function save() { - $key = $this->generate_key(); - - if ( ! empty( $this->data ) ) { - update_site_option( $key, $this->data ); - } - - 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 ); - } - - return $this; - } - - /** - * Delete queue - * - * @param string $key Key. - * - * @return $this - */ - public function delete( $key ) { - delete_site_option( $key ); - - 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_'; - - return substr( $prepend . $unique, 0, $length ); - } - - /** - * 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() ) { - // 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(); - } - - /** - * Is queue empty - * - * @return bool - */ - protected function is_queue_empty() { - global $wpdb; - - $table = $wpdb->options; - $column = 'option_name'; - - if ( is_multisite() ) { - $table = $wpdb->sitemeta; - $column = 'meta_key'; - } - - $key = $this->identifier . '_batch_%'; - - $count = $wpdb->get_var( $wpdb->prepare( " - SELECT COUNT(*) - FROM {$table} - WHERE {$column} LIKE %s - ", $key ) ); - - return ( $count > 0 ) ? false : true; - } - - /** - * Is process running - * - * Check whether the current process is already running - * in a background process. - */ - public function is_process_running() { - if ( get_site_transient( $this->identifier . '_process_lock' ) ) { - // Process already running. - return true; - } - - return false; - } - - /** - * 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. - */ - protected function lock_process() { - $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_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'; - } - - $key = $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; - } - - /** - * 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() ) { - $this->dispatch(); - } else { - $this->complete(); - } - - wp_die(); - } - - /** - * 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; - - if ( $current_memory >= $memory_limit ) { - $return = true; - } - - return apply_filters( $this->identifier . '_memory_exceeded', $return ); - } - - /** - * 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 = '32000M'; - } - - return intval( $memory_limit ) * 1024 * 1024; - } - - /** - * Time exceeded. - * - * Ensures the batch never exceeds a sensible time limit. - * A timeout limit of 30s 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; - - if ( time() >= $finish ) { - $return = true; - } - - return apply_filters( $this->identifier . '_time_exceeded', $return ); - } - - /** - * 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 ); - } - - // Adds every 5 minutes to the existing schedules. - $schedules[ $this->identifier . '_cron_interval' ] = array( - 'interval' => MINUTE_IN_SECONDS * $interval, - 'display' => sprintf( __( 'Every %d Minutes', 'give' ), $interval ), - ); - - return $schedules; - } - - /** - * 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. - exit; - } - - if ( $this->is_queue_empty() ) { - // No data to process. - $this->clear_scheduled_event(); - exit; - } - - $this->handle(); - - 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 ); - } - } - - /** - * Clear scheduled event - */ - protected function clear_scheduled_event() { - $timestamp = wp_next_scheduled( $this->cron_hook_identifier ); - - if ( $timestamp ) { - wp_unschedule_event( $timestamp, $this->cron_hook_identifier ); - } - } - - /** - * Cancel Process - * - * Stop processing queue items, clear cronjob and delete batch. - * - */ - public function cancel_process() { - if ( ! $this->is_queue_empty() ) { - $batch = $this->get_batch(); - - $this->delete( $batch->key ); - - wp_clear_scheduled_hook( $this->cron_hook_identifier ); - } - - } - - /** - * 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 ); - - } -} diff --git a/src/Framework/WordPressLibraries/WPAsyncRequest.php b/src/Framework/WordPressLibraries/WPAsyncRequest.php index 6341ffb79d..bb6bca655b 100644 --- a/src/Framework/WordPressLibraries/WPAsyncRequest.php +++ b/src/Framework/WordPressLibraries/WPAsyncRequest.php @@ -3,7 +3,11 @@ namespace Give\Framework\WordPressLibraries; /** - * This is a fork of WP_Async_Request that adds GiveWP namespaces to prevent conflicts with other plugins. + * This is a fork of WP_Async_Request that adds GiveWP namespaces to prevent conflicts with other plugins. + * + * IMPORTANT: Developers, please be aware that the usage of WPAsyncRequest and WPBackgroundProcess is discouraged as they are included only for legacy purposes. + * Instead, it is strongly recommended to use Action Scheduler for any asynchronous processing needs. + * Action Scheduler is available, provides a more efficient solution, and is the preferred choice for new development. * * @since 2.32.0 */ diff --git a/src/Framework/WordPressLibraries/WPBackgroundProcess.php b/src/Framework/WordPressLibraries/WPBackgroundProcess.php index 1eaef9cda4..d9ad61cefb 100644 --- a/src/Framework/WordPressLibraries/WPBackgroundProcess.php +++ b/src/Framework/WordPressLibraries/WPBackgroundProcess.php @@ -6,6 +6,10 @@ /** * This is a fork of WP_Background_Process that adds GiveWP namespaces to prevent conflicts with other plugins. * + * IMPORTANT: Developers, please be aware that the usage of WPAsyncRequest and WPBackgroundProcess is discouraged as they are included only for legacy purposes. + * Instead, it is strongly recommended to use Action Scheduler for any asynchronous processing needs. + * Action Scheduler is available, provides a more efficient solution, and is the preferred choice for new development. + * * @since 2.32.0 */ abstract class WPBackgroundProcess extends WPAsyncRequest diff --git a/src/ServiceProviders/LegacyServiceProvider.php b/src/ServiceProviders/LegacyServiceProvider.php index a599f60336..37c76a639d 100644 --- a/src/ServiceProviders/LegacyServiceProvider.php +++ b/src/ServiceProviders/LegacyServiceProvider.php @@ -39,6 +39,7 @@ public function boot() /** * Load all the legacy class files since they don't have auto-loading * + * @unrleased remove WP_Background_Process & WP_Async_Request in favor of namespaced versions. * @since 3.0.0 remove the manual (Test Donations) gateway from loading in favor of the new Test Donations gateway * @since 2.8.0 */ @@ -48,17 +49,6 @@ private function includeLegacyFiles() require_once GIVE_PLUGIN_DIR . 'includes/class-give-cache-setting.php'; - /** - * Load libraries. - */ - if (!class_exists('WP_Async_Request')) { - include_once GIVE_PLUGIN_DIR . 'includes/libraries/wp-async-request.php'; - } - - if (!class_exists('WP_Background_Process')) { - include_once GIVE_PLUGIN_DIR . 'includes/libraries/wp-background-process.php'; - } - require_once GIVE_PLUGIN_DIR . 'includes/setting-functions.php'; require_once GIVE_PLUGIN_DIR . 'includes/country-functions.php'; require_once GIVE_PLUGIN_DIR . 'includes/template-functions.php'; @@ -230,10 +220,10 @@ private function bindClasses() * * @since 2.8.0 * - * @param string $alias + * @param string $alias * @param string|Closure $class - * @param string $includesPath - * @param bool $singleton + * @param string $includesPath + * @param bool $singleton */ private function bindInstance($alias, $class, $includesPath, $singleton = false) {