← All changes
|
modules/optimization/components/bulk-optimization.php
+249
-86
1.2.0
→
1.7.7
View file →
| @@ -1,143 +1,306 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | 3 | namespace ImageOptimization\Modules\Optimization\Components; |
| 4 | 4 | |
| 5 | -use ImageOptimization\Classes\Async_Operation\Async_Operation_Hook; | |
| 5 | +use ImageOptimization\Classes\Async_Operation\{ | |
| 6 | + Async_Operation, | |
| 7 | + Async_Operation_Hook, | |
| 8 | + Async_Operation_Queue, | |
| 9 | +}; | |
| 10 | + | |
| 6 | 11 | use ImageOptimization\Classes\Image\{ |
| 7 | - Image, | |
| 8 | 12 | Image_Meta, |
| 9 | - Image_Optimization_Error_Type, | |
| 10 | - Image_Restore, | |
| 11 | 13 | Image_Status |
| 12 | 14 | }; |
| 13 | -use ImageOptimization\Classes\Logger; | |
| 14 | -use ImageOptimization\Modules\Oauth\Classes\Exceptions\Quota_Exceeded_Error; | |
| 15 | + | |
| 15 | 16 | use ImageOptimization\Modules\Optimization\{ |
| 16 | 17 | Classes\Exceptions\Bulk_Token_Expired_Error, |
| 17 | - Classes\Exceptions\Image_File_Already_Exists_Error, | |
| 18 | 18 | Classes\Optimize_Image, |
| 19 | - Classes\Bulk_Optimization_Controller, | |
| 20 | - Components\Exceptions\Bulk_Optimization_Token_Not_Found_Error, | |
| 19 | + Classes\Bulk_Optimization\Bulk_Optimization_Queue, | |
| 20 | + Classes\Bulk_Optimization\Bulk_Optimization_Queue_Status, | |
| 21 | + Classes\Bulk_Optimization\Bulk_Optimization_Queue_Type, | |
| 22 | + Classes\Bulk_Optimization\Bulk_Optimization_Token_Manager, | |
| 21 | 23 | }; |
| 22 | 24 | |
| 25 | +use ImageOptimization\Classes\Logger; | |
| 26 | +use ImageOptimization\Classes\Utils; | |
| 27 | +use ImageOptimization\Classes\Exceptions\Quota_Exceeded_Error; | |
| 23 | 28 | use Throwable; |
| 24 | 29 | |
| 30 | +// @codeCoverageIgnoreStart | |
| 25 | 31 | if ( ! defined( 'ABSPATH' ) ) { |
| 26 | 32 | exit; // Exit if accessed directly. |
| 27 | 33 | } |
| 34 | +// @codeCoverageIgnoreEnd | |
| 28 | 35 | |
| 29 | 36 | class Bulk_Optimization { |
| 30 | - const BULK_OPTIMIZATION_BASE_SLUG = 'image-optimization-bulk-optimization'; | |
| 31 | - const BULK_OPTIMIZATION_CAPABILITY = 'manage_options'; | |
| 37 | + /** @async */ | |
| 38 | + public function optimize_bulk() { | |
| 39 | + $queue = new Bulk_Optimization_Queue( Bulk_Optimization_Queue_Type::OPTIMIZATION ); | |
| 32 | 40 | |
| 33 | - public function render_app() { | |
| 34 | - ?> | |
| 35 | - <!-- The hack required to wrap WP notifications --> | |
| 36 | - <div class="wrap"> | |
| 37 | - <h1 style="display: none;" role="presentation"></h1> | |
| 38 | - </div> | |
| 41 | + if ( ! $queue->exists() ) { | |
| 42 | + Logger::debug( 'Bulk optimization queue did not found' ); | |
| 39 | 43 | |
| 40 | - <div id="image-optimization-app"></div> | |
| 41 | - <?php | |
| 42 | - } | |
| 44 | + return; | |
| 45 | + } | |
| 43 | 46 | |
| 44 | - public function register_page() { | |
| 45 | - add_media_page( | |
| 46 | - __( 'Bulk Optimization', 'image-optimization' ), | |
| 47 | - __( 'Bulk Optimization', 'image-optimization' ), | |
| 48 | - self::BULK_OPTIMIZATION_CAPABILITY, | |
| 49 | - self::BULK_OPTIMIZATION_BASE_SLUG, | |
| 50 | - [ $this, 'render_app' ], | |
| 51 | - 7 | |
| 52 | - ); | |
| 53 | - } | |
| 47 | + if ( ! $queue->has_more_images() ) { | |
| 48 | + Logger::debug( 'No more pending images, deleting queue' ); | |
| 49 | + $queue->delete(); | |
| 54 | 50 | |
| 55 | - /** @async */ | |
| 56 | - public function optimize_bulk( int $image_id, string $operation_id ) { | |
| 51 | + return; | |
| 52 | + } | |
| 53 | + | |
| 54 | + if ( $queue->should_refresh_token() ) { | |
| 55 | + Logger::debug( 'Refreshing bulk token' ); | |
| 56 | + | |
| 57 | + try { | |
| 58 | + $token_data = Bulk_Optimization_Token_Manager::obtain_token( | |
| 59 | + $queue->get_stats()[ Bulk_Optimization_Queue_Status::PENDING ], | |
| 60 | + $queue->get_max_batch_size() | |
| 61 | + ); | |
| 62 | + | |
| 63 | + $queue | |
| 64 | + ->set_bulk_token( | |
| 65 | + $token_data['token'], | |
| 66 | + time() + HOUR_IN_SECONDS, | |
| 67 | + $token_data['batch_size'] | |
| 68 | + ) | |
| 69 | + ->save(); | |
| 70 | + } catch ( Throwable $t ) { | |
| 71 | + Logger::info( 'Failed to obtain a bulk token: ' . $t->getMessage() ); | |
| 72 | + | |
| 73 | + foreach ( $queue->get_images_by_status( Bulk_Optimization_Queue_Status::PENDING ) as $image ) { | |
| 74 | + ( new Image_Meta( $image['id'] ) ) | |
| 75 | + ->set_status( Image_Status::NOT_OPTIMIZED ) | |
| 76 | + ->save(); | |
| 77 | + } | |
| 78 | + | |
| 79 | + $queue->delete(); | |
| 80 | + | |
| 81 | + return; | |
| 82 | + } | |
| 83 | + } | |
| 84 | + | |
| 85 | + $image_id = $queue->get_next_image(); | |
| 86 | + | |
| 87 | + Logger::debug( 'Processing image: ' . $image_id ); | |
| 88 | + | |
| 89 | + if ( ! $image_id ) { | |
| 90 | + Logger::debug( 'No image to process, deleting queue' ); | |
| 91 | + $queue->delete(); | |
| 92 | + | |
| 93 | + return; | |
| 94 | + } | |
| 95 | + | |
| 96 | + $queue | |
| 97 | + ->set_current_image_id( $image_id ) | |
| 98 | + ->save(); | |
| 99 | + | |
| 57 | 100 | try { |
| 58 | - $bulk_token = Bulk_Optimization_Controller::get_bulk_operation_token( $operation_id ); | |
| 59 | - | |
| 60 | 101 | $oi = new Optimize_Image( |
| 61 | 102 | $image_id, |
| 62 | 103 | 'bulk', |
| 63 | - $bulk_token | |
| 104 | + $queue->get_bulk_token() | |
| 64 | 105 | ); |
| 65 | 106 | |
| 66 | 107 | $oi->optimize(); |
| 67 | - } catch ( Quota_Exceeded_Error $qe ) { | |
| 68 | - ( new Image_Meta( $image_id ) ) | |
| 69 | - ->set_status( Image_Status::OPTIMIZATION_FAILED ) | |
| 70 | - ->set_error_type( Image_Optimization_Error_Type::QUOTA_EXCEEDED ) | |
| 108 | + | |
| 109 | + $queue | |
| 110 | + ->mark_image_completed( $image_id ) | |
| 111 | + ->increment_optimized_counter() | |
| 71 | 112 | ->save(); |
| 72 | - } catch ( Image_File_Already_Exists_Error $fe ) { | |
| 73 | - ( new Image_Meta( $image_id ) ) | |
| 74 | - ->set_status( Image_Status::OPTIMIZATION_FAILED ) | |
| 75 | - ->set_error_type( Image_Optimization_Error_Type::FILE_ALREADY_EXISTS ) | |
| 76 | - ->save(); | |
| 77 | - } catch ( Bulk_Token_Expired_Error | Bulk_Optimization_Token_Not_Found_Error $bte ) { | |
| 78 | - ( new Image_Meta( $image_id ) ) | |
| 79 | - ->set_status( Image_Status::NOT_OPTIMIZED ) | |
| 80 | - ->save(); | |
| 81 | 113 | |
| 82 | - Bulk_Optimization_Controller::reschedule_bulk_optimization(); | |
| 114 | + } catch ( Bulk_Token_Expired_Error $btee ) { | |
| 115 | + $queue->mark_image_failed( $image_id )->save(); | |
| 116 | + | |
| 117 | + Logger::info( "Token expired while optimizing image {$image_id}" ); | |
| 118 | + } catch ( Quota_Exceeded_Error $qee ) { | |
| 119 | + $queue->mark_image_failed( $image_id )->save(); | |
| 120 | + | |
| 121 | + Logger::info( "Quota exceeded while optimizing image {$image_id}" ); | |
| 122 | + } catch ( Throwable $e ) { | |
| 123 | + $queue->mark_image_failed( $image_id )->save(); | |
| 124 | + | |
| 125 | + Logger::info( "Failed to optimize image {$image_id}: " . $e->getMessage() ); | |
| 126 | + } | |
| 127 | + | |
| 128 | + $queue | |
| 129 | + ->set_current_image_id( null ) | |
| 130 | + ->save(); | |
| 131 | + | |
| 132 | + try { | |
| 133 | + if ( $queue->has_more_images() ) { | |
| 134 | + Async_Operation::create( | |
| 135 | + Async_Operation_Hook::OPTIMIZE_BULK, | |
| 136 | + [ 'operation_id' => $queue->get_operation_id() ], | |
| 137 | + Async_Operation_Queue::OPTIMIZE | |
| 138 | + ); | |
| 139 | + | |
| 140 | + Logger::debug( 'Async operation created' ); | |
| 141 | + } else { | |
| 142 | + Logger::debug( 'All images were optimized, deleting queue' ); | |
| 143 | + | |
| 144 | + $queue->delete(); | |
| 145 | + } | |
| 83 | 146 | } catch ( Throwable $t ) { |
| 84 | - Logger::log( Logger::LEVEL_ERROR, 'Optimization error. Reason: ' . $t->getMessage() ); | |
| 85 | - | |
| 86 | - ( new Image_Meta( $image_id ) ) | |
| 87 | - ->set_status( Image_Status::OPTIMIZATION_FAILED ) | |
| 88 | - ->set_error_type( Image_Optimization_Error_Type::GENERIC ) | |
| 89 | - ->save(); | |
| 147 | + Logger::error( 'Failed to create next async operation or delete queue: ' . $t->getMessage() ); | |
| 90 | 148 | } |
| 91 | 149 | } |
| 92 | 150 | |
| 93 | 151 | /** @async */ |
| 94 | - public function reoptimize_bulk( int $image_id, string $operation_id ) { | |
| 95 | - try { | |
| 96 | - $image = new Image( $image_id ); | |
| 152 | + public function reoptimize_bulk() { | |
| 153 | + $queue = new Bulk_Optimization_Queue( Bulk_Optimization_Queue_Type::REOPTIMIZATION ); | |
| 97 | 154 | |
| 98 | - if ( $image->can_be_restored() ) { | |
| 99 | - Image_Restore::restore( $image_id, true ); | |
| 155 | + if ( ! $queue->exists() ) { | |
| 156 | + return; | |
| 157 | + } | |
| 158 | + | |
| 159 | + if ( ! $queue->has_more_images() ) { | |
| 160 | + $queue->delete(); | |
| 161 | + | |
| 162 | + return; | |
| 163 | + } | |
| 164 | + | |
| 165 | + if ( $queue->should_refresh_token() ) { | |
| 166 | + try { | |
| 167 | + $token_data = Bulk_Optimization_Token_Manager::obtain_token( | |
| 168 | + $queue->get_stats()[ Bulk_Optimization_Queue_Status::PENDING ], | |
| 169 | + $queue->get_max_batch_size() | |
| 170 | + ); | |
| 171 | + | |
| 172 | + $queue | |
| 173 | + ->set_bulk_token( | |
| 174 | + $token_data['token'], | |
| 175 | + time() + HOUR_IN_SECONDS, | |
| 176 | + $token_data['batch_size'] | |
| 177 | + ) | |
| 178 | + ->save(); | |
| 179 | + } catch ( Throwable $t ) { | |
| 180 | + Logger::info( 'Failed to obtain bulk token: ' . $t->getMessage() ); | |
| 181 | + | |
| 182 | + foreach ( $queue->get_images_by_status( Bulk_Optimization_Queue_Status::PENDING ) as $image ) { | |
| 183 | + ( new Image_Meta( $image['id'] ) ) | |
| 184 | + ->set_status( Image_Status::OPTIMIZATION_FAILED ) | |
| 185 | + ->save(); | |
| 186 | + } | |
| 187 | + | |
| 188 | + $queue->delete(); | |
| 189 | + | |
| 190 | + return; | |
| 100 | 191 | } |
| 192 | + } | |
| 101 | 193 | |
| 102 | - $bulk_token = Bulk_Optimization_Controller::get_bulk_operation_token( $operation_id ); | |
| 194 | + $image_id = $queue->get_next_image(); | |
| 103 | 195 | |
| 196 | + if ( ! $image_id ) { | |
| 197 | + $queue | |
| 198 | + ->set_status( Bulk_Optimization_Queue_Status::COMPLETED ) | |
| 199 | + ->save(); | |
| 200 | + | |
| 201 | + return; | |
| 202 | + } | |
| 203 | + | |
| 204 | + $queue | |
| 205 | + ->set_current_image_id( $image_id ) | |
| 206 | + ->save(); | |
| 207 | + | |
| 208 | + try { | |
| 104 | 209 | $oi = new Optimize_Image( |
| 105 | 210 | $image_id, |
| 106 | - 'bulk', | |
| 107 | - $bulk_token | |
| 211 | + 'bulk-reoptimize', | |
| 212 | + $queue->get_bulk_token(), | |
| 213 | + true | |
| 108 | 214 | ); |
| 109 | 215 | |
| 110 | 216 | $oi->optimize(); |
| 111 | - } catch ( Quota_Exceeded_Error $qe ) { | |
| 112 | - ( new Image_Meta( $image_id ) ) | |
| 113 | - ->set_status( Image_Status::REOPTIMIZING_FAILED ) | |
| 114 | - ->set_error_type( Image_Optimization_Error_Type::QUOTA_EXCEEDED ) | |
| 217 | + | |
| 218 | + $queue | |
| 219 | + ->mark_image_completed( $image_id ) | |
| 220 | + ->increment_optimized_counter() | |
| 115 | 221 | ->save(); |
| 116 | - } catch ( Image_File_Already_Exists_Error $fe ) { | |
| 117 | - ( new Image_Meta( $image_id ) ) | |
| 118 | - ->set_status( Image_Status::REOPTIMIZING_FAILED ) | |
| 119 | - ->set_error_type( Image_Optimization_Error_Type::FILE_ALREADY_EXISTS ) | |
| 120 | - ->save(); | |
| 121 | - } catch ( Bulk_Token_Expired_Error | Bulk_Optimization_Token_Not_Found_Error $bte ) { | |
| 122 | - ( new Image_Meta( $image_id ) ) | |
| 123 | - ->set_status( Image_Status::NOT_OPTIMIZED ) | |
| 124 | - ->save(); | |
| 125 | 222 | |
| 126 | - Bulk_Optimization_Controller::reschedule_bulk_reoptimization(); | |
| 223 | + } catch ( Bulk_Token_Expired_Error $btee ) { | |
| 224 | + $queue->mark_image_failed( $image_id )->save(); | |
| 225 | + | |
| 226 | + Logger::info( "Token expired while reoptimizing image {$image_id}" ); | |
| 227 | + } catch ( Quota_Exceeded_Error $qee ) { | |
| 228 | + $queue->mark_image_failed( $image_id )->save(); | |
| 229 | + | |
| 230 | + foreach ( $queue->get_images_by_status( Bulk_Optimization_Queue_Status::PENDING ) as $image ) { | |
| 231 | + ( new Image_Meta( $image['id'] ) ) | |
| 232 | + ->set_status( Image_Status::OPTIMIZATION_FAILED ) | |
| 233 | + ->save(); | |
| 234 | + } | |
| 235 | + | |
| 236 | + $queue->delete(); | |
| 237 | + | |
| 238 | + Logger::info( "Quota exceeded while reoptimizing image {$image_id}" ); | |
| 239 | + | |
| 240 | + return; | |
| 241 | + } catch ( Throwable $e ) { | |
| 242 | + $queue->mark_image_failed( $image_id )->save(); | |
| 243 | + | |
| 244 | + Logger::info( "Failed to reoptimize image {$image_id}: " . $e->getMessage() ); | |
| 245 | + } | |
| 246 | + | |
| 247 | + $queue | |
| 248 | + ->set_current_image_id( null ) | |
| 249 | + ->save(); | |
| 250 | + | |
| 251 | + try { | |
| 252 | + if ( $queue->has_more_images() ) { | |
| 253 | + Async_Operation::create( | |
| 254 | + Async_Operation_Hook::REOPTIMIZE_BULK, | |
| 255 | + [ 'operation_id' => $queue->get_operation_id() ], | |
| 256 | + Async_Operation_Queue::OPTIMIZE | |
| 257 | + ); | |
| 258 | + } else { | |
| 259 | + $queue->delete(); | |
| 260 | + } | |
| 127 | 261 | } catch ( Throwable $t ) { |
| 128 | - Logger::log( Logger::LEVEL_ERROR, 'Reoptimization error. Reason: ' . $t->getMessage() ); | |
| 262 | + Logger::error( 'Failed to create next async operation or delete queue: ' . $t->getMessage() ); | |
| 263 | + } | |
| 264 | + } | |
| 129 | 265 | |
| 130 | - ( new Image_Meta( $image_id ) ) | |
| 131 | - ->set_status( Image_Status::REOPTIMIZING_FAILED ) | |
| 132 | - ->set_error_type( Image_Optimization_Error_Type::GENERIC ) | |
| 133 | - ->save(); | |
| 134 | - } | |
| 266 | + /** | |
| 267 | + * Renders the bulk optimization notice | |
| 268 | + * | |
| 269 | + * @return void | |
| 270 | + */ | |
| 271 | + public function render_bulk_optimization_notice() { | |
| 272 | + $queue = new Bulk_Optimization_Queue( Bulk_Optimization_Queue_Type::OPTIMIZATION ); | |
| 273 | + $is_in_progress = $queue->exists(); | |
| 274 | + ?> | |
| 275 | + <div class="notice notice-info notice image-optimizer__notice image-optimizer__notice--info image-optimizer__notice--bulk-tip" | |
| 276 | + style="display: <?php echo $is_in_progress ? 'block' : 'none'; ?>"> | |
| 277 | + <p> | |
| 278 | + <b> | |
| 279 | + <?php esc_html_e( | |
| 280 | + 'Heads up!', | |
| 281 | + 'image-optimization' | |
| 282 | + ); ?> | |
| 283 | + </b> | |
| 284 | + | |
| 285 | + <span> | |
| 286 | + <?php esc_html_e( | |
| 287 | + 'Bulk optimizing may take a lot of processing and server time, depending on the number of images. Your site will still work smoothly until the processing is all done, without any downtime.', | |
| 288 | + 'image-optimization' | |
| 289 | + ); ?> | |
| 290 | + </span> | |
| 291 | + </p> | |
| 292 | + </div> | |
| 293 | + <?php | |
| 135 | 294 | } |
| 136 | 295 | |
| 137 | 296 | public function __construct() { |
| 138 | - add_action( 'admin_menu', [ $this, 'register_page' ] ); | |
| 297 | + add_action( Async_Operation_Hook::OPTIMIZE_BULK, [ $this, 'optimize_bulk' ] ); | |
| 298 | + add_action( Async_Operation_Hook::REOPTIMIZE_BULK, [ $this, 'reoptimize_bulk' ] ); | |
| 139 | 299 | |
| 140 | - add_action( Async_Operation_Hook::OPTIMIZE_BULK, [ $this, 'optimize_bulk' ], 10, 2 ); | |
| 141 | - add_action( Async_Operation_Hook::REOPTIMIZE_BULK, [ $this, 'reoptimize_bulk' ], 10, 2 ); | |
| 300 | + add_action( 'current_screen', function () { | |
| 301 | + if ( Utils::is_bulk_optimization_page() ) { | |
| 302 | + add_filter( 'admin_footer_text', [ $this, 'render_bulk_optimization_notice' ] ); | |
| 303 | + } | |
| 304 | + } ); | |
| 142 | 305 | } |
| 143 | 306 | } |