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

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