CustomersScheduler.php
3 years ago
ImportInterface.php
4 years ago
ImportScheduler.php
1 month ago
MailchimpScheduler.php
1 year ago
OrdersScheduler.php
1 month ago
ImportScheduler.php
241 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Import related functions and actions. |
| 4 | */ |
| 5 | |
| 6 | namespace Automattic\WooCommerce\Internal\Admin\Schedulers; |
| 7 | |
| 8 | defined( 'ABSPATH' ) || exit; |
| 9 | |
| 10 | use Automattic\WooCommerce\Admin\API\Reports\Cache as ReportsCache; |
| 11 | use Automattic\WooCommerce\Admin\Schedulers\SchedulerTraits; |
| 12 | |
| 13 | /** |
| 14 | * ImportScheduler class. |
| 15 | */ |
| 16 | abstract class ImportScheduler implements ImportInterface { |
| 17 | /** |
| 18 | * Import stats option name. |
| 19 | */ |
| 20 | const IMPORT_STATS_OPTION = 'woocommerce_admin_import_stats'; |
| 21 | |
| 22 | /** |
| 23 | * Scheduler name. Subclasses must override this with a concrete value. |
| 24 | * |
| 25 | * @var string |
| 26 | */ |
| 27 | public static $name = ''; |
| 28 | |
| 29 | /** |
| 30 | * Scheduler traits. |
| 31 | */ |
| 32 | use SchedulerTraits { |
| 33 | get_batch_sizes as get_scheduler_batch_sizes; |
| 34 | } |
| 35 | |
| 36 | /** |
| 37 | * Returns true if an import is in progress. |
| 38 | * |
| 39 | * @internal |
| 40 | * @return bool |
| 41 | */ |
| 42 | public static function is_importing() { |
| 43 | $pending_jobs = self::queue()->search( |
| 44 | array( |
| 45 | 'status' => 'pending', |
| 46 | 'per_page' => 1, |
| 47 | 'claimed' => false, |
| 48 | 'search' => 'import', |
| 49 | 'group' => self::$group, |
| 50 | ) |
| 51 | ); |
| 52 | if ( empty( $pending_jobs ) ) { |
| 53 | $in_progress = self::queue()->search( |
| 54 | array( |
| 55 | 'status' => 'in-progress', |
| 56 | 'per_page' => 1, |
| 57 | 'search' => 'import', |
| 58 | 'group' => self::$group, |
| 59 | ) |
| 60 | ); |
| 61 | } |
| 62 | |
| 63 | return ! empty( $pending_jobs ) || ! empty( $in_progress ); |
| 64 | } |
| 65 | |
| 66 | /** |
| 67 | * Get batch sizes. |
| 68 | * |
| 69 | * @internal |
| 70 | * @return array |
| 71 | */ |
| 72 | public static function get_batch_sizes() { |
| 73 | return array_merge( |
| 74 | self::get_scheduler_batch_sizes(), |
| 75 | array( |
| 76 | 'delete' => 10, |
| 77 | 'import' => 25, |
| 78 | 'queue' => 100, |
| 79 | ) |
| 80 | ); |
| 81 | |
| 82 | } |
| 83 | |
| 84 | /** |
| 85 | * Get all available scheduling actions. |
| 86 | * Used to determine action hook names and clear events. |
| 87 | * |
| 88 | * @internal |
| 89 | * @return array |
| 90 | */ |
| 91 | public static function get_scheduler_actions() { |
| 92 | return array( |
| 93 | 'import_batch_init' => 'wc-admin_import_batch_init_' . static::$name, |
| 94 | 'import_batch' => 'wc-admin_import_batch_' . static::$name, |
| 95 | 'delete_batch_init' => 'wc-admin_delete_batch_init_' . static::$name, |
| 96 | 'delete_batch' => 'wc-admin_delete_batch_' . static::$name, |
| 97 | 'import' => 'wc-admin_import_' . static::$name, |
| 98 | ); |
| 99 | } |
| 100 | |
| 101 | /** |
| 102 | * Queue the imports into multiple batches. |
| 103 | * |
| 104 | * @internal |
| 105 | * @param integer|boolean $days Number of days to import. |
| 106 | * @param boolean $skip_existing Skip existing records. |
| 107 | */ |
| 108 | public static function import_batch_init( $days, $skip_existing ) { |
| 109 | $batch_size = static::get_batch_size( 'import' ); |
| 110 | $items = static::get_items( 1, 1, $days, $skip_existing ); |
| 111 | |
| 112 | if ( 0 === $items->total ) { |
| 113 | return; |
| 114 | } |
| 115 | |
| 116 | $num_batches = ceil( $items->total / $batch_size ); |
| 117 | |
| 118 | self::queue_batches( 1, $num_batches, 'import_batch', array( $days, $skip_existing ) ); |
| 119 | } |
| 120 | |
| 121 | /** |
| 122 | * Imports a batch of items to update. |
| 123 | * |
| 124 | * @internal |
| 125 | * @param int $batch_number Batch number to import (essentially a query page number). |
| 126 | * @param int|bool $days Number of days to import. |
| 127 | * @param bool $skip_existing Skip existing records. |
| 128 | * @return void |
| 129 | * @throws \Throwable Re-throws any error from a failed item import after logging diagnostics. |
| 130 | */ |
| 131 | public static function import_batch( $batch_number, $days, $skip_existing ) { |
| 132 | $batch_size = static::get_batch_size( 'import' ); |
| 133 | |
| 134 | $properties = array( |
| 135 | 'batch_number' => $batch_number, |
| 136 | 'batch_size' => $batch_size, |
| 137 | 'type' => static::$name, |
| 138 | ); |
| 139 | wc_admin_record_tracks_event( 'import_job_start', $properties ); |
| 140 | |
| 141 | // When we are skipping already imported items, the table of items to import gets smaller in |
| 142 | // every batch, so we want to always import the first page. |
| 143 | $page = $skip_existing ? 1 : $batch_number; |
| 144 | $items = static::get_items( $batch_size, $page, $days, $skip_existing ); |
| 145 | |
| 146 | foreach ( $items->ids as $id ) { |
| 147 | try { |
| 148 | static::import( $id ); |
| 149 | } catch ( \Throwable $e ) { |
| 150 | static::log_import_error( $id, $e ); |
| 151 | throw $e; |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | $import_stats = get_option( self::IMPORT_STATS_OPTION, array() ); |
| 156 | $imported_count = absint( $import_stats[ static::$name ]['imported'] ) + count( $items->ids ); |
| 157 | $import_stats[ static::$name ]['imported'] = $imported_count; |
| 158 | update_option( self::IMPORT_STATS_OPTION, $import_stats ); |
| 159 | |
| 160 | $properties['imported_count'] = $imported_count; |
| 161 | |
| 162 | wc_admin_record_tracks_event( 'import_job_complete', $properties ); |
| 163 | } |
| 164 | |
| 165 | /** |
| 166 | * Log details for an item that failed analytics import. |
| 167 | * |
| 168 | * @internal |
| 169 | * @param int $item_id Import item ID. |
| 170 | * @param \Throwable $error Error thrown during import. |
| 171 | * @param array $context Additional logger context. |
| 172 | * @return void |
| 173 | */ |
| 174 | final protected static function log_import_error( $item_id, \Throwable $error, array $context = array() ) { |
| 175 | $logger = wc_get_logger(); |
| 176 | $log_context = array_merge( |
| 177 | array( |
| 178 | 'source' => 'wc-analytics-import', |
| 179 | ), |
| 180 | $context, |
| 181 | array( |
| 182 | 'scheduler' => static::$name, |
| 183 | 'item_id' => (int) $item_id, |
| 184 | 'exception_class' => get_class( $error ), |
| 185 | 'exception_file' => $error->getFile(), |
| 186 | 'exception_line' => $error->getLine(), |
| 187 | 'trace' => $error->getTraceAsString(), |
| 188 | ) |
| 189 | ); |
| 190 | |
| 191 | $logger->error( |
| 192 | sprintf( |
| 193 | 'Failed to import analytics item %1$d for %2$s: [%3$s] %4$s in %5$s:%6$d', |
| 194 | $item_id, |
| 195 | static::$name, |
| 196 | get_class( $error ), |
| 197 | $error->getMessage(), |
| 198 | $error->getFile(), |
| 199 | $error->getLine() |
| 200 | ), |
| 201 | $log_context |
| 202 | ); |
| 203 | } |
| 204 | |
| 205 | /** |
| 206 | * Queue item deletion in batches. |
| 207 | * |
| 208 | * @internal |
| 209 | */ |
| 210 | public static function delete_batch_init() { |
| 211 | global $wpdb; |
| 212 | $batch_size = static::get_batch_size( 'delete' ); |
| 213 | $count = static::get_total_imported(); |
| 214 | |
| 215 | if ( 0 === $count ) { |
| 216 | return; |
| 217 | } |
| 218 | |
| 219 | $num_batches = ceil( $count / $batch_size ); |
| 220 | |
| 221 | self::queue_batches( 1, $num_batches, 'delete_batch' ); |
| 222 | } |
| 223 | |
| 224 | /** |
| 225 | * Delete a batch by passing the count to be deleted to the child delete method. |
| 226 | * |
| 227 | * @internal |
| 228 | * @return void |
| 229 | */ |
| 230 | public static function delete_batch() { |
| 231 | wc_admin_record_tracks_event( 'delete_import_data_job_start', array( 'type' => static::$name ) ); |
| 232 | |
| 233 | $batch_size = static::get_batch_size( 'delete' ); |
| 234 | static::delete( $batch_size ); |
| 235 | |
| 236 | ReportsCache::invalidate(); |
| 237 | |
| 238 | wc_admin_record_tracks_event( 'delete_import_data_job_complete', array( 'type' => static::$name ) ); |
| 239 | } |
| 240 | } |
| 241 |