OrderLogsCleanupHelper.php
2 months ago
OrderLogsDeletionProcessor.php
2 months ago
RemoteLogger.php
9 months ago
SafeGlobalFunctionProxy.php
1 year ago
OrderLogsDeletionProcessor.php
262 lines
| 1 | <?php |
| 2 | |
| 3 | declare( strict_types=1 ); |
| 4 | |
| 5 | namespace Automattic\WooCommerce\Internal\Logging; |
| 6 | |
| 7 | use Automattic\WooCommerce\Internal\BatchProcessing\BatchProcessorInterface; |
| 8 | use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController; |
| 9 | use Automattic\WooCommerce\Proxies\LegacyProxy; |
| 10 | use Automattic\WooCommerce\Utilities\StringUtil; |
| 11 | |
| 12 | /** |
| 13 | * Batch processor for deleting log entries of completed orders. |
| 14 | * It only works when either HPOS is enabled or the orders data store is the old CPT-based one, |
| 15 | * because otherwise the ability to query orders by meta key is not guaranteed. |
| 16 | */ |
| 17 | class OrderLogsDeletionProcessor implements BatchProcessorInterface { |
| 18 | |
| 19 | /** |
| 20 | * Constant representing the default size of the batches to process. |
| 21 | */ |
| 22 | public const DEFAULT_BATCH_SIZE = 1000; |
| 23 | |
| 24 | /** |
| 25 | * True if HPOS is enabled. |
| 26 | * |
| 27 | * @var bool |
| 28 | */ |
| 29 | private bool $hpos_in_use = false; |
| 30 | |
| 31 | /** |
| 32 | * True if HPOS is disabled and the orders data store in use is the old CPT one. |
| 33 | * |
| 34 | * @var bool |
| 35 | */ |
| 36 | private bool $cpt_in_use = false; |
| 37 | |
| 38 | /** |
| 39 | * The instance of LegacyProxy to use. |
| 40 | * |
| 41 | * @var LegacyProxy |
| 42 | */ |
| 43 | private LegacyProxy $legacy_proxy; |
| 44 | |
| 45 | /** |
| 46 | * The instance of OrderLogsCleanupHelper to use. |
| 47 | * |
| 48 | * @var OrderLogsCleanupHelper |
| 49 | */ |
| 50 | private OrderLogsCleanupHelper $order_logs_cleanup_helper; |
| 51 | |
| 52 | /** |
| 53 | * Initialize the instance. |
| 54 | * This is invoked by the dependency injection container. |
| 55 | * |
| 56 | * @param CustomOrdersTableController $hpos_controller The instance of CustomOrdersTableController to use. |
| 57 | * @param LegacyProxy $legacy_proxy The instance of LegacyProxy to use. |
| 58 | * @param OrderLogsCleanupHelper $order_logs_cleanup_helper The instance of OrderLogsCleanupHelper to use. |
| 59 | * |
| 60 | * @internal |
| 61 | */ |
| 62 | final public function init( CustomOrdersTableController $hpos_controller, LegacyProxy $legacy_proxy, OrderLogsCleanupHelper $order_logs_cleanup_helper ) { |
| 63 | $this->hpos_in_use = $hpos_controller->custom_orders_table_usage_is_enabled(); |
| 64 | if ( ! $this->hpos_in_use ) { |
| 65 | $this->cpt_in_use = \WC_Order_Data_Store_CPT::class === \WC_Data_Store::load( 'order' )->get_current_class_name(); |
| 66 | } |
| 67 | |
| 68 | $this->legacy_proxy = $legacy_proxy; |
| 69 | $this->order_logs_cleanup_helper = $order_logs_cleanup_helper; |
| 70 | } |
| 71 | |
| 72 | /** |
| 73 | * Get the name of the processor. |
| 74 | * |
| 75 | * @return string |
| 76 | */ |
| 77 | public function get_name(): string { |
| 78 | return 'Order logs deletion process'; |
| 79 | } |
| 80 | |
| 81 | /** |
| 82 | * Get a description of the processor. |
| 83 | * |
| 84 | * @return string |
| 85 | */ |
| 86 | public function get_description(): string { |
| 87 | return 'Deletes debug logs of completed orders.'; |
| 88 | } |
| 89 | |
| 90 | /** |
| 91 | * Get the default batch size for this processor. |
| 92 | * |
| 93 | * @return int |
| 94 | */ |
| 95 | public function get_default_batch_size(): int { |
| 96 | return self::DEFAULT_BATCH_SIZE; |
| 97 | } |
| 98 | |
| 99 | /** |
| 100 | * Get the total count of entries pending processing. |
| 101 | * |
| 102 | * @return int |
| 103 | */ |
| 104 | public function get_total_pending_count(): int { |
| 105 | if ( $this->hpos_in_use ) { |
| 106 | return $this->get_total_pending_count_hpos(); |
| 107 | } elseif ( $this->cpt_in_use ) { |
| 108 | return $this->get_total_pending_count_cpt(); |
| 109 | } else { |
| 110 | $this->throw_doing_it_wrong( StringUtil::class_name_without_namespace( __CLASS__ ) . '::' . __FUNCTION__ ); |
| 111 | return 0; |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | /** |
| 116 | * Get the total count of entries pending processing, HPOS version. |
| 117 | * |
| 118 | * @return int |
| 119 | */ |
| 120 | private function get_total_pending_count_hpos(): int { |
| 121 | global $wpdb; |
| 122 | |
| 123 | return (int) $wpdb->get_var( |
| 124 | $wpdb->prepare( |
| 125 | "SELECT COUNT(*) |
| 126 | FROM {$wpdb->prefix}wc_orders_meta |
| 127 | WHERE meta_key = %s", |
| 128 | '_debug_log_source_pending_deletion' |
| 129 | ) |
| 130 | ); |
| 131 | } |
| 132 | |
| 133 | /** |
| 134 | * Get the total count of entries pending processing, CPT datastore version. |
| 135 | * |
| 136 | * @return int |
| 137 | */ |
| 138 | private function get_total_pending_count_cpt(): int { |
| 139 | global $wpdb; |
| 140 | |
| 141 | return (int) $wpdb->get_var( |
| 142 | $wpdb->prepare( |
| 143 | "SELECT COUNT(*) |
| 144 | FROM {$wpdb->postmeta} pm |
| 145 | INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID |
| 146 | WHERE pm.meta_key = %s |
| 147 | AND p.post_type = %s", |
| 148 | '_debug_log_source_pending_deletion', |
| 149 | 'shop_order' |
| 150 | ) |
| 151 | ); |
| 152 | } |
| 153 | |
| 154 | /** |
| 155 | * Get the next batch of items to process. |
| 156 | * An item will be an associative array of 'order_id' and 'meta_value'. |
| 157 | * |
| 158 | * @param int $size Maximum size of the batch to return. |
| 159 | * @return array |
| 160 | */ |
| 161 | public function get_next_batch_to_process( int $size ): array { |
| 162 | if ( $this->hpos_in_use ) { |
| 163 | return $this->get_next_batch_to_process_hpos( $size ); |
| 164 | } elseif ( $this->cpt_in_use ) { |
| 165 | return $this->get_next_batch_to_process_cpt( $size ); |
| 166 | } else { |
| 167 | $this->throw_doing_it_wrong( StringUtil::class_name_without_namespace( __CLASS__ ) . '::' . __FUNCTION__ ); |
| 168 | return array(); |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | /** |
| 173 | * Get the next batch of items to process, HPOS version. |
| 174 | * |
| 175 | * @param int $size Maximum size of the batch to return. |
| 176 | * @return array |
| 177 | */ |
| 178 | private function get_next_batch_to_process_hpos( int $size ): array { |
| 179 | global $wpdb; |
| 180 | |
| 181 | return $wpdb->get_results( |
| 182 | $wpdb->prepare( |
| 183 | "SELECT order_id, meta_value |
| 184 | FROM {$wpdb->prefix}wc_orders_meta |
| 185 | WHERE meta_key = %s |
| 186 | ORDER BY order_id |
| 187 | LIMIT %d", |
| 188 | '_debug_log_source_pending_deletion', |
| 189 | $size |
| 190 | ), |
| 191 | ARRAY_A |
| 192 | ); |
| 193 | } |
| 194 | |
| 195 | /** |
| 196 | * Get the next batch of items to process, CPT datastore version. |
| 197 | * |
| 198 | * @param int $size Maximum size of the batch to return. |
| 199 | * @return array |
| 200 | */ |
| 201 | private function get_next_batch_to_process_cpt( int $size ): array { |
| 202 | global $wpdb; |
| 203 | |
| 204 | return $wpdb->get_results( |
| 205 | $wpdb->prepare( |
| 206 | "SELECT p.ID as order_id, pm.meta_value |
| 207 | FROM {$wpdb->postmeta} pm |
| 208 | INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID |
| 209 | WHERE pm.meta_key = %s |
| 210 | AND p.post_type = 'shop_order' |
| 211 | ORDER BY p.ID |
| 212 | LIMIT %d", |
| 213 | '_debug_log_source_pending_deletion', |
| 214 | $size |
| 215 | ), |
| 216 | ARRAY_A |
| 217 | ); |
| 218 | } |
| 219 | |
| 220 | /** |
| 221 | * Process a batch of items. |
| 222 | * Items are expected to be in the format returned by get_next_batch_to_process. |
| 223 | * |
| 224 | * @param array $batch Batch of items to process. |
| 225 | * @throws \Exception Invalid input. |
| 226 | */ |
| 227 | public function process_batch( array $batch ): void { |
| 228 | if ( empty( $batch ) ) { |
| 229 | return; |
| 230 | } |
| 231 | |
| 232 | if ( ! $this->hpos_in_use && ! $this->cpt_in_use ) { |
| 233 | $this->throw_doing_it_wrong( StringUtil::class_name_without_namespace( __CLASS__ ) . '::' . __FUNCTION__ ); |
| 234 | return; |
| 235 | } |
| 236 | |
| 237 | $items = array(); |
| 238 | foreach ( $batch as $item ) { |
| 239 | if ( ! is_array( $item ) || ! isset( $item['meta_value'] ) || ! isset( $item['order_id'] ) ) { |
| 240 | throw new \Exception( "\$batch must be an array of arrays, each having a 'meta_value' key and an 'order_id' key" ); |
| 241 | } |
| 242 | $items[ $item['order_id'] ] = $item['meta_value']; |
| 243 | } |
| 244 | |
| 245 | $this->order_logs_cleanup_helper->clear_logs_and_delete_meta( $items ); |
| 246 | } |
| 247 | |
| 248 | /** |
| 249 | * Throw a "doing it wrong" error. |
| 250 | * |
| 251 | * @param string $function_name Class and function name to include in the error. |
| 252 | */ |
| 253 | private function throw_doing_it_wrong( string $function_name ) { |
| 254 | $this->legacy_proxy->call_function( |
| 255 | 'wc_doing_it_wrong', |
| 256 | $function_name, |
| 257 | "This processor shouldn't be enqueued when the orders data store in use is neither the HPOS one nor the CPT one. Just delete the order debug logs directly.", |
| 258 | '10.3.0' |
| 259 | ); |
| 260 | } |
| 261 | } |
| 262 |