get_attr( 'next_steps' ) ) && empty( $this->get_attr( 'completed_steps' ) ) ) { $this->assign_next_steps(); } $this->do_next_step(); return $this->job; } /** * Assigns the next steps needed for this sync job. * * Adds the next steps to the 'next_steps' attribute. * * @since 2.0.0 */ abstract protected function assign_next_steps(); /** * Gets the next step in the sync process. * * @since 2.0.0 * * @return string|null */ protected function get_next_step() { $next_steps = $this->get_next_steps(); return isset( $next_steps[0] ) ? $next_steps[0] : null; } /** * Gets the next steps for the sync process. * * @since 2.0.0 * * @return string[] */ protected function get_next_steps() { return $this->get_attr( 'next_steps' ); } /** * Performs the next step in the sync process. * * @since 2.0.0 */ protected function do_next_step() { $max_retry = 3; // Maximum number of retries for rate limit errors. $retry = $this->get_attr( 'retry', 0 ); // Number of retries for rate limit errors. $next_step = $this->get_next_step(); if ( is_callable( array( $this, $next_step ) ) ) { $this->start_step_cycle( $next_step ); try { $this->$next_step(); $this->complete_step_cycle( $next_step ); $this->set_attr( 'retry', 0 ); // Reset retry count to 0 after successful step cycle. } catch ( \Exception $exception ) { $error_message = $exception->getMessage(); // If sync fail with rate limit error, retry the sync process after few seconds. (retry upto 3 times) if ( false !== strpos( $error_message, 'RATE_LIMITED' ) && $retry < $max_retry ) { wc_square()->log( 'Rate limit error detected, pausing sync process for few secs...' ); $this->set_attr( 'retry', $retry + 1 ); return; } $this->complete_step_cycle( $next_step, false, $exception->getMessage() ); $this->fail( $exception->getMessage() ); return; } } if ( ! $this->get_next_step() ) { $this->complete(); } } /** * Records the beginning of a new step cycle, meaning a new loop on the job for a given step. * * @since 2.0.0 * * @param string $step_name the step name */ protected function start_step_cycle( $step_name ) { $current_step_cycle = array( 'step_name' => $step_name, 'start_time' => microtime( true ), ); wc_square()->log( "Starting step cycle: $step_name" ); $this->set_attr( 'current_step_cycle', $current_step_cycle ); } /** * Records the completion of a step cycle. * * @since 2.0.0 * * @param string $step_name the step name * @param bool $is_successful (optional) whether the step completion is from a success or not * @param string $error_message (optional) error message to include with failed step log */ protected function complete_step_cycle( $step_name, $is_successful = true, $error_message = '' ) { $current_step_cycle = $this->get_attr( 'current_step_cycle', array() ); if ( ! empty( $current_step_cycle ) ) { $current_step_cycle['end_time'] = microtime( true ); $current_step_cycle['runtime'] = number_format( $current_step_cycle['end_time'] - $current_step_cycle['start_time'], 2 ) . 's'; $current_step_cycle['success'] = true === $is_successful; if ( true === $is_successful ) { wc_square()->log( "Completed step cycle: $step_name ({$current_step_cycle['runtime']})" ); } else { wc_square()->log( "Failed step cycle: $step_name ({$current_step_cycle['runtime']}) - $error_message" ); } $completed_cycles = $this->get_attr( 'completed_step_cycles', array() ); $completed_cycles[] = $current_step_cycle; $this->set_attr( 'completed_step_cycles', $completed_cycles ); } } /** * Completes the specified step (if it's the next step). * * @since 2.0.0 * * @param string $step_name */ protected function complete_step( $step_name ) { $next_steps = $this->get_next_steps(); if ( isset( $next_steps[0] ) && $step_name === $next_steps[0] ) { $this->add_completed_step( $step_name ); array_shift( $next_steps ); $this->set_attr( 'next_steps', $next_steps ); } } /** * Adds a step to the completed steps array. * * @since 2.0.0 * * @param string $step_name */ protected function add_completed_step( $step_name ) { if ( empty( $step_name ) ) { return; } $completed_steps = $this->get_attr( 'completed_steps', array() ); $completed_steps[] = array( 'name' => $step_name, 'completion_time' => current_time( 'mysql' ), ); $this->set_attr( 'completed_steps', $completed_steps ); $update_data = $this->get_step_update_data( $step_name ); wc_square()->log( 'Completed job step: ' . $step_name . $update_data ); } /** * Get step update data like count of synced products or categories. * * @param string $step_name Step name. * @return string */ protected function get_step_update_data( $step_name ) { $update_data = ''; $count = $this->get_attr( $step_name . '_count', 0 ); switch ( $step_name ) { // Product Import. case 'import_products': $imported = count( $this->get_attr( 'processed_product_ids', array() ) ); $updated = count( $this->get_attr( 'updated_product_ids', array() ) ); $skipped = count( $this->get_attr( 'skipped_products', array() ) ); $update_data = sprintf( ' (Imported products: %d, Updated products: %d, Skipped products: %d)', $imported, $updated, $skipped ); break; case 'import_inventory': $update_data = sprintf( ' (Synced products: %d)', $count ); break; // Manual Sync. case 'validate_products': $count = count( $this->get_attr( 'validated_product_ids', array() ) ); $update_data = sprintf( ' (Validated products: %d)', $count ); break; case 'extract_category_ids': $count = count( $this->get_attr( 'category_ids', array() ) ); $update_data = sprintf( ' (Extracted categories: %d)', $count ); break; case 'refresh_category_mappings': $mapped_cat = count( $this->get_attr( 'mapped_categories', array() ) ); $unmapped_cat = count( $this->get_attr( 'unmapped_categories', array() ) ); $update_data = sprintf( ' (Mapped categories: %d, Unmapped categories: %d)', $mapped_cat, $unmapped_cat ); break; case 'query_unmapped_categories': $mapped_cat = count( $this->get_attr( 'mapped_categories', array() ) ); $update_data = sprintf( ' (Total mapped categories: %d)', $mapped_cat ); break; case 'upsert_categories': $count = count( $this->get_attr( 'category_ids', array() ) ); $update_data = sprintf( ' (Upserted categories: %d)', $count ); break; case 'update_matched_products': $count = count( $this->get_attr( 'processed_product_ids', array() ) ); $update_data = sprintf( ' (Synced matched products: %d)', $count ); break; case 'search_matched_products': case 'square_sor_sync': $count = count( $this->get_attr( 'processed_product_ids', array() ) ); $update_data = sprintf( ' (Synced products: %d)', $count ); break; case 'upsert_new_products': $count = count( $this->get_attr( 'inventory_push_product_ids', array() ) ); $update_data = sprintf( ' (Newly upserted products: %d)', $count ); break; case 'push_inventory': $update_data = sprintf( ' (Synced products: %d)', $count ); break; case 'pull_inventory': $count = count( $this->get_attr( 'processed_square_variation_ids', array() ) ); $update_data = sprintf( ' (Synced products: %d)', $count ); break; // Interval Polling. case 'update_category_data': $update_data = sprintf( ' (Updated categories: %d)', $count ); break; case 'update_product_data': $update_data = sprintf( ' (Updated products: %d)', $count ); break; case 'update_inventory_counts': $update_data = sprintf( ' (Synced products: %d)', $count ); break; default: break; } return $update_data; } }