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

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