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

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