prefix = 'wc_square'; $this->action = 'background_sync'; $this->data_key = 'product_ids'; parent::__construct(); add_action( "{$this->identifier}_job_complete", array( $this, 'job_complete' ) ); add_action( "{$this->identifier}_job_failed", array( $this, 'job_failed' ) ); add_filter( 'woocommerce_debug_tools', array( $this, 'add_debug_tool' ) ); add_action( 'wc_square_job_runner', array( $this, 'handle' ) ); // Sync healthcheck add_action( $this->cron_hook_identifier, array( $this, 'handle_sync_healthcheck' ) ); } /** * Creates a new job. * * @since 2.0.0 * * @param array $attrs array of job attributes * @return \stdClass|null */ public function create_job( $attrs ) { $sor = wc_square()->get_settings_handler()->get_system_of_record(); return parent::create_job( wp_parse_args( $attrs, array( 'action' => '', // job action 'catalog_processed' => false, // whether the Square catalog has been processed 'cursor' => '', // job advancement position 'manual' => false, // whether it's a sync job triggered manually 'percentage' => 0, // percentage completed 'product_ids' => array(), // products to process 'processed_product_ids' => array(), // newly imported products processed 'updated_product_ids' => array(), // updated products processed 'skipped_products' => array(), // remote product IDs that were skipped 'system_of_record' => $sor, // Sync setting used ) ) ); } /** * Handles job execution. * * Overridden to support our multi-step job structure. There are steps that can take a long time to process, so this * ensures only one step is performed for each background request. * * @since 2.0.0 */ public function handle() { // Schedule sync healthcheck event if not already scheduled. $this->schedule_event(); $this->lock_process(); // Get next job in the queue $job = $this->get_job(); // handle PHP errors from here on out register_shutdown_function( array( $this, 'handle_shutdown' ), $job ); // Start processing $this->process_job( $job ); $this->unlock_process(); // Start next job or complete process if ( ! $this->is_queue_empty() ) { // If the job has a retry count set, we'll retry the job after a delay. if ( isset( $job->retry ) && is_numeric( $job->retry ) && $job->retry > 0 ) { $base_delay = 30; // Base delay in seconds for rate limit errors. 30 seconds. $delay = $base_delay * ( pow( 2, $job->retry ) ); wc_square()->log( "Retrying in {$delay} seconds." ); as_schedule_single_action( time() + $delay, 'wc_square_job_runner' ); } else { as_enqueue_async_action( 'wc_square_job_runner' ); } } else { $this->complete(); } } /** * Processes a background job. * * @since 2.0.0 * * @param object|\stdClass $job * @param null $items_per_batch * @return false|object|\stdClass */ public function process_job( $job, $items_per_batch = null ) { if ( ! $job ) { return; } // indicate that the job has started processing if ( 'processing' !== $job->status ) { $job->status = 'processing'; $job->started_processing_at = current_time( 'mysql' ); $job = $this->update_job( $job ); } if ( 'poll' === $job->action ) { $job = new Interval_Polling( $job ); } elseif ( 'product_import' === $job->action ) { $job = new Product_Import( $job ); } elseif ( ! empty( $job->manual ) ) { $job = new Manual_Synchronization( $job ); } if ( $job instanceof Job ) { $current_user_id = get_current_user_id(); $job = $job->run(); wp_set_current_user( $current_user_id ); // phpcs:ignore Generic.PHP.ForbiddenFunctions.Discouraged -- required for background job processing } return $job; } /** * Handles actions after a sync job is complete. * * @since 2.0.0 * * @param $job */ public function job_complete( $job ) { wc_square()->get_sync_handler()->set_last_synced_at(); wc_square()->get_sync_handler()->record_sync( $job->processed_product_ids, $job ); wc_square()->get_email_handler()->get_sync_completed_email()->trigger( $job ); } /** * Handles actions after a sync job has failed. * * @since 2.0.0 * * @param $job */ public function job_failed( $job ) { Records::set_record( array( 'type' => 'alert', 'message' => 'Sync failed. Please try again', ) ); wc_square()->get_email_handler()->get_sync_completed_email()->trigger( $job ); } /** * No-op: implements framework parent abstract method. * * @since 2.0.0 * * @param null $item * @param \stdClass $job */ protected function process_item( $item, $job ) {} /** * Adds some helpful debug tools. * * @since 2.0.0 * * @param array $tools existing debug tools * @return array */ public function add_debug_tool( $tools ) { // this key is not unique to the plugin to avoid duplicate tools $tools['wc_square_clear_background_jobs'] = array( 'name' => __( 'Clear Square Sync', 'woocommerce-square' ), 'button' => __( 'Clear', 'woocommerce-square' ), 'desc' => __( 'This tool will clear any ongoing Square product syncs.', 'woocommerce-square' ), 'callback' => array( $this, 'run_clear_background_jobs' ), ); return $tools; } /** * Clear all background jobs of any status. * * @since 2.0.0 */ public function clear_all_jobs() { $jobs = $this->get_jobs(); if ( is_array( $jobs ) ) { $this->delete_jobs( $jobs ); } delete_transient( 'wc_square_background_sync_process_lock' ); } /** * Deletes a set of background jobs. * * @since 2.0.0 * * @param object[] $jobs jobs to delete */ public function delete_jobs( $jobs ) { foreach ( $jobs as $job ) { $this->delete_job( $job ); } } /** * Runs the "Clear Square Sync" tool. * * Provides a way for merchants to clear any ongoing or stuck product syncs. * * @since 2.0.0 */ public function run_clear_background_jobs() { $this->clear_all_jobs(); $this->debug_message = esc_html__( 'Success! You can now sync your products.', 'woocommerce-square' ); return true; } /** * Handle Sync healthcheck * * Restart the background sync process if not already running * and data exists in the queue. * * @since 3.8.2 */ public function handle_sync_healthcheck() { if ( $this->is_process_running() ) { // background process already running return; } if ( $this->is_queue_empty() ) { // no data to process return; } if ( as_has_scheduled_action( 'wc_square_job_runner' ) ) { // scheduled action for trigger sync is already exists return; } // Start the sync process as_enqueue_async_action( 'wc_square_job_runner' ); } }