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

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