PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 4.5.4
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v4.5.4
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.4, at includes/class-updraft-smush-manager.php

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