action = 'wc_db_upgrade'; add_action( 'plugins_loaded', array( $this, 'init' ) ); add_action( 'init', array( $this, 'process_handler' ) ); add_action( 'init', array( $this, 'clear_all_process' ) ); add_action( 'action_scheduler_failed_action', array( $this, 'restart_failed_action' ) ); } /** * Init */ public function init() { global $woocommerce_smart_coupon; // Get list of db updates. $updates = $this->get_updates(); if ( ! empty( $updates ) ) { foreach ( $updates as $update ) { // Break if version is empty. if ( empty( $update['version'] ) ) { break; } $version = $update['version']; $update_status = $this->get_status( $version ); if ( version_compare( $woocommerce_smart_coupon->get_smart_coupons_version(), $version, '>=' ) && ( false === $update_status ) ) { // Set db update status to pending. $this->set_status( $version, 'pending' ); } $handler = isset( $update['cron_handler'] ) ? $update['cron_handler'] : ''; $this->register_scheduler( $handler ); } } } /** * Process handler */ public function process_handler() { if ( ! isset( $_GET['wc_sc_update'] ) || ! isset( $_GET['wc_sc_db_update_nonce'] ) ) { return; } if ( ! wp_verify_nonce( wc_clean( wp_unslash( $_GET['wc_sc_db_update_nonce'] ) ), 'wc_sc_db_process' ) ) { // phpcs:ignore return; } $this->handle_all( wc_clean( wp_unslash( $_GET['wc_sc_update'] ) ) ); // phpcs:ignore } /** * Get list of db updates. */ public function get_updates() { return array( array( 'version' => '4.28.0', // Minimum plugin version to do the action. 'get_row_handler' => array( __CLASS__, 'get_applied_coupon_profile_options' ), // get data. 'cron_handler' => 'wcsc_move_applied_coupon_options_to_transient', // define cron handler which should be in this class. ), ); } /** * Register action schedulers for db update. * * @param string $handler Handler name. */ public function register_scheduler( $handler = '' ) { if ( ! empty( $handler ) && is_callable( array( $this, $handler ) ) ) { add_action( $handler, array( $this, $handler ) ); } } /** * Handle all updates. * * @param string $current_version Plugin version. */ protected function handle_all( $current_version = '0' ) { if ( empty( $current_version ) ) { return; } do_action( 'wc_sc_start_background_update', $current_version ); $updates = $this->get_updates(); if ( is_array( $updates ) && ! empty( $updates ) ) { foreach ( $updates as $update ) { // Compare version for db updates. if ( ! empty( $update['version'] ) && version_compare( $current_version, $update['version'], '>=' ) ) { $cron_handler = isset( $update['cron_handler'] ) ? $update['cron_handler'] : ''; if ( ! empty( $cron_handler ) && is_callable( array( $this, $cron_handler ) ) ) { $row_handler = isset( $update['get_row_handler'] ) ? $update['get_row_handler'] : ''; $this->process( $update['version'], $cron_handler, $row_handler ); } } } } } /** * Handle the process. * * @param string $version Version number. * @param string $cron_handler Cron handler action name. * @param string $row_handler Fetch data handler name. */ private function process( $version = '', $cron_handler = '', $row_handler = '' ) { $rows = ! empty( $row_handler ) && is_callable( $row_handler ) ? call_user_func( $row_handler ) : ''; if ( ! empty( $rows ) && is_array( $rows ) ) { // Start the process if the status is pending. if ( 'pending' === $this->get_status( $version ) ) { if ( function_exists( 'as_enqueue_async_action' ) ) { $this->set_status( $version, 'processing' ); as_enqueue_async_action( $cron_handler ); } } } else { // Set status to `completed` if the data is empty. $this->set_status( $version, 'completed' ); } } /** * Clear all update process. */ public function clear_all_process() { $updates = $this->get_updates(); foreach ( $updates as $update ) { $version = isset( $update['version'] ) ? $update['version'] : ''; $row_handler = isset( $update['get_row_handler'] ) ? $update['get_row_handler'] : ''; $status = $this->get_status( $version ); if ( false === $status || 'done' === $status ) { return; } $rows = ! empty( $row_handler ) && is_callable( $row_handler ) ? call_user_func( $row_handler ) : ''; if ( 'processing' === $status && empty( $rows ) ) { do_action( 'wc_sc_background_update_completed', $version ); $this->set_status( $version, 'completed' ); } } } /** * Callback Method for move options to transient. * * @return void */ public function wcsc_move_applied_coupon_options_to_transient() { $options = $this->get_applied_coupon_profile_options(); // Check if coupons are not empty. if ( ! empty( $options ) && is_array( $options ) ) { $start_time = time(); $loop = 1; foreach ( $options as $option ) { // disable auto apply. $this->move_option_to_transient( $option ); if ( $this->loop_exceeded( $loop ) || $this->time_exceeded( $start_time ) || $this->memory_exceeded() ) { // Update auto apply coupon id list. if ( function_exists( 'as_enqueue_async_action' ) ) { as_enqueue_async_action( __FUNCTION__ ); } break; } $loop++; } } } /** * Restart scheduler after one minute if it fails * * @param array $action_id id of failed action. */ public function restart_failed_action( $action_id = 0 ) { if ( empty( $action_id ) || ! class_exists( 'ActionScheduler' ) || ! is_callable( array( 'ActionScheduler', 'store' ) ) || ! function_exists( 'as_enqueue_async_action' ) ) { return; } $action = ActionScheduler::store()->fetch_action( $action_id ); $action_hook = $action->get_hook(); $updates = $this->get_updates(); if ( ! empty( $updates ) ) { foreach ( $updates as $update ) { if ( ! empty( $update['version'] ) && ! empty( $update['cron_handler'] ) ) { if ( $action_hook === $update['cron_handler'] ) { $this->set_status( $update['version'], 'processing' ); as_enqueue_async_action( $update['cron_handler'] ); } } } } } /** * Method to update the status of db upgrade. * * @param string $version wcsc db version. * @param string $status status. * * @return bool. */ public function set_status( $version = '', $status = '' ) { if ( ! empty( $version ) && ! empty( $status ) ) { $db_status = get_option( 'sc_wc_db_update_status', array() ); $db_status[ $version ] = $status; return update_option( 'sc_wc_db_update_status', $db_status, 'no' ); } return false; } /** * Method to get the status of db upgrade. * * @param string $version wcsc db version. * * @return bool. */ public function get_status( $version = '' ) { if ( ! empty( $version ) ) { $db_status = get_option( 'sc_wc_db_update_status', array() ); return ( ! empty( $db_status ) && isset( $db_status[ $version ] ) ) ? $db_status[ $version ] : false; } return false; } /** * Loop exceeded * * Ensures the batch process never exceeds to handle the given limit of row. * * @param int $loop Number of loop done. * @return bool */ public function loop_exceeded( $loop = 0 ) { return $loop > apply_filters( $this->action . '_default_row_limit', $this->row_limit ); } /** * 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 ); if ( $current_memory >= $memory_limit ) { return true; } return false; } /** * 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 === intval( $memory_limit ) ) { // Unlimited, set to 32GB. $memory_limit = '32G'; } return wp_convert_hr_to_bytes( $memory_limit ); } /** * Time exceeded. * * Ensures the batch never exceeds a sensible time limit. * A timeout limit of 30s is common on shared hosting. * * @param string $start_time start timestamp. * @return bool */ protected function time_exceeded( $start_time = '' ) { $finish = $start_time + apply_filters( $this->action . '_default_time_limit', 20 ); // 20 seconds $return = false; if ( time() >= $finish ) { $return = true; } return apply_filters( $this->action . '_time_exceeded', $return ); } /** * Get `applied_coupon_profile` options names and values * * @return array */ public function get_applied_coupon_profile_options() { global $wpdb; $option_name = 'sc_applied_coupon_profile_%'; $options = $wpdb->get_results( // @codingStandardsIgnoreLine $wpdb->prepare( "SELECT option_name, option_value FROM $wpdb->options WHERE option_name LIKE %s", $option_name ), ARRAY_A ); return $options; } /** * Method to transfer single options to transient. * * @param array $option Array of option name and value. * @return void */ protected function move_option_to_transient( $option = array() ) { if ( isset( $option['option_name'] ) && isset( $option['option_value'] ) ) { // Add new transient with option name. $move = set_transient( $option['option_name'], maybe_unserialize( $option['option_value'] ), apply_filters( 'wc_sc_applied_coupon_by_url_expire_time', MONTH_IN_SECONDS ) ); if ( true === $move ) { // Delete the option if option is successfully moved. delete_option( $option['option_name'] ); } } } } // End class } // End class exists check WC_SC_Background_Upgrade::get_instance();