PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 4.5.2
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v4.5.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.php

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

1,749 lines 58.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Extends the generic task manager to manage smush related queues
4 */
5
6 if (!defined('ABSPATH')) die('Access denied.');
7
8 if (!class_exists('Updraft_Task_Manager_1_4')) require_once(WPO_PLUGIN_MAIN_PATH . 'vendor/team-updraft/common-libs/src/updraft-tasks/class-updraft-task-manager.php');
9
10 if (!class_exists('Updraft_Smush_Manager')) :
11
12 class Updraft_Smush_Manager extends Updraft_Task_Manager_1_4 {
13
14 static protected $_instance = null;
15
16 /**
17 * Options used for smush jobs
18 *
19 * @var array
20 */
21 public $options;
22
23 /**
24 * The service provider to use
25 *
26 * @var string
27 */
28 public $webservice;
29
30 /**
31 * The logger for this instance
32 *
33 * @var mixed
34 */
35 public $logger;
36
37 /**
38 * Require task-level locking
39 *
40 * @var integer
41 */
42 protected $use_per_task_lock = 60;
43
44 /**
45 * The Task Manager constructor
46 */
47 public function __construct() {
48 parent::__construct();
49
50
51 $this->commands = new Updraft_Smush_Manager_Commands($this);
52 $this->options = WP_Optimize()->get_options();
53
54 // we set default options when compression server is false - it means that options was not saved before
55 if (!$this->options->get_option('compression_server')) {
56 $this->set_default_options();
57 }
58
59 $this->webservice = $this->options->get_option('compression_server', 'resmushit');
60
61 // Ensure the saved service is valid
62 if (!in_array($this->webservice, $this->get_allowed_services())) {
63 $this->webservice = $this->get_default_webservice();
64 }
65 $this->logger = new Updraft_File_Logger($this->get_logfile_path());
66 $this->add_logger($this->logger);
67
68 add_action('wp_ajax_updraft_smush_ajax', array($this, 'updraft_smush_ajax'));
69 add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'), 9);
70 add_action('elementor/editor/before_enqueue_scripts', array($this, 'admin_enqueue_scripts'));
71 add_action('add_attachment', array($this, 'autosmush_create_task'));
72 add_action('ud_task_initialised', array($this, 'set_task_logger'));
73 add_action('ud_task_started', array($this, 'set_task_logger'));
74 add_action('ud_task_completed', array($this, 'record_stats'));
75 add_action('ud_task_failed', array($this, 'record_stats'));
76 add_action('prune_smush_logs', array($this, 'prune_smush_logs'));
77 add_action('process_smush_tasks', array($this, 'process_smush_tasks'));
78 if ('show' === $this->options->get_option('show_smush_metabox', 'show')) {
79 add_action('add_meta_boxes_attachment', array($this, 'add_smush_metabox'), 10, 2);
80 }
81 add_action('delete_attachment', array($this, 'unscheduled_original_file_deletion'));
82
83 add_filter('manage_media_columns', array($this, 'manage_media_columns'));
84 add_action('manage_media_custom_column', array($this, 'manage_media_custom_column'), 10, 2);
85
86 // clean backup images cron action.
87 add_action('wpo_smush_clear_backup_images', array($this, 'clear_backup_images'));
88
89 // add filter for already compressed images by EWWW Image Optimizer.
90 add_filter('wpo_get_uncompressed_images_args', array($this, 'ewww_image_optimizer_compressed_images_args'));
91
92 // schedule or unschedule clear backup images cron, if needed
93 $scheduled = wp_next_scheduled('wpo_smush_clear_backup_images');
94 if ($this->options->get_option('back_up_delete_after', true)) {
95 if (!$scheduled) {
96 wp_schedule_event(time(), 'daily', 'wpo_smush_clear_backup_images');
97 }
98 } else {
99 if ($scheduled) {
100 wp_unschedule_event($scheduled, 'wpo_smush_clear_backup_images');
101 }
102 }
103
104 // Schedule CRON job for deleting failed smush tasks
105 add_action('wpo_smush_clear_failed_tasks', array($this, 'clear_failed_tasks'));
106 if (!wp_next_scheduled('wpo_smush_clear_failed_tasks')) {
107 wp_schedule_event(time(), 'wpo_monthly', 'wpo_smush_clear_failed_tasks');
108 }
109 }
110
111 /**
112 * Add custom column to Media Library.
113 *
114 * @param array $columns
115 * @return array
116 */
117 public function manage_media_columns($columns) {
118 $columns['wpo_smush'] = 'WP-Optimize';
119
120 return $columns;
121 }
122
123 /**
124 * Display content in the custom column.
125 *
126 * @param string $column
127 * @param int $attachment_id
128 */
129 public function manage_media_custom_column($column, $attachment_id) {
130 if ('wpo_smush' !== $column) return;
131 echo $this->get_smush_details($attachment_id); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output already escaped when generating smush details
132 }
133
134 /**
135 * Get smush details of given image ID
136 *
137 * @param int $attachment_id
138 *
139 * @return string smush details
140 */
141 public function get_smush_details($attachment_id) {
142 $info = '<div class="wpo-smush-media-library-column" data-blog="'.esc_attr(get_current_blog_id()).'" data-id="'.esc_attr($attachment_id).'">';
143 $info .= $this->get_media_smush_column_content($attachment_id);
144 $info .= '</div>';
145
146 return $info;
147 }
148
149 /**
150 * Get content for Media Library column.
151 *
152 * @param int $attachment_id
153 *
154 * @return string
155 */
156 public function get_media_smush_column_content($attachment_id) {
157 $file = get_attached_file($attachment_id);
158 $ext = WPO_Image_Utils::get_extension($file);
159 $allowed_extensions = WPO_Image_Utils::get_allowed_extensions();
160
161 $compressed = get_post_meta($attachment_id, 'smush-complete', true) ? true : false;
162 $has_backup = get_post_meta($attachment_id, 'original-file', true) ? true : false;
163
164 $smush_info = get_post_meta($attachment_id, 'smush-info', true);
165 $smush_stats = get_post_meta($attachment_id, 'smush-stats', true);
166 $marked = get_post_meta($attachment_id, 'smush-marked');
167
168 $extract = array(
169 'blog_id' => get_current_blog_id(),
170 'post_id' => $attachment_id,
171 'smush_info_display'=> $compressed ? "display:inline-block;" : "display:none;",
172 'smush_display' => $compressed ? "display:none;" : "display:inline-block;",
173 'before_smush_sep' => !$compressed ? '<span class="wpo-action-separator"> | </span>' : '',
174 'restore_action' => $has_backup && $compressed ? "display:block;" : "display:none;",
175 'smush_mark' => !$compressed && !$marked ? "display:inline-block;" : "display:none;",
176 'smush_unmark' => $marked ? "display:inline-block;" : "display:none;",
177 'smush_info' => $smush_info ?: ' ',
178 'smush_details' => '',
179 'restore_tooltip' => $this->get_restore_image_tooltip_text(),
180 'smush_marked' => $marked,
181 );
182
183 if (!empty($smush_stats['sizes-info'])) {
184 $extract['smush_details'] = WP_Optimize()->include_template('images/smush-details.php', true, array('sizes_info' => $smush_stats['sizes-info']));
185 }
186
187 $extract['compressed_by_another_plugin'] = $this->is_image_compressed_by_another_plugin($attachment_id);
188
189 $output = '';
190
191 if (WPO_Image_Utils::is_supported_extension($ext, $allowed_extensions)) {
192 $output .= WP_Optimize()->include_template('admin-medialibrary-smush-column.php', true, $extract);
193 } else {
194 $message = __('Compressing this file type extension is not supported', 'wp-optimize');
195 $output .= sprintf("<p>%s</p>", esc_html($message));
196
197 }
198
199 if (WPO_Image_Utils::is_supported_extension($ext, array_diff($allowed_extensions, array('gif'))) && file_exists($file) && !file_exists($file . '.webp')) {
200 if (WPO_WebP_Utils::can_do_webp_conversion()) {
201 $link_text = __('Convert to WebP', 'wp-optimize');
202 $output .= '<span class="wpo-action-separator"> | </span>';
203 $output .= sprintf('<a href="#" class="convert-to-webp" data-attachment-id="%d">%s</a><br>', esc_attr($attachment_id), esc_html($link_text));
204 }
205 }
206
207 return $output;
208 }
209
210 /**
211 * The Task Manager AJAX handler
212 */
213 public function updraft_smush_ajax() {
214
215 $nonce = TeamUpdraft\WP_Optimize\Includes\Fragments\verify_nonce('nonce', 'updraft-task-manager-ajax-nonce');
216 $subaction = TeamUpdraft\WP_Optimize\Includes\Fragments\fetch_superglobal('request', 'subaction', null, 'sanitize_key');
217 $data = TeamUpdraft\WP_Optimize\Includes\Fragments\fetch_superglobal('request', 'data');
218
219 if (empty($nonce) || empty($subaction)) {
220 die('Security check failed');
221 }
222
223 if (!current_user_can(WP_Optimize()->capability_required())) {
224 die('You are not allowed to run this command.');
225 }
226
227 $allowed_commands = Updraft_Smush_Manager_Commands::get_allowed_ajax_commands();
228
229 if (in_array($subaction, $allowed_commands)) {
230
231 if ($data) {
232 $results = call_user_func(array($this->commands, $subaction), $data);
233 } else {
234 $results = call_user_func(array($this->commands, $subaction));
235 }
236
237 if (is_wp_error($results)) {
238 $results = array(
239 'status' => true,
240 'result' => false,
241 'error_code' => $results->get_error_code(),
242 'error_message' => $results->get_error_message(),
243 'error_data' => $results->get_error_data(),
244 );
245 }
246
247 echo wp_json_encode($results);
248 } else {
249 echo wp_json_encode(array('error' => 'No such command found'));
250 }
251 die();
252 }
253
254 /**
255 * Creates a task to auto compress an image on upload
256 *
257 * @param int $post_id - id of the post
258 */
259 public function autosmush_create_task($post_id) {
260
261 $post = get_post($post_id);
262 $file = get_attached_file($post_id);
263 $ext = WPO_Image_Utils::get_extension($file);
264 $allowed_extensions = WPO_Image_Utils::get_allowed_extensions();
265
266 if(!in_array($ext, $allowed_extensions)) return;
267
268 if (!$this->options->get_option('autosmush'))
269 return;
270
271 if ('image' !== substr($post->post_mime_type, 0, 5))
272 return;
273
274 if ($this->task_exists($post_id))
275 return;
276
277 $task_options = $this->get_smush_options();
278 $task_options = array_merge($task_options, array('attachment_id' => $post_id, 'blog_id' => get_current_blog_id()));
279
280 if (filesize($file) > 5242880) {
281 $task_options['request_timeout'] = 180;
282 }
283
284 $server = $this->options->get_option('compression_server', $this->webservice);
285 $task_name = $this->get_associated_task($server);
286
287 $blog_info = is_multisite() ? ', blog ID : '.get_current_blog_id() : '';
288 $description = "$task_name with attachment ID : ".$post_id . $blog_info .", auto created on : ".gmdate("F d, Y h:i:s", time());
289
290 $task = call_user_func(array($task_name, 'create_task'), 'smush', $description, $task_options, $task_name);
291
292 if ($task) $this->set_task_logger($task);
293 $this->log($description);
294
295 if (!wp_next_scheduled('process_smush_tasks')) {
296 wp_schedule_single_event(time() + 300, 'process_smush_tasks');
297 }
298 }
299
300 /**
301 * Processes the smush tasks in the queue, then cleans up the completed tasks.
302 *
303 * Before processing the queue, it first schedules a cron job to re-initiate the process after a certain
304 * interval, ensuring that the process will be completed later in case the current processing fails
305 * or is interrupted. This method can be invoked directly or scheduled as a cron job.
306 */
307 public function process_smush_tasks() {
308 /*
309 * Only add log header when called as a cron job, assuming the log header is already added by the caller
310 * when called directly. This is to avoid duplicate log headers in the log file.
311 */
312 if (defined('DOING_CRON') && DOING_CRON) {
313 $this->write_log_header();
314 }
315
316 // If there are no pending tasks, nothing to process. In that case, attempt to clean up old tasks and return
317 if ($this->is_queue_processed()) {
318 $this->clean_up_old_tasks('smush');
319 return;
320 }
321
322 if (!wp_next_scheduled('process_smush_tasks')) {
323 wp_schedule_single_event(time() + 600, 'process_smush_tasks');
324 }
325
326 // Process the queue
327 $this->clear_cached_data();
328 $this->process_queue('smush');
329 $this->clean_up_old_tasks('smush');
330 }
331
332 /**
333 * Process the compression of a single image
334 *
335 * @param int $image - ID of image
336 * @param array $options - options to use
337 * @param string $server - the server to process with
338 *
339 * @return bool | WP_Error - Status of the task
340 */
341 public function compress_single_image($image, $options, $server) {
342 $task_name = $this->get_associated_task($server);
343 $blog_info = is_multisite() ? ', blog ID : '.get_current_blog_id() : '';
344 $description = "$task_name - attachment ID : ". $image . $blog_info. ", started on : ". gmdate("F d, Y h:i:s", time());
345
346 $task = call_user_func(array($task_name, 'create_task'), 'smush', $description, $options, $task_name);
347 if ($task) $this->set_task_logger($task);
348 $this->clear_cached_data();
349
350 if (!wp_next_scheduled('prune_smush_logs')) {
351 wp_schedule_single_event(time() + 7200, 'prune_smush_logs');
352 }
353
354 return $this->process_task($task);
355 }
356
357 /**
358 * Restores a single image if a backup is available
359 *
360 * @param int $image_id - The id of the image
361 * @param int $blog_id - The id of the blog
362 *
363 * @return bool|WP_Error - success or failure
364 */
365 public function restore_single_image($image_id, $blog_id) {
366
367 $switched_blog = false;
368 if (is_multisite() && current_user_can('manage_network_options')) {
369 switch_to_blog($blog_id);
370 $switched_blog = true;
371 } elseif (is_multisite() && get_current_blog_id() != $blog_id) {
372 return new WP_Error('restore_backup_wrong_blog_id', __('The blog ID provided does not match the current blog.', 'wp-optimize'));
373 }
374
375 $error = false;
376
377 $image_path = get_attached_file($image_id);
378 $backup_path = get_post_meta($image_id, 'original-file', true);
379
380 // If the file doesn't exist, check if it's relative
381 if (!is_file($backup_path)) {
382 $uploads_dir = wp_upload_dir();
383 $uploads_basedir = trailingslashit($uploads_dir['basedir']);
384
385 if (is_file($uploads_basedir . $backup_path)) {
386 $backup_path = $uploads_basedir . $backup_path;
387 }
388 }
389
390 // If the file still doesn't exist, the record could be an absolute path from a migrated site
391 // Use the current Uploads path
392 if (!is_file($backup_path)) {
393 $current_uploads_dir_folder = trailingslashit(substr($uploads_basedir, strlen(ABSPATH)));
394 // A strict operator (!==) needs to be used, as 0 is a positive result.
395 if (false !== strpos($backup_path, $current_uploads_dir_folder)) {
396 $temp_relative_backup_path = substr($backup_path, strpos($backup_path, $current_uploads_dir_folder) + strlen($current_uploads_dir_folder));
397 if (is_file($uploads_basedir . $temp_relative_backup_path)) {
398 $backup_path = $uploads_basedir . $temp_relative_backup_path;
399 }
400 }
401
402
403 }
404
405 // If the file still doesn't exist, the record could be an absolute path from a migrated site
406 // The current Uploads path failed, so try with the default uploads directory value
407 if (!is_file($backup_path)) {
408 // A strict operator (!==) needs to be used, as 0 is a positive result.
409 if (false !== strpos($backup_path, 'wp-content/uploads/')) {
410 $backup_path = substr($backup_path, strpos($backup_path, 'wp-content/uploads/') + strlen('wp-content/uploads/'));
411 $backup_path = $uploads_basedir . $backup_path;
412 }
413 }
414
415 if (!is_file($backup_path)) {
416 // Delete information about backup.
417 delete_post_meta($image_id, 'original-file');
418 $error = new WP_Error('restore_backup_not_found', __('The backup was not found; it may have been deleted or was already restored', 'wp-optimize'));
419 } elseif (!wp_is_writable($image_path)) {
420 $error = new WP_Error('restore_failed', __('The destination could not be written to.', 'wp-optimize').' '.__("Please check your folder's permissions", 'wp-optimize'));
421 } elseif (!copy($backup_path, $image_path)) {
422 $error = new WP_Error('restore_failed', __('The file could not be copied; check your PHP error logs for details', 'wp-optimize'));
423 } elseif (!WP_Optimize_Utils::wp_delete_file($backup_path)) {
424 // translators: %s is the backup file path
425 $error = new WP_Error('restore_failed', sprintf(__('The backup file %s could not be deleted.', 'wp-optimize'), $backup_path));
426 }
427
428 if (!$error) {
429 // if backup image deleted successfully
430 // then delete from attachment meta associated smush data
431 delete_post_meta($image_id, 'smush-complete');
432 delete_post_meta($image_id, 'smush-stats');
433 delete_post_meta($image_id, 'original-file');
434 delete_post_meta($image_id, 'smush-info');
435 }
436
437 if ($switched_blog) {
438 restore_current_blog();
439 }
440
441 if (is_wp_error($error)) return $error;
442
443 $this->delete_from_cache('uncompressed_images');
444
445 if (!wp_next_scheduled('prune_smush_logs')) {
446 wp_schedule_single_event(time() + 7200, 'prune_smush_logs');
447 }
448
449 return true;
450 }
451
452 /**
453 * Restore compressed images for selected blog.
454 *
455 * @param bool $restore_backup if true then restore images from backup otherwise just delete meta.
456 * @param int $blog_id blog id.
457 * @param int $images_limit how many images process per time.
458 * @param bool $delete_only_backups_meta meta fields will be deleted only for images those will be restored from backup.
459 *
460 * @return array ['completed' => (bool), 'message' => (string), 'error' => (string)]
461 */
462 public function bulk_restore_compressed_images($restore_backup, $blog_id = 1, $images_limit = 100, $delete_only_backups_meta = false) {
463 global $wpdb;
464
465 if (is_multisite()) {
466 switch_to_blog($blog_id);
467 }
468
469 $result = array(
470 'completed' => false,
471 'message' => '',
472 'smushed_images_count' => 0,
473 );
474
475 $processed = 0;
476
477 if ($restore_backup) {
478 // get post ids those have backup meta field.
479 $image_ids = $wpdb->get_results($wpdb->prepare("SELECT post_id FROM `{$wpdb->postmeta}` WHERE meta_key = 'original-file' LIMIT %d;", $images_limit), ARRAY_A);
480
481 if (!empty($image_ids)) {
482 // run restore function for each found image.
483 foreach ($image_ids as $image) {
484 $restore_result = $this->restore_single_image($image['post_id'], $blog_id);
485
486 // if we get an error then we stop the work, except situation when "backup already restored".
487 if (is_wp_error($restore_result) && 'restore_backup_not_found' != $restore_result->get_error_code()) {
488 // we need to stop the work as we haven't restored the backup.
489 $result['error'] = $restore_result->get_error_message();
490 $this->options->delete_option('smush_images_restored');
491 break;
492 }
493
494 $processed++;
495 }
496 }
497
498 $images_count = count($image_ids);
499
500 // if all images processed then set flag completed to true.
501 if ($processed === $images_count && $images_count < $images_limit) {
502 $this->options->delete_option('smush_images_restored');
503 $result['completed'] = true;
504 } else {
505 // save into options total processed count.
506 $processed += $this->options->get_option('smush_images_restored', 0);
507 $this->options->update_option('smush_images_restored', $processed);
508
509 if (is_multisite()) {
510 // translators: %1$s is the number of restored images, %2$s is the site url.
511 $result['message'] = sprintf(__('%1$s compressed images were restored from their backup for the site %2$s', 'wp-optimize'), $processed, get_site_url($blog_id));
512 } else {
513 // translators: %s is the number of restored images.
514 $result['message'] = sprintf(__('%s compressed images were restored from their backup', 'wp-optimize'), $processed);
515 }
516 }
517
518 } else {
519 // if we just delete compressed images meta then set complete flag to true.
520 $result['completed'] = true;
521 }
522
523 if ($result['completed']) {
524
525 $smushed_images_count = $wpdb->get_var("SELECT COUNT(*) FROM `{$wpdb->postmeta}` WHERE meta_key='smush-complete' AND meta_value=1");
526 $result['smushed_images_count'] = $smushed_images_count;
527
528 if ($delete_only_backups_meta) {
529 if (is_multisite()) {
530 if ($smushed_images_count > 0) {
531 // translators: %s is the site url
532 $result['message'] = sprintf(__('All the compressed images for site %s with backup copies of their original files were successfully restored.', 'wp-optimize'), get_site_url($blog_id));
533 // translators: %s is the number of images
534 $result['message'] .= ' '.sprintf(_n('Unable to restore %s image without backup files.', 'Unable to restore %s images without backup files.', $smushed_images_count, 'wp-optimize'), $smushed_images_count);
535 } else {
536 // translators: %s is the site url
537 $result['message'] = sprintf(__('All the compressed images for the site %s were successfully restored.', 'wp-optimize'), get_site_url($blog_id));
538 }
539 } else {
540 if ($smushed_images_count > 0) {
541 $result['message'] = __('All the compressed images with backup copies of their original files were successfully restored.', 'wp-optimize');
542 // translators: %s is the number of images
543 $result['message'] .= ' '.sprintf(_n('Unable to restore %s image without backup files.', 'Unable to restore %s images without backup files.', $smushed_images_count, 'wp-optimize'), $smushed_images_count);
544 } else {
545 $result['message'] = __('All the compressed images were successfully restored.', 'wp-optimize');
546 }
547 }
548 } else {
549 if (is_multisite()) {
550 // translators: %s is the site url
551 $result['message'] = sprintf(__('All the compressed images for the site %s were successfully marked as uncompressed.', 'wp-optimize'), get_site_url($blog_id));
552 } else {
553 $result['message'] = __('All the compressed images were successfully marked as uncompressed.', 'wp-optimize');
554 }
555 }
556
557 // clear all metas for smushed images after work completed.
558 // if $delete_only_backup_meta set to true then all meta fields was deleted in restore_single_image()
559 // and we don't need to delete metas for other images.
560 if (!$delete_only_backups_meta) {
561 $wpdb->query("DELETE FROM `{$wpdb->postmeta}` WHERE meta_key IN ('smush-complete', 'smush-stats', 'original-file', 'smush-info');");
562 }
563 }
564
565 if (is_multisite()) {
566 restore_current_blog();
567 }
568
569 return $result;
570 }
571
572 /**
573 * Process bulk smushing operation
574 *
575 * @param array $images - the array of images to process
576 * @return bool - true if processing complete
577 */
578 public function process_bulk_smush($images = array()) {
579
580 // Get a list of pending tasks so we can exclude those
581 $pending_tasks = $this->get_pending_tasks();
582 $queued_images = array();
583
584 $this->write_log_header();
585
586 if (!empty($pending_tasks)) {
587 foreach ($pending_tasks as $task) {
588 $queued_images[] = array(
589 'attachment_id' => $task->get_option('attachment_id'),
590 'blog_id' => $task->get_option('blog_id')
591 );
592 }
593 }
594 $default_task_options = $this->get_smush_options();
595 foreach ($images as $image) {
596 // Skip if already in the queue
597 if (in_array($image, $queued_images)) continue;
598
599 $task_options = array_merge($default_task_options, array('attachment_id' => intval($image['attachment_id']), 'blog_id' => intval($image['blog_id'])));
600 $server = $this->options->get_option('compression_server', $this->webservice);
601 $task_name = $this->get_associated_task($server);
602
603 $blog_info = is_multisite() ? ', Blog ID : '.intval($image['blog_id']) : '';
604 $description = "$task_name - Attachment ID : ". intval($image['attachment_id']) . $blog_info . ", Started on : ". gmdate("F d, Y h:i:s", time());
605 $task = call_user_func(array($task_name, 'create_task'), 'smush', $description, $task_options, $task_name);
606 if ($task) $this->set_task_logger($task);
607 }
608
609 $this->process_smush_tasks();
610
611 if (!wp_next_scheduled('prune_smush_logs')) {
612 wp_schedule_single_event(time() + 7200, 'prune_smush_logs');
613 }
614
615 return true;
616 }
617
618
619 /**
620 * Check if a specified server online
621 *
622 * @param string $server - the server to test
623 * @return bool - true if yes, false otherwise
624 */
625 public function check_server_online($server = 'resmushit') {
626 $task = $this->get_associated_task($server);
627 $online = call_user_func(array($task, 'is_server_online'));
628
629 if ($online) {
630 update_option($task, strtotime('now'));
631 } else {
632 $this->log(get_option($task));
633 }
634
635 $this->log(sprintf('%s server status: %s', $task, $online ? 'Online' : 'Offline'));
636 return $online;
637 }
638
639 /**
640 * Checks if the queue for smushing is complete
641 *
642 * @return bool - true if processed, false otherwise
643 */
644 public function is_queue_processed() {
645
646 $active = $this->get_pending_tasks();
647 if ($active && 0 != count($active)) return false;
648
649 return true;
650 }
651
652 /**
653 * Logs useful data once a smush task completes or if it fails
654 *
655 * @param mixed $task - A task object
656 */
657 public function record_stats($task) {
658
659 $attachment_id = $task->get_option('attachment_id');
660 $completed_task_count = $this->options->get_option('completed_task_count');
661 $failed_task_count = $this->options->get_option('failed_task_count', 0);
662 $total_bytes_saved = $this->options->get_option('total_bytes_saved');
663 $total_percent_saved = $this->options->get_option('total_percent_saved', 0);
664
665 if ('ud_task_failed' === current_action()) {
666 $this->options->update_option('failed_task_count', ++$failed_task_count);
667 return;
668 }
669
670 if (empty($completed_task_count)) {
671 $completed_task_count = $total_bytes_saved = 0;
672 }
673
674 if (!$total_bytes_saved) {
675 $total_bytes_saved = 0;
676 }
677
678 if (is_multisite()) {
679 switch_to_blog($task->get_option('blog_id', 1));
680 $stats = get_post_meta($attachment_id, 'smush-stats', true);
681 restore_current_blog();
682 } else {
683 $stats = get_post_meta($attachment_id, 'smush-stats', true);
684 }
685
686 if (isset($stats['sizes-info'])) {
687
688 $original_size = 0;
689 $compressed_size = 0;
690
691 foreach ($stats['sizes-info'] as $info) {
692 $original_size += $info['original'];
693 $compressed_size += $info['compressed'];
694 }
695
696 $percent = round((($original_size - $compressed_size) / $original_size * 100), 2);
697 } else {
698 $original_size = $stats['original-size'] ?? 0;
699 $compressed_size = $stats['smushed-size'] ?? 0;
700 $percent = $stats['savings-percent'] ?? 0;
701 }
702
703 $saved = $original_size - $compressed_size;
704 $completed_task_count++;
705
706 $total_bytes_saved += $saved;
707 $total_percent_saved = (($total_percent_saved * ($completed_task_count - 1)) + $percent) / $completed_task_count;
708
709 $this->options->update_option('completed_task_count', $completed_task_count);
710 $this->options->update_option('total_bytes_saved', $total_bytes_saved);
711 $this->options->update_option('total_percent_saved', $total_percent_saved);
712 }
713
714 /**
715 * Get current smush options.
716 *
717 * @return array
718 */
719 public function get_smush_options() {
720 static $smush_options = array();
721 if (empty($smush_options)) {
722 $smush_options = array(
723 'compression_server' => $this->options->get_option('compression_server', $this->get_default_webservice()),
724 'image_quality' => $this->options->get_option('image_quality', 92),
725 'lossy_compression' => $this->options->get_option('lossy_compression'),
726 'keep_original' => $this->options->get_option('back_up_original', true),//repeated below
727 'back_up_original' => $this->options->get_option('back_up_original', true),
728 'back_up_delete_after' => $this->options->get_option('back_up_delete_after', true),
729 'back_up_delete_after_days' => $this->options->get_option('back_up_delete_after_days', 50),
730 'preserve_exif' => $this->options->get_option('preserve_exif', false),
731 'autosmush' => $this->options->get_option('autosmush'),
732 'show_smush_metabox' => $this->options->get_option('show_smush_metabox', 'show') === 'show',
733 'webp_conversion' => $this->options->get_option('webp_conversion')
734 );
735 }
736 return $smush_options;
737 }
738
739 /**
740 * Update smush options
741 * Only options whose values differ from the stored values are updated.
742 *
743 * @param array $options Associative array of option names and values sent in via AJAX.
744 * @return bool True if all updates succeeded or no changes were required,
745 * false if at least one option update failed.
746 */
747 public function update_smush_options($options) {
748 $success = true;
749 foreach ($options as $option => $value) {
750 // Here we only store string, array as string after serialization, boolean, and integer values which will be stored as strings (LONGTEXT field type)
751 // So loose comparison is fine, strict comparison results to buggy behavior
752 if ($this->options->get_option($option) == $value) {
753 continue;
754 }
755
756 if (!$this->options->update_option($option, $value)) {
757 $success = false;
758 }
759 }
760 return $success;
761 }
762
763 /**
764 * Clears smush related stats
765 *
766 * @return bool - status of the update
767 */
768 public function clear_smush_stats() {
769 $this->options->update_option('failed_task_count', 0);
770 $this->options->update_option('completed_task_count', false);
771 $this->options->update_option('total_bytes_saved', false);
772 $this->options->update_option('total_percent_saved', 0);
773
774 return true;
775 }
776
777 /**
778 * Returns array of translations used in javascript code.
779 *
780 * @return array - translations used in JS
781 */
782 public function smush_js_translations() {
783 $resmushit_article_link = WP_Optimize()->wp_optimize_url('https://resmush.it/api/', __('resmushIt', 'wp-optimize'), '', '', true);
784
785 return apply_filters('updraft_smush_js_translations', array(
786 'all_images_compressed' => __('No uncompressed images were found.', 'wp-optimize'),
787 'error_unexpected_response' => __('An unexpected response was received from the server.', 'wp-optimize') . ' ' . __('More information has been logged in the browser console.', 'wp-optimize'),
788 'compress_single_image_dialog' => __('Please wait: compressing the selected image.', 'wp-optimize'),
789 'error_try_again_later' => __('Please try again later.', 'wp-optimize'),
790 'server_check' => __('Connecting to the Smush API server, please wait', 'wp-optimize'),
791 'please_wait' => __('Please wait while the request is being processed', 'wp-optimize'),
792 'server_error' => __('There was an error connecting to the image compression server.', 'wp-optimize') .
793 '<br>' . __('This could mean either the server is temporarily unavailable or there are connectivity issues with your internet connection.', 'wp-optimize') . ' ' .
794 // translators: %s is a link
795 '<i>' . sprintf(__('(Also ensure IPs listed at the bottom of this %s page are whitelisted by your webserver).', 'wp-optimize'), $resmushit_article_link) . '</i>' .
796 '<br>' . __('Please try later.', 'wp-optimize'),
797 'exceeded_max_filesize' => __('This image is too large to be optimized — it exceeds the provider\'s size limit', 'wp-optimize'),
798 'please_select_images' => __('Please select the images you want compressed from the "Uncompressed images" panel first', 'wp-optimize'),
799 'please_updating_images_info' => __('Please wait: updating information about the selected image.', 'wp-optimize'),
800 'please_select_compressed_images' => __('Please select the images you want to mark as already compressed from the "Uncompressed images" panel first', 'wp-optimize'),
801 'view_image' => __('View Image', 'wp-optimize'),
802 'delete_image_backup_confirm' => __('Do you really want to delete all backup images now?', 'wp-optimize') . ' ' . __('This action is irreversible.', 'wp-optimize'),
803 'mark_all_images_uncompressed' => __('Do you really want to mark all the images as uncompressed?', 'wp-optimize') . ' ' . __('This action is irreversible.', 'wp-optimize'),
804 'restore_images_from_backup' => __('Do you want to restore the original images from the backup (where they exist?)', 'wp-optimize'),
805 'restore_all_compressed_images' => __('Do you really want to restore all the compressed images?', 'wp-optimize'),
806 'webp_conversion_tool_error' => __('No WebP conversion tools are available on your web-server.', 'wp-optimize'),
807 'webp_conversion_tool_how_to' => __('How to get the WebP conversion tools to work?', 'wp-optimize'),
808 'more' => __('More', 'wp-optimize'),
809 'less' => __('Less', 'wp-optimize'),
810 'converting_to_webp' => __('Converting image to WebP format, please wait', 'wp-optimize'),
811 'compress_image' => __('Compress Image', 'wp-optimize'),
812 'compress' => __('Compress', 'wp-optimize'),
813 'cancel' => __('Cancel', 'wp-optimize'),
814 'close' => __('Close', 'wp-optimize'),
815 ));
816 }
817
818 /**
819 * Adds a smush metabox on the post edit screen for images
820 *
821 * @param WP_Post $post - a post object
822 */
823 public function add_smush_metabox($post) {
824
825 if (!wp_attachment_is_image($post->ID)) return;
826
827 if (!file_exists(get_attached_file($post->ID))) {
828 return;
829 }
830
831 add_meta_box('smush-metabox', __('Compress Image', 'wp-optimize'), array($this, 'render_smush_metabox'), 'attachment', 'side');
832 }
833
834 /**
835 * Renders a metabox on the post edit screen for images
836 *
837 * @param WP_Post $post - a post object
838 */
839 public function render_smush_metabox($post) {
840
841 $compressed = (bool) get_post_meta($post->ID, 'smush-complete', true);
842 $has_backup = (bool) get_post_meta($post->ID, 'original-file', true);
843
844 $smush_info = get_post_meta($post->ID, 'smush-info', true);
845 $smush_stats = get_post_meta($post->ID, 'smush-stats', true);
846 $marked = get_post_meta($post->ID, 'smush-marked');
847
848 $options = Updraft_Smush_Manager()->get_smush_options();
849
850 $file = get_attached_file($post->ID);
851 $ext = WPO_Image_Utils::get_extension($file);
852 $allowed_extensions = WPO_Image_Utils::get_allowed_extensions();
853 $file_size = ($file && is_file($file)) ? filesize($file) : 0;
854
855 $extract = array(
856 'post_id' => $post->ID,
857 'smush_display' => $compressed ? "display:none;" : "display:block;",
858 'restore_display' => $compressed ? "display:block;" : "display:none;",
859 'restore_action' => $has_backup ? "display:block;" : "display:none;",
860 'smush_mark' => !$compressed && !$marked ? "display:block;" : "display:none;",
861 'smush_unmark' => $marked ? "display:block;" : "display:none;",
862 'smush_info' => $smush_info ?: ' ',
863 'file_size' => $file_size,
864 'smush_options' => $options,
865 'custom' => 90 >= $options['image_quality'] && 65 <= $options['image_quality'],
866 'smush_details' => '',
867 'restore_tooltip' => $this->get_restore_image_tooltip_text(),
868 'smush_marked' => $marked,
869 );
870
871 if (!empty($smush_stats['sizes-info'])) {
872 $extract['smush_details'] = WP_Optimize()->include_template('images/smush-details.php', true, array('sizes_info' => $smush_stats['sizes-info']));
873 }
874
875 $extract['smush_settings_form'] = WP_Optimize()->include_template('admin-metabox-smush-settings.php', true, $extract);
876 $extract['compressed_by_another_plugin'] = $this->is_image_compressed_by_another_plugin($post->ID);
877 if (WPO_Image_Utils::is_supported_extension($ext, $allowed_extensions)) {
878 WP_Optimize()->include_template('admin-metabox-smush.php', false, $extract);
879 } else {
880 printf("<p>%s</p>", esc_html__('Compressing this file type extension is not supported', 'wp-optimize'));
881 }
882 }
883
884 /**
885 * Get text for restore image tooltip.
886 *
887 * @return string
888 */
889 private function get_restore_image_tooltip_text() {
890 $text = __('Only the original image will be restored.', 'wp-optimize');
891 $text .= ' ';
892 $text .= __('In order to restore the other sizes, you should use a plugin such as "Regenerate Thumbnails".', 'wp-optimize');
893
894 return $text;
895 }
896
897 /**
898 * Check if a single image compressed by another plugin.
899 *
900 * @param int $image_id
901 * @return bool
902 */
903 private function is_image_compressed_by_another_plugin($image_id) {
904 global $wpdb;
905 static $is_ewwwio_images_table_exists = null;
906
907 $meta = $wpdb->get_results($wpdb->prepare("SELECT meta_key, meta_value FROM `{$wpdb->postmeta}` WHERE `post_id`=%d", $image_id), ARRAY_A);
908
909 if (is_array($meta)) {
910 foreach ($meta as $row) {
911 // Smush, Imagify, Compress JPEG & PNG images by TinyPNG.
912 if (in_array($row['meta_key'], array('wp-smpro-smush-data', '_imagify_optimization_level', 'tiny_compress_images'))) return true;
913 // ShortPixel Image Optimizer
914 if ('_shortpixel_status' === $row['meta_key'] && 2 <= (int) $row['meta_value'] && 3 > (int) $row['meta_value']) return true;
915 }
916 }
917
918 $is_ewwwio_images_table_exists = is_null($is_ewwwio_images_table_exists) ? WP_Optimize()->get_db_info()->table_exists('ewwwio_images') : $is_ewwwio_images_table_exists;
919
920 if ($is_ewwwio_images_table_exists) {
921 $old_show_errors = $wpdb->show_errors(false);
922 // EWWW Image Optimizer.
923 $ewww_image = $wpdb->get_col($wpdb->prepare("SELECT attachment_id FROM `{$wpdb->prefix}ewwwio_images` WHERE attachment_id=%d AND gallery='media' LIMIT 1", $image_id));
924 if (!empty($ewww_image)) return true;
925 $wpdb->show_errors($old_show_errors);
926 }
927
928 return apply_filters('wpo_image_compressed_by_another_plugin', false);
929 }
930
931 /**
932 * Get attachment ids for images those already compressed with EWWW Image Optimizer.
933 * Used with filter `wpo_get_uncompressed_images_args`.
934 *
935 * @param array $args WP_Query arguments.
936 * @return array
937 */
938 public function ewww_image_optimizer_compressed_images_args($args) {
939 global $wpdb;
940
941 if (!WP_Optimize()->get_db_info()->table_exists('ewwwio_images')) return $args;
942
943 $old_show_errors = $wpdb->show_errors(false);
944 $compressed_images = $wpdb->get_col("SELECT DISTINCT(attachment_id) FROM `{$wpdb->prefix}ewwwio_images` WHERE gallery='media'");
945 $wpdb->show_errors($old_show_errors);
946
947 if (isset($args['post__not_in'])) {
948 $args['post__not_in'] = array_merge($args['post__not_in'], $compressed_images); // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- is necessary and used with caution
949 } else {
950 $args['post__not_in'] = $compressed_images; // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- is necessary and used with caution
951 }
952
953 return $args;
954 }
955
956 /**
957 * Returns a list of images for smush (from cache if available)
958 *
959 * @param string $use_cache
960 * @return array - uncompressed images
961 */
962 public function get_uncompressed_images($use_cache = "true") {
963 if ("true" === $use_cache) {
964 $uncompressed_images = $this->get_from_cache('uncompressed_images');
965 if ($uncompressed_images && is_array($uncompressed_images)) return $uncompressed_images;
966 }
967
968 $uncompressed_images = array();
969 $accepted_mimes = array('image/jpeg', 'image/gif', 'image/png');
970
971 $args = array(
972 'post_type' => 'attachment',
973 'post_mime_type' => $accepted_mimes,
974 'post_status' => 'inherit',
975 'posts_per_page' => apply_filters('updraft_smush_posts_per_page', 1000),
976 'meta_query' => $this->get_uncompressed_images_meta_query(),
977 'no_found_rows' => true,
978 'fields' => 'ids'
979 );
980
981 $allowed_extensions = WPO_Image_Utils::get_allowed_extensions();
982
983 if (is_multisite()) {
984
985 $sites = WP_Optimize()->get_sites();
986
987 foreach ($sites as $site) {
988
989 switch_to_blog($site->blog_id);
990
991 $args = apply_filters('wpo_get_uncompressed_images_args', $args);
992 $images = new WP_Query($args);
993
994 foreach ($images->posts as $image) {
995 // If `field` is removed from $args it returns a WP_Post obj
996 $image_id = is_int($image) ? $image : $image->ID;
997
998 $file = get_attached_file($image_id);
999 $ext = WPO_Image_Utils::get_extension($file);
1000 if (file_exists($file)) {
1001 if (WPO_Image_Utils::is_supported_extension($ext, $allowed_extensions)) {
1002 $uncompressed_images[$site->blog_id][] = array(
1003 'id' => $image_id,
1004 'thumb_url' => wp_get_attachment_thumb_url($image_id),
1005 'filesize' => filesize(get_attached_file($image_id))
1006 );
1007 } else {
1008 $this->log("Blog_id=$site->blog_id, ID=$image_id, File=$file This image type is not supported.");
1009 }
1010 } else {
1011 $this->log("Could not find file for image: blog_id=$site->blog_id, ID=$image_id, file=$file");
1012 }
1013 }
1014
1015 restore_current_blog();
1016 }
1017
1018 } else {
1019 $args = apply_filters('wpo_get_uncompressed_images_args', $args);
1020 $images = new WP_Query($args);
1021 foreach ($images->posts as $image) {
1022 // If `field` is removed from $args it returns a WP_Post obj
1023 $image_id = is_int($image) ? $image : $image->ID;
1024
1025 $file = get_attached_file($image_id);
1026 $ext = WPO_Image_Utils::get_extension($file);
1027
1028 if (file_exists($file)) {
1029 if (WPO_Image_Utils::is_supported_extension($ext, $allowed_extensions)) {
1030 $uncompressed_images[1][] = array(
1031 'id' => $image_id,
1032 'thumb_url' => wp_get_attachment_thumb_url($image_id),
1033 'filesize' => filesize(get_attached_file($image_id))
1034 );
1035 } else {
1036 $this->log("Image ID=$image_id, File=$file This image type is not supported.");
1037 }
1038 } else {
1039 $this->log("Could not find file for image: ID=$image_id, file=$file");
1040 }
1041 }
1042 }
1043
1044 $this->save_to_cache('uncompressed_images', $uncompressed_images);
1045 return $uncompressed_images;
1046 }
1047
1048 /**
1049 * Returns a list of admin URLs. This is to prevent unnecessary bloat in the output of get_uncompressed_images() (and thus better performance over the network on sites with huge numbers of images)
1050 *
1051 * @return array - list of admin URLs
1052 */
1053 public function get_admin_urls() {
1054
1055 $admin_urls = $this->get_from_cache('admin_urls');
1056
1057 if ($admin_urls) return $admin_urls;
1058
1059 $admin_urls = array();
1060
1061 if (is_multisite()) {
1062
1063 $sites = WP_Optimize()->get_sites();
1064
1065 foreach ($sites as $site) {
1066 switch_to_blog($site->blog_id);
1067 $admin_urls[$site->blog_id] = admin_url();
1068 restore_current_blog();
1069 }
1070
1071 } else {
1072 // The pseudo-blog_id here (1) matches (and must match) what is used in get_uncompressed_images
1073 $admin_urls[1] = admin_url();
1074 }
1075
1076 $this->save_to_cache('admin_urls', $admin_urls);
1077 return $admin_urls;
1078 }
1079
1080 /**
1081 * Check if a task exists for a given image
1082 *
1083 * @param string $image - The attachment ID of the image
1084 * @return bool - true if yes, false otherwise
1085 */
1086 public function task_exists($image) {
1087
1088 $blog_id = get_current_blog_id();
1089 $pending_tasks = $this->get_active_tasks('smush');
1090
1091 if (!empty($pending_tasks)) {
1092 foreach ($pending_tasks as $task) {
1093 $task_attachment_id = $task->get_option('attachment_id');
1094 $task_blog_id = $task->get_option('blog_id');
1095
1096 if ($image === $task_attachment_id && $blog_id === $task_blog_id) {
1097 return true;
1098 }
1099 }
1100 }
1101 return false;
1102 }
1103
1104 /**
1105 * Returns the status of images compressed in this iteration of the bulk compress
1106 *
1107 * @param array $images - List of images in the current session
1108 *
1109 * @return array - status of the operation
1110 */
1111 public function get_session_stats($images) {
1112 $stats = array();
1113
1114 foreach ($images as $image) {
1115 if (is_multisite()) {
1116 switch_to_blog($image['blog_id']);
1117 $stats[] = get_post_meta($image['attachment_id'], 'smush-complete', true) ? 'success' : 'fail';
1118 restore_current_blog();
1119 } else {
1120 $stats[] = get_post_meta($image['attachment_id'], 'smush-complete', true) ? 'success' : 'fail';
1121 }
1122 }
1123
1124 return array_count_values($stats);
1125 }
1126
1127 /**
1128 * Returns a list of images for smush (from cache if available)
1129 *
1130 * @return array|mixed - List of task objects with uncompressed images
1131 */
1132 public function get_pending_tasks() {
1133 return $this->get_active_tasks('smush');
1134 }
1135
1136 /**
1137 * Deletes and removes any pending tasks from queue
1138 *
1139 * @return bool
1140 */
1141 public function clear_pending_images() {
1142
1143 $pending_tasks = $this->get_active_tasks('smush');
1144
1145 if (!empty($pending_tasks)) {
1146 foreach ($pending_tasks as $task) {
1147 $task->delete_meta();
1148 $task->delete();
1149 }
1150 }
1151
1152 return true;
1153 }
1154
1155
1156 /**
1157 * Returns a count of failed tasks
1158 *
1159 * @return int - failed tasks
1160 */
1161 public function get_failed_task_count() {
1162 return $this->options->get_option('failed_task_count', 0);
1163 }
1164
1165 /**
1166 * Adds the required scripts and styles
1167 */
1168 public function admin_enqueue_scripts() {
1169 $current_screen = get_current_screen();
1170 if (null === $current_screen) return;
1171 // load scripts and styles only on WP-Optimize pages
1172 if (!preg_match('/wp\-optimize|attachment|upload/i', $current_screen->id)) return;
1173
1174 $enqueue_version = WP_Optimize()->get_enqueue_version();
1175 $min_or_not = WP_Optimize()->get_min_or_not_string();
1176 $min_or_not_internal = WP_Optimize()->get_min_or_not_internal_string();
1177
1178 $js_variables = $this->smush_js_translations();
1179 $js_variables['ajaxurl'] = admin_url('admin-ajax.php');
1180 $js_variables['features'] = $this->get_features();
1181
1182 $js_variables['smush_ajax_nonce'] = wp_create_nonce('updraft-task-manager-ajax-nonce');
1183 $js_variables['smush_settings'] = $this->get_smush_options();
1184 $js_variables['blog_id'] = get_current_blog_id();
1185 $js_variables['compress'] = esc_html__('Compress', 'wp-optimize');
1186 $js_variables['cancel'] = esc_html__('Cancel', 'wp-optimize');
1187 $js_variables['cancelling'] = esc_html__('Cancelling...', 'wp-optimize');
1188 $js_variables['images_restored_successfully'] = esc_html__('The images were restored successfully', 'wp-optimize');
1189 $js_variables['logo_src'] = esc_url(WPO_PLUGIN_URL.'images/notices/wp_optimize_logo.png');
1190
1191 wp_enqueue_script('block-ui-js', WPO_PLUGIN_URL.'includes/blockui/jquery.blockUI'.$min_or_not.'.js', array('jquery'), $enqueue_version);
1192 wp_enqueue_script('wp-optimize-heartbeat-js', WPO_PLUGIN_URL.'js/heartbeat'.$min_or_not_internal.'.js', array('jquery'), $enqueue_version);
1193 wp_localize_script('wp-optimize-heartbeat-js', 'wpo_heartbeat_ajax', array(
1194 'ajaxurl' => admin_url('admin-ajax.php'),
1195 'nonce' => wp_create_nonce('heartbeat-nonce'),
1196 'interval' => WPO_Ajax::HEARTBEAT_INTERVAL
1197 ));
1198 wp_enqueue_script('smush-js', WPO_PLUGIN_URL.'js/wposmush'.$min_or_not_internal.'.js', array('jquery', 'block-ui-js', 'wp-optimize-block-ui', 'wp-optimize-send-command', 'wp-optimize-heartbeat-js'), $enqueue_version);
1199 wp_enqueue_style('smush-css', WPO_PLUGIN_URL.'css/smush'.$min_or_not_internal.'.css', array(), $enqueue_version);
1200 wp_localize_script('smush-js', 'wposmush', $js_variables);
1201 }
1202
1203 /**
1204 * Gets default service provider for smush
1205 *
1206 * @return string - service name
1207 */
1208 public function get_default_webservice() {
1209 return 'resmushit';
1210 }
1211
1212 /**
1213 * Sets default options for smush
1214 */
1215 public function set_default_options() {
1216
1217 $options = array(
1218 'compression_server' => $this->get_default_webservice(),
1219 'image_quality' => 92,
1220 'lossy_compression' => false,
1221 'back_up_original' => true,
1222 'preserve_exif' => false,
1223 'autosmush' => false,
1224 'back_up_delete_after' => $this->options->get_option('back_up_delete_after', true),
1225 'back_up_delete_after_days' => $this->options->get_option('back_up_delete_after_days', 50),
1226 'webp_conversion' => false,
1227 );
1228
1229 $this->update_smush_options($options);
1230 }
1231
1232 /**
1233 * Gets default service provider for smush
1234 *
1235 * @param string $server - The name of the server
1236 * @return string - associated task type, default if none found
1237 */
1238 public function get_associated_task($server) {
1239 $allowed = $this->get_allowed_services();
1240
1241 if (key_exists($server, $allowed))
1242 return $allowed[$server];
1243
1244 $default = $this->get_default_webservice();
1245 return $allowed[$default];
1246 }
1247
1248 /**
1249 * Gets allowed service providers for smush
1250 *
1251 * @return array - A pair of key/value. e.g. service name => task name
1252 */
1253 public function get_allowed_services() {
1254 return array(
1255 'resmushit' => 'Re_Smush_It_Task',
1256 );
1257 }
1258
1259 /**
1260 * Gets current compression server hint.
1261 *
1262 * @return string
1263 */
1264 public function get_compression_server_hint() {
1265
1266 switch ($this->webservice) {
1267 case 'resmushit':
1268 $title = 'reSmush.it';
1269 break;
1270 default:
1271 $title = $this->webservice;
1272 }
1273
1274 // translators: %s is a service used to compress images
1275 $hint = sprintf(__('Service is provided by %s', 'wp-optimize'), $title);
1276
1277 return $hint;
1278 }
1279
1280 /**
1281 * Gets allowed service provider features smush
1282 *
1283 * @return array - A pair of key/value. e.g. service name => features exposed
1284 */
1285 public function get_features() {
1286 $features = array();
1287 foreach ($this->get_allowed_services() as $service => $class_name) {
1288 $features[$service] = call_user_func(array($class_name, 'get_features'));
1289 }
1290 return $features;
1291 }
1292
1293 /**
1294 * Returns the path to the logfile
1295 *
1296 * @return string - file path
1297 */
1298 public function get_logfile_path() {
1299 return WP_Optimize_Utils::get_log_file_path('smush');
1300 }
1301
1302 /**
1303 * Delete all smush log files
1304 *
1305 * @deprecated 3.5.0
1306 */
1307 public function delete_log_files() {
1308 _deprecated_function(__METHOD__, '3.5.0');
1309 }
1310
1311 /**
1312 * Adds a logger to the task
1313 *
1314 * @param mixed $task - a task object
1315 */
1316 public function set_task_logger($task) {
1317 if (!$this->logger) {
1318 $this->logger = new Updraft_File_Logger($this->get_logfile_path());
1319 }
1320
1321 if (!$task->get_loggers()) {
1322 $task->add_logger($this->logger);
1323 }
1324 }
1325
1326 /**
1327 * Writes a standardised header to the log file
1328 */
1329 public function write_log_header() {
1330 global $wpdb;
1331
1332 // phpcs:disable
1333 $wp_version = $this->get_wordpress_version();
1334 $mysql_version = $wpdb->db_version();
1335 $disabled_functions = ini_get('disable_functions');
1336 $max_execution_time = (int) @ini_get("max_execution_time");
1337
1338 $memory_limit = ini_get('memory_limit');
1339 $memory_usage = round(@memory_get_usage(false)/1048576, 1);
1340 $total_memory_usage = round(@memory_get_usage(true)/1048576, 1);
1341
1342 // Attempt to raise limit
1343 @set_time_limit(330);
1344
1345 $log_header = array();
1346
1347 // phpcs:enable
1348 $log_header[] = "\n";
1349 $log_header[] = "Header for logs at time: ".gmdate('r')." on ".network_site_url();
1350 $log_header[] = "WP: ".$wp_version;
1351 $php_uname = '';
1352 if (function_exists('php_uname')) {
1353 $php_uname = ", " . php_uname();
1354 }
1355 $log_header[] = "PHP: ".phpversion()." (".PHP_SAPI.$php_uname.")";
1356 $log_header[] = "MySQL: $mysql_version";
1357 $log_header[] = "WPLANG: ".get_locale();
1358 $log_header[] = "Server: ". TeamUpdraft\WP_Optimize\Includes\Fragments\fetch_superglobal('server', 'SERVER_SOFTWARE', 'string', 'sanitize_text_field', '-');
1359 $log_header[] = "Outbound connections: ".(defined('WP_HTTP_BLOCK_EXTERNAL') ? 'Y' : 'N');
1360 $log_header[] = "Disabled Functions: $disabled_functions";
1361 $log_header[] = "max_execution_time: $max_execution_time";
1362 $log_header[] = "memory_limit: $memory_limit (used: {$memory_usage}M | {$total_memory_usage}M)";
1363 $log_header[] = "multisite: ".(is_multisite() ? 'Y' : 'N');
1364 $log_header[] = "openssl: ".(defined('OPENSSL_VERSION_TEXT') ? OPENSSL_VERSION_TEXT : 'N');
1365
1366 if (apply_filters("wpo_write_server_info_in_smush_log", false)) {
1367 foreach ($log_header as $log_entry) {
1368 $this->log($log_entry);
1369 }
1370 }
1371
1372 $memory_limit = $this->memory_check_current();
1373
1374 if ($memory_limit<65 && $memory_limit>0) {
1375 $this->log(sprintf('The amount of memory (RAM) allowed for PHP is very low (%s Mb) - you should increase it to avoid failures due to insufficient memory (consult your web hosting company for more help)', round($memory_limit, 1)), 'warning');
1376 }
1377
1378 if ($max_execution_time>0 && $max_execution_time<20) {
1379 $this->log(sprintf('The amount of time allowed for WordPress plugins to run is very low (%s seconds) - you should increase it to avoid failures due to time-outs (consult your web hosting company for more help - it is the max_execution_time PHP setting; the recommended value is %s seconds or more)', $max_execution_time, 90), 'warning');
1380 }
1381 }
1382
1383 /**
1384 * Prunes the log file
1385 */
1386 public function prune_smush_logs() {
1387 $this->log("Pruning the smush log file");
1388 $this->logger->prune_logs();
1389 }
1390
1391 /**
1392 * Get the WordPress version
1393 *
1394 * @return string - the version
1395 */
1396 public function get_wordpress_version() {
1397 static $got_wp_version = false;
1398
1399 if (!$got_wp_version) {
1400 global $wp_version;
1401 @include(ABSPATH.WPINC.'/version.php');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress warning if `version.php` does not exist
1402 $got_wp_version = $wp_version;
1403 }
1404
1405 return $got_wp_version;
1406 }
1407
1408 /**
1409 * Get the current memory limit
1410 *
1411 * @param ?string $memory_limit Memory limit in megabytes
1412 *
1413 * @return string - memory limit in megabytes
1414 */
1415 public function memory_check_current($memory_limit = null) {
1416 // Returns in megabytes
1417 if (empty($memory_limit)) $memory_limit = ini_get('memory_limit');
1418 $memory_limit = rtrim($memory_limit);
1419 $memory_unit = $memory_limit[strlen($memory_limit)-1];
1420 if (0 === (int) $memory_unit && '0' !== $memory_unit) {
1421 $memory_limit = substr($memory_limit, 0, strlen($memory_limit)-1);
1422 } else {
1423 $memory_unit = '';
1424 }
1425 switch ($memory_unit) {
1426 case '':
1427 $memory_limit = floor($memory_limit/1048576);
1428 break;
1429 case 'K':
1430 case 'k':
1431 $memory_limit = floor($memory_limit/1024);
1432 break;
1433 case 'G':
1434 $memory_limit = $memory_limit*1024;
1435 break;
1436 case 'M':
1437 // assumed size, no change needed
1438 break;
1439 }
1440 return $memory_limit;
1441 }
1442
1443 /**
1444 * Saves a value to the cache.
1445 *
1446 * @param string $key
1447 * @param mixed $value
1448 * @param int $blog_id
1449 */
1450 public function save_to_cache($key, $value, $blog_id = 1) {
1451 $transient_limit = 3600 * 48;
1452 $key = 'wpo_smush_cache_' . $blog_id . '_'. $key;
1453
1454 WP_Optimize_Transients_Cache::get_instance()->set_transient($key, $value, $transient_limit);
1455 }
1456
1457 /**
1458 * Gets value from the cache.
1459 *
1460 * @param string $key
1461 * @param int $blog_id
1462 * @return mixed
1463 */
1464 public function get_from_cache($key, $blog_id = 1) {
1465 $key = 'wpo_smush_cache_' . $blog_id . '_'. $key;
1466
1467 return WP_Optimize_Transients_Cache::get_instance()->get($key);
1468 }
1469
1470 /**
1471 * Deletes a value from the cache.
1472 *
1473 * @param string $key
1474 * @param int $blog_id
1475 */
1476 public function delete_from_cache($key, $blog_id = 1) {
1477 $key = 'wpo_smush_cache_' . $blog_id . '_'. $key;
1478
1479 WP_Optimize_Transients_Cache::get_instance()->delete($key);
1480
1481 $this->delete_transient($key);
1482 }
1483
1484 /**
1485 * Wrapper for deleting a transient
1486 *
1487 * @param string $key
1488 */
1489 public function delete_transient($key) {
1490 if ($this->is_multisite_mode()) {
1491 delete_site_transient($key);
1492 } else {
1493 delete_transient($key);
1494 }
1495 }
1496
1497 /**
1498 * Removes all cached data
1499 */
1500 public function clear_cached_data() {
1501 global $wpdb;
1502
1503 // get list of cached data by optimization.
1504 if ($this->is_multisite_mode()) {
1505 $keys = $wpdb->get_col("SELECT meta_key FROM `{$wpdb->sitemeta}` WHERE meta_key LIKE '%wpo_smush_cache_%'");
1506 } else {
1507 $keys = $wpdb->get_col("SELECT option_name FROM `{$wpdb->options}` WHERE option_name LIKE '%wpo_smush_cache_%'");
1508 }
1509
1510 if (!empty($keys)) {
1511 $transient_keys = array();
1512 foreach ($keys as $key) {
1513 preg_match('/wpo_smush_cache_.+/', $key, $option_name);
1514 $option_name = $option_name[0];
1515 $transient_keys[] = $option_name;
1516 }
1517
1518 // get unique keys.
1519 $transient_keys = array_unique($transient_keys);
1520
1521 // delete transients.
1522 foreach ($transient_keys as $key) {
1523 $this->delete_transient($key);
1524 }
1525 }
1526 }
1527
1528 /**
1529 * Delete recursively all smush backup files created more that $days_ago days.
1530 *
1531 * @param string $directory upload directory
1532 * @param int $days_ago
1533 */
1534 public function clear_backup_images_directory($directory, $days_ago = 30) {
1535
1536 $directory = trailingslashit($directory);
1537 $current_time = time();
1538
1539 if (preg_match('/(\d{4})\/(\d{2})\/$/', $directory, $match)) {
1540
1541 $check_date = false;
1542
1543 if ($days_ago > 0) {
1544 // check if it is end directory then scan for backup images.
1545 $year = (int) $match[1];
1546 $month = (int) $match[2];
1547
1548 $limit = strtotime('-'.$days_ago.' '.(($days_ago > 1) ? 'days' : 'day'));
1549 $year_limit = (int) gmdate('Y', $limit);
1550 $month_limit = (int) gmdate('m', $limit);
1551 $day_limit = (int) gmdate('j', $limit);
1552
1553 // if current directory is newer than needed then we skip it.
1554 if ($year_limit < $year || ($year_limit === $year && $month_limit < $month)) {
1555 return;
1556 }
1557
1558 // we will check dates only in directory that contain limit date.
1559 $check_date = ($year_limit === $year && $month_limit === $month);
1560 }
1561
1562 // GLOB_BRACE isn't defined on some systems (Solaris, SunOS and more) > https://www.php.net/manual/en/function.glob.php
1563 $files = glob($directory . '*-updraft-pre-smush-original.*', (defined('GLOB_BRACE') ? GLOB_BRACE : 0));
1564
1565 foreach ($files as $file) {
1566 if ($check_date) {
1567 $filedate_day = (int) gmdate('j', filectime($file));
1568 if ($filedate_day >= $day_limit) continue;
1569 }
1570
1571 wp_delete_file($file);
1572 }
1573
1574 } else {
1575 // scan directories recursively.
1576 $handle = opendir($directory);
1577
1578 if (false === $handle) return;
1579
1580 $file = readdir($handle);
1581
1582 while (false !== $file) {
1583
1584 if ('.' === $file || '..' === $file) {
1585 $file = readdir($handle);
1586 continue;
1587 }
1588
1589 if (is_dir($directory . $file)) {
1590 $this->clear_backup_images_directory($directory . $file, $days_ago);
1591 } elseif (is_file($directory . $file) && preg_match('/^.+-updraft-pre-smush-original\.\S{3,4}/i', $file)) {
1592 // check the file time and compare with $days_ago.
1593 $filedate_day = (int) filectime($directory . $file);
1594 if ($filedate_day > 0 && ($current_time - $filedate_day) / 86400 >= $days_ago) wp_delete_file($directory . $file);
1595 }
1596
1597 $file = readdir($handle);
1598 }
1599 }
1600
1601 }
1602
1603 /**
1604 * Clean backup smush images according to saved options.
1605 */
1606 public function clear_backup_images() {
1607 $back_up_delete_after = $this->options->get_option('back_up_delete_after');
1608
1609 if (!$back_up_delete_after) return;
1610
1611 $back_up_delete_after_days = $this->options->get_option('back_up_delete_after_days', 50);
1612
1613 $upload_dir = wp_upload_dir(null, false);
1614 $base_dir = $upload_dir['basedir'];
1615
1616 $this->clear_backup_images_directory($base_dir, $back_up_delete_after_days);
1617 }
1618
1619 /**
1620 * Check if attachment already compressed.
1621 *
1622 * @param int $attachment_id
1623 *
1624 * @return bool
1625 */
1626 public function is_compressed($attachment_id) {
1627 return (true === (bool) get_post_meta($attachment_id, 'smush-complete', true));
1628 }
1629
1630 /**
1631 * Returns true if multisite
1632 *
1633 * @return bool
1634 */
1635 public function is_multisite_mode() {
1636 return WP_Optimize()->is_multisite_mode();
1637 }
1638
1639 /**
1640 * This callback function is triggered due to delete_attachment action (wp-includes/post.php) and is executed prior to deletion of post-type attachment
1641 *
1642 * @param int $post_id - WordPress Post ID
1643 */
1644 public function unscheduled_original_file_deletion($post_id) {
1645 $the_original_file = get_post_meta($post_id, 'original-file', true);
1646 $uploads_dir = wp_get_upload_dir();
1647 $the_original_file = trailingslashit($uploads_dir['basedir']) . $the_original_file;
1648 if ('' != $the_original_file && file_exists($the_original_file)) {
1649 wp_delete_file($the_original_file);
1650 }
1651 }
1652
1653 /**
1654 * Remove failed smush tasks from the wp_tm_tasks table
1655 */
1656 public function clear_failed_tasks() {
1657 $failed_tasks = $this->get_tasks('failed', 'smush');
1658 if (empty($failed_tasks)) return;
1659
1660 foreach ($failed_tasks as $task) {
1661 $task->delete_meta();
1662 $task->delete();
1663 }
1664 }
1665
1666 /**
1667 * Instance of WP_Optimize_Page_Cache_Preloader.
1668 *
1669 * @return self
1670 */
1671 public static function instance() {
1672 if (empty(self::$_instance)) {
1673 self::$_instance = new self();
1674 }
1675
1676 return self::$_instance;
1677 }
1678
1679 /**
1680 * Meta query array for getting uncompressed images
1681 *
1682 * @return array
1683 */
1684 public function get_uncompressed_images_meta_query() {
1685 return array(
1686 'relation' => 'AND',
1687 array(
1688 'relation' => 'OR',
1689 array(
1690 'key' => 'smush-complete',
1691 'compare' => '!=',
1692 'value' => '1',
1693 ),
1694 array(
1695 'key' => 'smush-complete',
1696 'compare' => 'NOT EXISTS',
1697 'value' => '',
1698 ),
1699 ),
1700 // ShortPixel Image Optimizer plugin
1701 array(
1702 'relation' => 'OR',
1703 array(
1704 'key' => '_shortpixel_status',
1705 'compare' => '<',
1706 'value' => '2',
1707 ),
1708 array(
1709 'key' => '_shortpixel_status',
1710 'compare' => '>=',
1711 'value' => '3',
1712 ),
1713 array(
1714 'key' => '_shortpixel_status',
1715 'compare' => 'NOT EXISTS',
1716 'value' => '',
1717 ),
1718 ),
1719 // Smush plugin
1720 array(
1721 'key' => 'wp-smpro-smush-data',
1722 'compare' => 'NOT EXISTS',
1723 'value' => '',
1724 ),
1725 // Imagify
1726 array(
1727 'key' => '_imagify_optimization_level',
1728 'compare' => 'NOT EXISTS',
1729 'value' => '',
1730 ),
1731 // Compress JPEG & PNG images by TinyPNG
1732 array(
1733 'key' => 'tiny_compress_images',
1734 'compare' => 'NOT EXISTS',
1735 'value' => '',
1736 ),
1737 );
1738 }
1739 }
1740
1741 /**
1742 * Returns Updraft_Smush_Manager instance
1743 */
1744 function Updraft_Smush_Manager() {
1745 return Updraft_Smush_Manager::instance();
1746 }
1747
1748 endif;
1749