PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 3.3.2
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v3.3.2
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / includes / class-updraft-smush-manager-commands.php

class-updraft-smush-manager-commands.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 3.3.2, at includes/class-updraft-smush-manager-commands.php

582 lines 20.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * A Smush Task manager class
4 */
5
6 if (!defined('ABSPATH')) die('Access denied.');
7
8 if (!class_exists('Updraft_Task_Manager_Commands_1_0')) require_once(WPO_PLUGIN_MAIN_PATH . 'vendor/team-updraft/common-libs/src/updraft-tasks/class-updraft-task-manager-commands.php');
9
10 if (!class_exists('Updraft_Smush_Manager_Commands')) :
11
12 class Updraft_Smush_Manager_Commands extends Updraft_Task_Manager_Commands_1_0 {
13
14 /**
15 * The commands constructor
16 *
17 * @param mixed $task_manager - A task manager instance
18 */
19 public function __construct($task_manager) {
20 parent::__construct($task_manager);
21 }
22
23 /**
24 * Returns a list of commands available for smush related operations
25 */
26 public static function get_allowed_ajax_commands() {
27
28 $commands = parent::get_allowed_ajax_commands();
29
30 $smush_commands = array(
31 'compress_single_image',
32 'restore_single_image',
33 'process_bulk_smush',
34 'update_smush_options',
35 'get_ui_update',
36 'process_pending_images',
37 'clear_pending_images',
38 'clear_smush_stats',
39 'check_server_status',
40 'get_smush_logs',
41 'mark_as_compressed',
42 'mark_all_as_uncompressed',
43 'clean_all_backup_images',
44 'reset_webp_serving_method',
45 'convert_to_webp_format',
46 'update_webp_options',
47 );
48
49 return array_merge($commands, $smush_commands);
50 }
51
52 /**
53 * Process the compression of a single image
54 *
55 * @param mixed $data - sent in via AJAX
56 * @return WP_Error|array - information about the operation or WP_Error object on failure
57 */
58 public function compress_single_image($data) {
59
60 $options = !empty($data['smush_options']) ? $data['smush_options'] : $this->task_manager->get_smush_options();
61 $image = isset($data['selected_image']) ? filter_var($data['selected_image']['attachment_id'], FILTER_SANITIZE_NUMBER_INT) : false;
62 $blog = isset($data['selected_image']) ? filter_var($data['selected_image']['blog_id'], FILTER_SANITIZE_NUMBER_INT) : false;
63
64 // A subsite administrator can only compress their own image. If the blog ID isn't theirs, return an error.
65 if ($blog && is_multisite() && get_current_blog_id() != $blog && !current_user_can('manage_network_options')) {
66 return new WP_Error('compression_not_permitted', __('The blog ID provided does not match the current blog.', 'wp-optimize'));
67 }
68
69 $server = sanitize_text_field($options['compression_server']);
70
71 $lossy = filter_var($options['lossy_compression'], FILTER_VALIDATE_BOOLEAN) ? true : false;
72 $backup = filter_var($options['back_up_original'], FILTER_VALIDATE_BOOLEAN) ? true : false;
73 $exif = filter_var($options['preserve_exif'], FILTER_VALIDATE_BOOLEAN) ? true : false;
74 $quality = filter_var($options['image_quality'], FILTER_SANITIZE_NUMBER_INT);
75
76 $options = array(
77 'attachment_id' => $image,
78 'blog_id' => $blog,
79 'image_quality' => $quality,
80 'keep_original' => $backup,
81 'lossy_compression' => $lossy,
82 'preserve_exif' => $exif
83 );
84
85 if (filesize(get_attached_file($image)) > 5242880) {
86 $options['request_timeout'] = 180;
87 }
88
89 $success = $this->task_manager->compress_single_image($image, $options, $server);
90
91 if (!$success) {
92 return new WP_Error('compress_failed', get_post_meta($image, 'smush-info', true));
93 }
94
95 $response = array();
96 $response['status'] = true;
97 $response['operation'] = 'compress';
98 $response['options'] = $options;
99 $response['server'] = $server;
100 $response['success'] = $success;
101 $response['restore_possible'] = $backup;
102 $response['summary'] = get_post_meta($image, 'smush-info', true);
103
104 $smush_stats = get_post_meta($image, 'smush-stats', true);
105 if (isset($smush_stats['sizes-info'])) {
106 $response['sizes-info'] = WP_Optimize()->include_template('images/smush-details.php', true, array('sizes_info' => $smush_stats['sizes-info']));
107 }
108
109 return $response;
110 }
111
112 /**
113 * Restores a single image, if backup is available
114 *
115 * @param mixed $data - Sent in via AJAX
116 * @return WP_Error|array - information about the operation or a WP_Error object on failure
117 */
118 public function restore_single_image($data) {
119
120 $blog_id = isset($data['blog_id']) ? $data['blog_id'] : false;
121 $image_id = isset($data['selected_image']) ? $data['selected_image'] : false;
122
123 $success = $this->task_manager->restore_single_image($image_id, $blog_id);
124
125 if (is_wp_error($success)) {
126 return $success;
127 }
128
129 $response = array();
130 $response['status'] = true;
131 $response['operation'] = 'restore';
132 $response['blog_id'] = $blog_id;
133 $response['image'] = $image_id;
134 $response['success'] = $success;
135 $response['summary'] = __('The image was restored successfully', 'wp-optimize');
136
137 return $response;
138 }
139
140 /**
141 * Process the compression of multiple images
142 *
143 * @param mixed $data - Sent in via AJAX
144 */
145 public function process_bulk_smush($data = array()) {
146 $images = isset($data['selected_images']) ? $data['selected_images'] : array();
147
148 $ui_update = $this->get_ui_update($images);
149 WP_Optimize()->close_browser_connection(json_encode($ui_update));
150 $this->task_manager->process_bulk_smush($images);
151 // Since we already sent back data and closed the browser connection, we must not return (that would result in further sending back of JSON).
152 die();
153 }
154
155 /**
156 * Returns useful information for the UI and closes the connection
157 *
158 * @param mixed $data - Sent in via AJAX
159 *
160 * @return mixed - Information for the UI
161 */
162 public function get_ui_update($data) {
163 $ui_update = array();
164 $ui_update['status'] = true;
165 $ui_update['is_multisite'] = is_multisite() ? 1 : 0;
166 $pending_tasks = $this->task_manager->get_pending_tasks();
167
168 $ui_update['pending_tasks'] = is_array($pending_tasks) ? count($this->task_manager->get_pending_tasks()) : 0;
169 $ui_update['unsmushed_images'] = $this->task_manager->get_uncompressed_images();
170 $ui_update['admin_urls'] = $this->task_manager->get_admin_urls();
171 $ui_update['completed_task_count'] = $this->task_manager->options->get_option('completed_task_count', 0);
172 $ui_update['bytes_saved'] = WP_Optimize()->format_size($this->task_manager->options->get_option('total_bytes_saved', 0));
173 $ui_update['percent_saved'] = number_format($this->task_manager->options->get_option('total_percent_saved', 1), 2).'%';
174 $ui_update['failed_task_count'] = $this->task_manager->get_failed_task_count();
175
176 $ui_update['summary'] = sprintf(__('Since your compression statistics were last reset, a total of %d image(s) were compressed on this site.', 'wp-optimize').' '.__('This saved approximately %s of space at an average of %02d percent per image.', 'wp-optimize'), $ui_update['completed_task_count'], $ui_update['bytes_saved'], $ui_update['percent_saved']);
177 $ui_update['failed'] = sprintf(__("%d image(s) could not be compressed.", 'wp-optimize'), $ui_update['failed_task_count']) . ' ' . __('Please see the logs for more information, or try again later.', 'wp-optimize');
178 $ui_update['pending'] = sprintf(__("%d image(s) images were selected for compressing previously, but were not all processed.", 'wp-optimize'), $ui_update['pending_tasks']) . ' ' . __('You can either complete them now or cancel and retry later.', 'wp-optimize');
179 $ui_update['smush_complete'] = $this->task_manager->is_queue_processed();
180
181 if (isset($data['image_list'])) {
182 $images = $data['image_list'];
183 $stats = $this->task_manager->get_session_stats($images);
184 $ui_update['session_stats'] = "";
185
186 if (!empty($stats['success'])) {
187 $ui_update['session_stats'] .= sprintf(__("A total of %d image(s) were successfully compressed in this iteration.", 'wp-optimize'), $stats['success']);
188 }
189
190 if (!empty($stats['fail'])) {
191 $ui_update['session_stats'] .= sprintf(__("%d selected image(s) could not be compressed.", 'wp-optimize'), $stats['fail']) . ' ' . __('Please see the logs for more information, you may try again later.', 'wp-optimize');
192 }
193 }
194
195 return $ui_update;
196
197 }
198
199 /**
200 * Updates webp related options
201 *
202 * @param mixed $data - Sent in via AJAX
203 * @return WP_Error|array - information about the operation or WP_Error object on failure
204 */
205 public function update_webp_options($data) {
206 $webp_instance = WP_Optimize()->get_webp_instance();
207 $options = array();
208 $options['webp_conversion'] = filter_var($data['webp_conversion'], FILTER_VALIDATE_BOOLEAN);
209
210 // Only run checks when trying to enable WebP
211 if ($options['webp_conversion']) {
212 //Run checks if we are enabling webp conversion
213 if (!WP_Optimize_WebP::is_shell_functions_available()) {
214 $webp_instance->disable_webp_conversion();
215 return new WP_Error('update_failed_no_shell_functions', __('Required WebP shell functions are not available on server.', 'wp-optimize'));
216 }
217
218 // Run conversion test if not already done and set necessary option value
219 if ($webp_instance->should_run_webp_conversion_test()) {
220 $converter_status = WPO_WebP_Test_Run::get_converter_status();
221
222 if (!$webp_instance->is_webp_conversion_successful()) {
223 $webp_instance->disable_webp_conversion();
224 return new WP_Error('update_failed_no_working_webp_converter', __('No working Webp converter was found on server.', 'wp-optimize'));
225 }
226
227 $options['webp_conversion_test'] = true;
228 $options['webp_converters'] = $converter_status['working_converters'];
229 }
230
231 // Run serving methods tests and set necessary option values
232 // Not possible to test alter html since test is browser based
233 $webp_instance->save_htaccess_rules();
234 if (!$webp_instance->is_webp_redirection_possible()) {
235 $webp_instance->empty_htaccess_file();
236 $options['redirection_possible'] = 'false';
237 } else {
238 $options['redirection_possible'] = 'true';
239 }
240 }
241
242 $success = $this->task_manager->update_smush_options($options);
243
244 if (!$success) {
245 $webp_instance->disable_webp_conversion();
246 return new WP_Error('update_failed', __('Webp options could not be updated.', 'wp-optimize'));
247 }
248
249 // Setup daily CRON only when enabling WebP and Delete daily CRON when disabling WebP
250 if ($options['webp_conversion']) {
251 $webp_instance->init_webp_cron_scheduler();
252 } else {
253 $webp_instance->remove_webp_cron_schedules();
254 $webp_instance->empty_htaccess_file();
255 }
256
257 do_action('wpo_save_images_settings');
258
259 $response = array();
260 $response['status'] = true;
261 $response['saved'] = $success;
262 $response['summary'] = __('Webp options updated successfully.', 'wp-optimize');
263
264 return $response;
265 }
266
267 /**
268 * Updates smush related options
269 *
270 * @param mixed $data - Sent in via AJAX
271 * @return WP_Error|array - information about the operation or WP_Error object on failure
272 */
273 public function update_smush_options($data) {
274 $options = array();
275 $options['compression_server'] = sanitize_text_field($data['compression_server']);
276 $options['lossy_compression'] = filter_var($data['lossy_compression'], FILTER_VALIDATE_BOOLEAN) ? true : false;
277 $options['back_up_original'] = filter_var($data['back_up_original'], FILTER_VALIDATE_BOOLEAN) ? true : false;
278 $options['back_up_delete_after'] = filter_var($data['back_up_delete_after'], FILTER_VALIDATE_BOOLEAN) ? true : false;
279 $options['back_up_delete_after_days'] = filter_var($data['back_up_delete_after_days'], FILTER_SANITIZE_NUMBER_INT);
280 $options['preserve_exif'] = filter_var($data['preserve_exif'], FILTER_VALIDATE_BOOLEAN) ? true : false;
281 $options['autosmush'] = filter_var($data['autosmush'], FILTER_VALIDATE_BOOLEAN) ? true : false;
282 $options['image_quality'] = filter_var($data['image_quality'], FILTER_SANITIZE_NUMBER_INT);
283 $options['show_smush_metabox'] = filter_var($data['show_smush_metabox'], FILTER_VALIDATE_BOOLEAN) ? 'show' : 'hide';
284
285 $success = $this->task_manager->update_smush_options($options);
286
287 if (!$success) {
288 return new WP_Error('update_failed', __('Smush options could not be updated', 'wp-optimize'));
289 }
290
291 do_action('wpo_save_images_settings');
292
293 $response = array();
294 $response['status'] = true;
295 $response['saved'] = $success;
296 $response['summary'] = __('Options updated successfully', 'wp-optimize');
297
298 return $response;
299 }
300
301 /**
302 * Clears any smush related stats
303 *
304 * @return WP_Error|array - information about the operation or WP_Error object on failure
305 */
306 public function clear_smush_stats() {
307
308 $success = $this->task_manager->clear_smush_stats();
309
310 if (!$success) {
311 return new WP_Error('update_failed', __('Stats could not be cleared', 'wp-optimize'));
312 }
313
314 $response = array();
315 $response['status'] = true;
316 $response['summary'] = __('Stats cleared successfully', 'wp-optimize');
317
318 return $response;
319 }
320
321 /**
322 * Checks if the selected server is online
323 *
324 * @param mixed $data - Sent in via AJAX
325 */
326 public function check_server_status($data) {
327 $server = sanitize_text_field($data['server']);
328 $response = array();
329 $response['status'] = true;
330 $response['online'] = $this->task_manager->check_server_online($server);
331
332 if (!$response['online']) {
333 $response['error'] = get_option($this->task_manager->get_associated_task($server));
334 }
335
336 return $response;
337 }
338
339 /**
340 * Completes any pending tasks
341 */
342 public function process_pending_images() {
343 $this->process_bulk_smush();
344 }
345
346 /**
347 * Deletes and removes any pending tasks from queue
348 *
349 * @return WP_Error|array - information about the operation or WP_Error object on failure
350 */
351 public function clear_pending_images() {
352
353 $success = $this->task_manager->clear_pending_images();
354
355 if (!$success) {
356 return new WP_Error('error_deleting_tasks', __('Pending tasks could not be cleared', 'wp-optimize'));
357 }
358
359 $response = array();
360 $response['status'] = true;
361 $response['summary'] = __('Pending tasks cleared successfully', 'wp-optimize');
362
363 return $response;
364 }
365
366 /**
367 * Mark selected images as already compressed.
368 *
369 * @param array $data
370 * @return array
371 */
372 public function mark_as_compressed($data) {
373 $response = array();
374 $selected_images = array();
375
376 $unmark = isset($data['unmark']) && $data['unmark'];
377
378 foreach ($data['selected_images'] as $image) {
379 if (!array_key_exists($image['blog_id'], $selected_images)) $selected_images[$image['blog_id']] = array();
380
381 $selected_images[$image['blog_id']][] = $image['attachment_id'];
382 }
383
384 $info = __('This image is marked as already compressed by another tool.', 'wp-optimize');
385
386 foreach (array_keys($selected_images) as $blog_id) {
387 if (is_multisite()) switch_to_blog($blog_id);
388
389 foreach ($selected_images[$blog_id] as $attachment_id) {
390 if ($unmark) {
391 delete_post_meta($attachment_id, 'smush-complete');
392 delete_post_meta($attachment_id, 'smush-marked');
393 delete_post_meta($attachment_id, 'smush-info');
394 } else {
395 update_post_meta($attachment_id, 'smush-complete', true);
396 update_post_meta($attachment_id, 'smush-marked', true);
397 update_post_meta($attachment_id, 'smush-info', $info);
398 }
399 }
400
401 if (is_multisite()) restore_current_blog();
402 }
403
404 $response['status'] = true;
405
406 if ($unmark) {
407 $response['summary'] = _n('The selected image was successfully marked as uncompressed', 'The selected images were successfully marked as uncompressed', count($data['selected_images']), 'wp-optimize');
408 } else {
409 $response['summary'] = _n('The selected image was successfully marked as compressed', 'The selected images were successfully marked as compressed', count($data['selected_images']), 'wp-optimize');
410 }
411
412 $response['info'] = $info;
413
414 return $response;
415 }
416
417 /**
418 * Mark all images as uncompressed and if posted restore_backup argument
419 * then try to restore images form backup.
420 *
421 * @param array $data
422 * @return array
423 */
424 public function mark_all_as_uncompressed($data) {
425
426 $restore_backup = isset($data['restore_backup']) && $data['restore_backup'];
427 $images_per_request = apply_filters('mark_all_as_uncompressed_images_per_request', 100);
428 $delete_only_backups_meta = isset($data['delete_only_backups_meta']) && $data['delete_only_backups_meta'];
429
430 if (is_multisite()) {
431 // option where we store last completed blog id
432 $option_name = 'mark_as_uncompressed_last_blog_id';
433 // set default value for response
434 $response = array(
435 'completed' => true,
436 'message' => __('All the compressed images were successfully restored.', 'wp-optimize'),
437 );
438
439 // get all blogs ids
440 $blogs = WP_Optimize()->get_sites();
441 $blogs_ids = wp_list_pluck($blogs, 'blog_id');
442 sort($blogs_ids);
443
444 // select the blog for processing
445 $last_completed_blog_id = $this->task_manager->options->get_option($option_name, false);
446 $index = $last_completed_blog_id ? array_search($last_completed_blog_id, $blogs_ids) + 1 : 0;
447
448 if ($index < count($blogs_ids)) {
449 $blog_id = $blogs_ids[$index];
450 $response = $this->task_manager->bulk_restore_compressed_images($restore_backup, $blog_id, $images_per_request, $delete_only_backups_meta);
451
452 // if we get completed the current blog then update last completed blog option value
453 // and if we have other blogs for processing then set complete to false as we have not
454 // processed all blogs
455 if ($response['completed']) {
456 if ($index + 1 < count($blogs_ids)) {
457 $response['completed'] = false;
458 } else {
459 if ($delete_only_backups_meta) {
460 $response['message'] = __('All the compressed images were successfully restored.', 'wp-optimize');
461 } else {
462 $response['message'] = __('All the compressed images were successfully marked as uncompressed.', 'wp-optimize');
463 }
464 }
465 $this->task_manager->options->update_option($option_name, $blog_id);
466 }
467 }
468
469 // if we get an error or completed the work then delete option with last completed blog id.
470 if ($response['completed'] || isset($response['error'])) {
471 $this->task_manager->options->delete_option($option_name);
472 }
473 } else {
474 $response = $this->task_manager->bulk_restore_compressed_images($restore_backup, 0, $images_per_request, $delete_only_backups_meta);
475 }
476
477 return $response;
478 }
479
480 /**
481 * Returns the log file
482 *
483 * @return WP_Error|file - logfile or WP_Error object on failure
484 */
485 public function get_smush_logs() {
486
487 $logfile = $this->task_manager->get_logfile_path();
488
489 if (!file_exists($logfile)) {
490 $this->task_manager->write_log_header();
491 }
492
493 if (is_file($logfile)) {
494 header('Content-Description: File Transfer');
495 header('Content-Type: application/octet-stream');
496 header('Content-Disposition: attachment; filename="'.basename($logfile).'"');
497 header('Expires: 0');
498 header('Cache-Control: must-revalidate');
499 header('Pragma: public');
500 header('Content-Length: ' . filesize($logfile));
501 readfile($logfile);
502 exit;
503 } else {
504 return new WP_Error('log_file_error', __('Log file does not exist or could not be read', 'wp-optimize'));
505 }
506 }
507
508 /**
509 * Clean all backup images command.
510 *
511 * @return array
512 */
513 public function clean_all_backup_images() {
514 $upload_dir = wp_upload_dir(null, false);
515 $base_dir = $upload_dir['basedir'];
516
517 $this->task_manager->clear_backup_images_directory($base_dir, 0);
518
519 return array(
520 'status' => true,
521 );
522 }
523
524 /**
525 * Resets webp serving method
526 *
527 * @return array|WP_Error
528 */
529 public function reset_webp_serving_method() {
530 $webp_instance = WP_Optimize()->get_webp_instance();
531 //Run checks before calling reset_webp_serving_method
532 if (!WP_Optimize_WebP::is_shell_functions_available()) {
533 $webp_instance->disable_webp_conversion();
534 return new WP_Error('reset_failed_no_shell_functions', __('Required WebP shell functions are not available on server', 'wp-optimize'));
535 } elseif (!$webp_instance->is_webp_conversion_enabled()) {
536 $webp_instance->disable_webp_conversion();
537 return new WP_Error('reset_failed_webp_conversion_disabled', __('The WebP serving method cannot be reset because WebP conversion is currently disabled', 'wp-optimize'));
538 }
539
540 $webp_instance->reset_webp_serving_method();
541 return array(
542 'success' => true,
543 );
544 }
545
546 /**
547 * Convert the image to webp format
548 *
549 * @param array $data
550 * @return array
551 */
552 public function convert_to_webp_format($data) {
553 $attachment_id = isset($data['attachment_id']) ? $data['attachment_id'] : 0;
554 if (0 === $attachment_id) return $this->image_not_found_response();
555
556 $images = WPO_Image_Utils::get_attachment_files($attachment_id);
557 if (empty($images)) return $this->image_not_found_response();
558
559 $images['original'] = get_attached_file($attachment_id);
560 foreach ($images as $image) {
561 WPO_WebP_Utils::do_webp_conversion($image);
562 }
563
564 return array(
565 'success' => __('Image is converted to WebP format.', 'wp-optimize'),
566 );
567 }
568
569 /**
570 * Returns image not found response
571 *
572 * @return array
573 */
574 private function image_not_found_response() {
575 return array(
576 'error' => __('Image not found', 'wp-optimize'),
577 );
578 }
579 }
580
581 endif;
582