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

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