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

1,651 lines 53.1 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 'webp_conversion_tool_error' => __('No WebP conversion tools are available on your web-server.', 'wp-optimize'),
745 'webp_conversion_tool_how_to' => __('How to get the WebP conversion tools to work?', 'wp-optimize'),
746 'more' => __('More', 'wp-optimize'),
747 'less' => __('Less', 'wp-optimize'),
748 'converting_to_webp' => __('Converting image to WebP format, please wait', 'wp-optimize'),
749 ));
750 }
751
752 /**
753 * Adds a smush metabox on the post edit screen for images
754 *
755 * @param WP_Post $post - a post object
756 */
757 public function add_smush_metabox($post) {
758
759 if (!wp_attachment_is_image($post->ID)) return;
760
761 if (!file_exists(get_attached_file($post->ID))) {
762 return;
763 }
764
765 add_meta_box('smush-metabox', __('Compress Image', 'wp-optimize'), array($this, 'render_smush_metabox'), 'attachment', 'side');
766 }
767
768 /**
769 * Renders a metabox on the post edit screen for images
770 *
771 * @param WP_Post $post - a post object
772 */
773 public function render_smush_metabox($post) {
774
775 $compressed = get_post_meta($post->ID, 'smush-complete', true) ? true : false;
776 $has_backup = get_post_meta($post->ID, 'original-file', true) ? true : false;
777
778 $smush_info = get_post_meta($post->ID, 'smush-info', true);
779 $smush_stats = get_post_meta($post->ID, 'smush-stats', true);
780 $marked = get_post_meta($post->ID, 'smush-marked', false);
781
782 $options = Updraft_Smush_Manager()->get_smush_options();
783
784 $file = get_attached_file($post->ID);
785 $ext = WPO_Image_Utils::get_extension($file);
786 $allowed_extensions = WPO_Image_Utils::get_allowed_extensions();
787 $file_size = ($file && is_file($file)) ? filesize($file) : 0;
788
789 $extract = array(
790 'post_id' => $post->ID,
791 'smush_display' => $compressed ? "style='display:none;'" : "style='display:block;'",
792 'restore_display' => $compressed ? "style='display:block;'" : "style='display:none;'",
793 'restore_action' => $has_backup ? "style='display:block;'" : "style='display:none;'",
794 'smush_mark' => !$compressed && !$marked ? "style='display:block;'" : "style='display:none;'",
795 'smush_unmark' => $marked ? "style='display:block;'" : "style='display:none;'",
796 'smush_info' => $smush_info ? $smush_info : ' ',
797 'file_size' => $file_size,
798 'smush_options' => $options,
799 'custom' => 90 >= $options['image_quality'] && 65 <= $options['image_quality'],
800 'smush_details' => '',
801 );
802
803 if (!empty($smush_stats['sizes-info'])) {
804 $extract['smush_details'] = WP_Optimize()->include_template('images/smush-details.php', true, array('sizes_info' => $smush_stats['sizes-info']));
805 }
806
807 $extract['compressed_by_another_plugin'] = $this->is_image_compressed_by_another_plugin($post->ID);
808 if (WPO_Image_Utils::is_supported_extension($ext, $allowed_extensions)) {
809 WP_Optimize()->include_template('admin-metabox-smush.php', false, $extract);
810 } else {
811 printf("<p>%s</p>", __('Compressing this file type extension is not supported', 'wp-optimize'));
812 }
813 }
814
815 /**
816 * Check if a single image compressed by another plugin.
817 *
818 * @param int $image_id
819 * @return bool
820 */
821 private function is_image_compressed_by_another_plugin($image_id) {
822 global $wpdb;
823
824 $meta = $wpdb->get_results("SELECT meta_key, meta_value FROM {$wpdb->postmeta} WHERE `post_id`={$image_id}", ARRAY_A);
825
826 if (is_array($meta)) {
827 foreach ($meta as $row) {
828 // Smush, Imagify, Compress JPEG & PNG images by TinyPNG.
829 if (in_array($row['meta_key'], array('wp-smpro-smush-data', '_imagify_optimization_level', 'tiny_compress_images'))) return true;
830 // ShortPixel Image Optimizer
831 if ('_shortpixel_status' == $row['meta_key'] && 2 <= $row['meta_key'] && 3 > $row['meta_key']) return true;
832 }
833 }
834
835 if (WP_Optimize()->get_db_info()->table_exists('ewwwio_images')) {
836 $old_show_errors = $wpdb->show_errors(false);
837 // EWWW Image Optimizer.
838 $ewww_image = $wpdb->get_col("SELECT attachment_id FROM {$wpdb->prefix}ewwwio_images WHERE attachment_id={$image_id} AND gallery='media' LIMIT 1");
839 if (!empty($ewww_image)) return true;
840 $wpdb->show_errors($old_show_errors);
841 }
842
843 return apply_filters('wpo_image_compressed_by_another_plugin', false);
844 }
845
846 /**
847 * Get attachment ids for images those already compressed with EWWW Image Optimizer.
848 * Used with filter `wpo_get_uncompressed_images_args`.
849 *
850 * @param array $args WP_Query arguments.
851 * @return array
852 */
853 public function ewww_image_optimizer_compressed_images_args($args) {
854 global $wpdb;
855
856 if (!WP_Optimize()->get_db_info()->table_exists('ewwwio_images')) return $args;
857
858 $old_show_errors = $wpdb->show_errors(false);
859 $compressed_images = $wpdb->get_col("SELECT DISTINCT(attachment_id) FROM {$wpdb->prefix}ewwwio_images WHERE gallery='media'");
860 $wpdb->show_errors($old_show_errors);
861
862 if (isset($args['post__not_in'])) {
863 $args['post__not_in'] = array_merge($args['post__not_in'], $compressed_images);
864 } else {
865 $args['post__not_in'] = $compressed_images;
866 }
867
868 return $args;
869 }
870
871 /**
872 * Returns a list of images for smush (from cache if available)
873 *
874 * @return array - uncompressed images
875 */
876 public function get_uncompressed_images() {
877
878 $uncompressed_images = $this->get_from_cache('uncompressed_images');
879
880 if ($uncompressed_images) return $uncompressed_images;
881
882 $uncompressed_images = array();
883 $accepted_mimes = array('image/jpeg', 'image/gif', 'image/png');
884
885 $args = array(
886 'post_type' => 'attachment',
887 'post_mime_type' => $accepted_mimes,
888 'post_status' => 'inherit',
889 'posts_per_page' => apply_filters('updraft_smush_posts_per_page', 1000),
890 'meta_query' => array(
891 'relation' => 'AND',
892 array(
893 'relation' => 'OR',
894 array(
895 'key' => 'smush-complete',
896 'compare' => '!=',
897 'value' => '1',
898 ),
899 array(
900 'key' => 'smush-complete',
901 'compare' => 'NOT EXISTS',
902 'value' => '',
903 ),
904 ),
905 // ShortPixel Image Optimizer plugin
906 array(
907 'relation' => 'OR',
908 array(
909 'key' => '_shortpixel_status',
910 'compare' => '<',
911 'value' => '2',
912 ),
913 array(
914 'key' => '_shortpixel_status',
915 'compare' => '>=',
916 'value' => '3',
917 ),
918 array(
919 'key' => '_shortpixel_status',
920 'compare' => 'NOT EXISTS',
921 'value' => '',
922 ),
923 ),
924 // Smush plugin
925 array(
926 'key' => 'wp-smpro-smush-data',
927 'compare' => 'NOT EXISTS',
928 'value' => '',
929 ),
930 // Imagify
931 array(
932 'key' => '_imagify_optimization_level',
933 'compare' => 'NOT EXISTS',
934 'value' => '',
935 ),
936 // Compress JPEG & PNG images by TinyPNG
937 array(
938 'key' => 'tiny_compress_images',
939 'compare' => 'NOT EXISTS',
940 'value' => '',
941 ),
942 )
943 );
944
945 $allowed_extensions = WPO_Image_Utils::get_allowed_extensions();
946
947 if (is_multisite()) {
948
949 $sites = WP_Optimize()->get_sites();
950
951 foreach ($sites as $site) {
952
953 switch_to_blog($site->blog_id);
954
955 $args = apply_filters('wpo_get_uncompressed_images_args', $args);
956 $images = new WP_Query($args);
957
958 foreach ($images->posts as $image) {
959 $file = get_attached_file($image->ID);
960 $ext = WPO_Image_Utils::get_extension($file);
961 if (file_exists($file)) {
962 if (WPO_Image_Utils::is_supported_extension($ext, $allowed_extensions)) {
963 $uncompressed_images[$site->blog_id][] = array(
964 'id' => $image->ID,
965 'thumb_url' => wp_get_attachment_thumb_url($image->ID),
966 'filesize' => filesize(get_attached_file($image->ID))
967 );
968 } else {
969 $this->log("Blog_id={$site->blog_id}, ID={$image->ID}, File={$file} This image type is not supported.");
970 }
971 } else {
972 $this->log("Could not find file for image: blog_id={$site->blog_id}, ID={$image->ID}, file={$file}");
973 }
974 }
975
976 restore_current_blog();
977 }
978
979 } else {
980 $args = apply_filters('wpo_get_uncompressed_images_args', $args);
981 $images = new WP_Query($args);
982 foreach ($images->posts as $image) {
983 $file = get_attached_file($image->ID);
984 $ext = WPO_Image_Utils::get_extension($file);
985
986 if (file_exists($file)) {
987 if (WPO_Image_Utils::is_supported_extension($ext, $allowed_extensions)) {
988 $uncompressed_images[1][] = array(
989 'id' => $image->ID,
990 'thumb_url' => wp_get_attachment_thumb_url($image->ID),
991 'filesize' => filesize(get_attached_file($image->ID))
992 );
993 } else {
994 $this->log("Image ID={$image->ID}, File={$file} This image type is not supported.");
995 }
996 } else {
997 $this->log("Could not find file for image: ID={$image->ID}, file={$file}");
998 }
999 }
1000 }
1001
1002 $this->save_to_cache('uncompressed_images', $uncompressed_images);
1003 return $uncompressed_images;
1004 }
1005
1006 /**
1007 * 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)
1008 *
1009 * @return array - list of admin URLs
1010 */
1011 public function get_admin_urls() {
1012
1013 $admin_urls = $this->get_from_cache('admin_urls');
1014
1015 if ($admin_urls) return $admin_urls;
1016
1017 $admin_urls = array();
1018
1019 if (is_multisite()) {
1020
1021 $sites = WP_Optimize()->get_sites();
1022
1023 foreach ($sites as $site) {
1024 switch_to_blog($site->blog_id);
1025 $admin_urls[$site->blog_id] = admin_url();
1026 restore_current_blog();
1027 }
1028
1029 } else {
1030 // The pseudo-blog_id here (1) matches (and must match) what is used in get_uncompressed_images
1031 $admin_urls[1] = admin_url();
1032 }
1033
1034 $this->save_to_cache('admin_urls', $admin_urls);
1035 return $admin_urls;
1036 }
1037
1038 /**
1039 * Check if a task exists for a given image
1040 *
1041 * @param string $image - The attachment ID of the image
1042 * @return bool - true if yes, false otherwise
1043 */
1044 public function task_exists($image) {
1045
1046 $blog_id = get_current_blog_id();
1047 $pending_tasks = $this->get_active_tasks('smush');
1048
1049 if (!empty($pending_tasks)) {
1050 foreach ($pending_tasks as $task) {
1051 $task_attachment_id = $task->get_option('attachment_id');
1052 $task_blog_id = $task->get_option('blog_id');
1053
1054 if ($image === $task_attachment_id && $blog_id === $task_blog_id) {
1055 return true;
1056 }
1057 }
1058 }
1059 return false;
1060 }
1061
1062 /**
1063 * Returns the status of images compressed in this iteration of the bulk compress
1064 *
1065 * @param array $images - List of images in the current session
1066 *
1067 * @return array - status of the operation
1068 */
1069 public function get_session_stats($images) {
1070 $stats = array();
1071
1072 foreach ($images as $image) {
1073 if (is_multisite()) {
1074 switch_to_blog($image['blog_id'], 1);
1075 $stats[] = get_post_meta($image['attachment_id'], 'smush-complete', true) ? 'success' : 'fail';
1076 restore_current_blog();
1077 } else {
1078 $stats[] = get_post_meta($image['attachment_id'], 'smush-complete', true) ? 'success' : 'fail';
1079 }
1080 }
1081
1082 return array_count_values($stats);
1083 }
1084
1085 /**
1086 * Returns a list of images for smush (from cache if available)
1087 *
1088 * @return array - List of task objects with uncompressed images
1089 */
1090 public function get_pending_tasks() {
1091 return $this->get_active_tasks('smush');
1092 }
1093
1094 /**
1095 * Deletes and removes any pending tasks from queue
1096 */
1097 public function clear_pending_images() {
1098
1099 $pending_tasks = $this->get_active_tasks('smush');
1100
1101 foreach ($pending_tasks as $task) {
1102 $task->delete_meta();
1103 $task->delete();
1104 }
1105
1106 return true;
1107 }
1108
1109
1110 /**
1111 * Returns a count of failed tasks
1112 *
1113 * @return int - failed tasks
1114 */
1115 public function get_failed_task_count() {
1116 return $this->options->get_option('failed_task_count', 0);
1117 }
1118
1119 /**
1120 * Adds the required scripts and styles
1121 */
1122 public function admin_enqueue_scripts() {
1123 $current_screen = get_current_screen();
1124 // load scripts and styles only on WP-Optimize pages or if show_smush_metabox option enabled.
1125 if (!preg_match('/wp\-optimize|attachment|upload/i', $current_screen->id) && 'show' != $this->options->get_option('show_smush_metabox', 'show')) return;
1126
1127 $enqueue_version = WP_Optimize()->get_enqueue_version();
1128 $min_or_not = WP_Optimize()->get_min_or_not_string();
1129 $min_or_not_internal = WP_Optimize()->get_min_or_not_internal_string();
1130
1131 $js_variables = $this->smush_js_translations();
1132 $js_variables['ajaxurl'] = admin_url('admin-ajax.php');
1133 $js_variables['features'] = $this->get_features();
1134
1135 $js_variables['smush_ajax_nonce'] = wp_create_nonce('updraft-task-manager-ajax-nonce');
1136
1137 wp_enqueue_script('block-ui-js', WPO_PLUGIN_URL.'includes/blockui/jquery.blockUI'.$min_or_not.'.js', array('jquery'), $enqueue_version);
1138 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);
1139 wp_enqueue_style('smush-css', WPO_PLUGIN_URL.'css/smush'.$min_or_not_internal.'.css', array(), $enqueue_version);
1140 wp_localize_script('smush-js', 'wposmush', $js_variables);
1141 }
1142
1143 /**
1144 * Gets default service provider for smush
1145 *
1146 * @return string - service name
1147 */
1148 public function get_default_webservice() {
1149 return 'resmushit';
1150 }
1151
1152 /**
1153 * Sets default options for smush
1154 */
1155 public function set_default_options() {
1156
1157 $options = array(
1158 'compression_server' => $this->get_default_webservice(),
1159 'image_quality' => 92,
1160 'lossy_compression' => false,
1161 'back_up_original' => true,
1162 'preserve_exif' => false,
1163 'autosmush' => false,
1164 'back_up_delete_after' => $this->options->get_option('back_up_delete_after', true),
1165 'back_up_delete_after_days' => $this->options->get_option('back_up_delete_after_days', 50),
1166 'webp_conversion' => false,
1167 );
1168
1169 $this->update_smush_options($options);
1170 }
1171
1172 /**
1173 * Gets default service provider for smush
1174 *
1175 * @param string $server - The name of the server
1176 * @return string - associated task type, default if none found
1177 */
1178 public function get_associated_task($server) {
1179 $allowed = $this->get_allowed_services();
1180
1181 if (key_exists($server, $allowed))
1182 return $allowed[$server];
1183
1184 $default = $this->get_default_webservice();
1185 return $allowed[$default];
1186 }
1187
1188 /**
1189 * Gets allowed service providers for smush
1190 *
1191 * @return array - key value pair of service name => task name
1192 */
1193 public function get_allowed_services() {
1194 return array(
1195 'resmushit' => 'Re_Smush_It_Task',
1196 );
1197 }
1198
1199 /**
1200 * Gets allowed service provider features smush
1201 *
1202 * @return array - key value pair of service name => features exposed
1203 */
1204 public function get_features() {
1205 $features = array();
1206 foreach ($this->get_allowed_services() as $service => $class_name) {
1207 $features[$service] = call_user_func(array($class_name, 'get_features'));
1208 }
1209 return $features;
1210 }
1211
1212 /**
1213 * Returns the path to the logfile
1214 *
1215 * @return string - file path
1216 */
1217 public function get_logfile_path() {
1218 return WP_Optimize_Utils::get_log_file_path('smush');
1219 }
1220
1221 /**
1222 * Delete all smush log files
1223 */
1224 public function delete_log_files() {
1225 if (!function_exists('glob')) return;
1226 $upload_dir = wp_get_upload_dir();
1227 $upload_base = $upload_dir['basedir'];
1228 $files = glob($upload_base . '/smush-*.log');
1229 if (false === $files) return;
1230 foreach ($files as $file) {
1231 if (is_file($file)) {
1232 @unlink($file); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress error due to file permission issues
1233 }
1234 }
1235
1236 }
1237
1238 /**
1239 * Adds a logger to the task
1240 *
1241 * @param Mixed $task - a task object
1242 */
1243 public function set_task_logger($task) {
1244 if (!$this->logger) {
1245 $this->logger = new Updraft_File_Logger($this->get_logfile_path());
1246 }
1247
1248 if (!$task->get_loggers()) {
1249 $task->add_logger($this->logger);
1250 }
1251 }
1252
1253 /**
1254 * Writes a standardised header to the log file
1255 */
1256 public function write_log_header() {
1257 global $wpdb;
1258
1259 // phpcs:disable
1260 $wp_version = $this->get_wordpress_version();
1261 $mysql_version = $wpdb->db_version();
1262 $disabled_functions = ini_get('disable_functions');
1263 $max_execution_time = (int) @ini_get("max_execution_time");
1264
1265 $memory_limit = ini_get('memory_limit');
1266 $memory_usage = round(@memory_get_usage(false)/1048576, 1);
1267 $total_memory_usage = round(@memory_get_usage(true)/1048576, 1);
1268
1269 // Attempt to raise limit
1270 @set_time_limit(330);
1271
1272 $log_header = array();
1273
1274 // phpcs:enable
1275 $log_header[] = "\n";
1276 $log_header[] = "Header for logs at time: ".date('r')." on ".network_site_url();
1277 $log_header[] = "WP: ".$wp_version;
1278 $php_uname = '';
1279 if (function_exists('php_uname')) {
1280 $php_uname = ", " . php_uname();
1281 }
1282 $log_header[] = "PHP: ".phpversion()." (".PHP_SAPI.$php_uname.")";
1283 $log_header[] = "MySQL: $mysql_version";
1284 $log_header[] = "WPLANG: ".get_locale();
1285 $log_header[] = "Server: ".$_SERVER["SERVER_SOFTWARE"];
1286 $log_header[] = "Outbound connections: ".(defined('WP_HTTP_BLOCK_EXTERNAL') ? 'Y' : 'N');
1287 $log_header[] = "Disabled Functions: $disabled_functions";
1288 $log_header[] = "max_execution_time: $max_execution_time";
1289 $log_header[] = "memory_limit: $memory_limit (used: {$memory_usage}M | {$total_memory_usage}M)";
1290 $log_header[] = "multisite: ".(is_multisite() ? 'Y' : 'N');
1291 $log_header[] = "openssl: ".(defined('OPENSSL_VERSION_TEXT') ? OPENSSL_VERSION_TEXT : 'N');
1292
1293 if (apply_filters("wpo_write_server_info_in_smush_log", false)) {
1294 foreach ($log_header as $log_entry) {
1295 $this->log($log_entry);
1296 }
1297 }
1298
1299 $memlim = $this->memory_check_current();
1300
1301 if ($memlim<65 && $memlim>0) {
1302 $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');
1303 }
1304
1305 if ($max_execution_time>0 && $max_execution_time<20) {
1306 $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');
1307 }
1308 }
1309
1310 /**
1311 * Prunes the log file
1312 */
1313 public function prune_smush_logs() {
1314 $this->log("Pruning the smush log file");
1315 $this->logger->prune_logs();
1316 }
1317
1318 /**
1319 * Get the WordPress version
1320 *
1321 * @return String - the version
1322 */
1323 public function get_wordpress_version() {
1324 static $got_wp_version = false;
1325
1326 if (!$got_wp_version) {
1327 global $wp_version;
1328 @include(ABSPATH.WPINC.'/version.php');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress warning if `version.php` does not exists
1329 $got_wp_version = $wp_version;
1330 }
1331
1332 return $got_wp_version;
1333 }
1334
1335 /**
1336 * Get the current memory limit
1337 *
1338 * @return String - memory limit in megabytes
1339 */
1340 public function memory_check_current($memory_limit = false) {
1341 // Returns in megabytes
1342 if (false == $memory_limit) $memory_limit = ini_get('memory_limit');
1343 $memory_limit = rtrim($memory_limit);
1344 $memory_unit = $memory_limit[strlen($memory_limit)-1];
1345 if (0 == (int) $memory_unit && '0' !== $memory_unit) {
1346 $memory_limit = substr($memory_limit, 0, strlen($memory_limit)-1);
1347 } else {
1348 $memory_unit = '';
1349 }
1350 switch ($memory_unit) {
1351 case '':
1352 $memory_limit = floor($memory_limit/1048576);
1353 break;
1354 case 'K':
1355 case 'k':
1356 $memory_limit = floor($memory_limit/1024);
1357 break;
1358 case 'G':
1359 $memory_limit = $memory_limit*1024;
1360 break;
1361 case 'M':
1362 // assumed size, no change needed
1363 break;
1364 }
1365 return $memory_limit;
1366 }
1367
1368 /**
1369 * Saves a value to the cache.
1370 *
1371 * @param string $key
1372 * @param mixed $value
1373 * @param int $blog_id
1374 */
1375 public function save_to_cache($key, $value, $blog_id = 1) {
1376 $transient_limit = 3600 * 48;
1377 $key = 'wpo_smush_cache_' . $blog_id . '_'. $key;
1378
1379 return WP_Optimize_Transients_Cache::get_instance()->set($key, $value, $transient_limit);
1380 }
1381
1382 /**
1383 * Gets value from the cache.
1384 *
1385 * @param string $key
1386 * @param int $blog_id
1387 * @return mixed
1388 */
1389 public function get_from_cache($key, $blog_id = 1) {
1390 $key = 'wpo_smush_cache_' . $blog_id . '_'. $key;
1391
1392 $value = WP_Optimize_Transients_Cache::get_instance()->get($key);
1393
1394 return $value;
1395 }
1396
1397 /**
1398 * Deletes a value from the cache.
1399 *
1400 * @param string $key
1401 * @param int $blog_id
1402 */
1403 public function delete_from_cache($key, $blog_id = 1) {
1404 $key = 'wpo_smush_cache_' . $blog_id . '_'. $key;
1405
1406 WP_Optimize_Transients_Cache::get_instance()->delete($key);
1407
1408 $this->delete_transient($key);
1409 }
1410
1411 /**
1412 * Wrapper for deleting a transient
1413 *
1414 * @param string $key
1415 */
1416 public function delete_transient($key) {
1417 if ($this->is_multisite_mode()) {
1418 delete_site_transient($key);
1419 } else {
1420 delete_transient($key);
1421 }
1422 }
1423
1424 /**
1425 * Removes all cached data
1426 */
1427 public function clear_cached_data() {
1428 global $wpdb;
1429
1430 // get list of cached data by optimization.
1431 if ($this->is_multisite_mode()) {
1432 $keys = $wpdb->get_col("SELECT meta_key FROM {$wpdb->sitemeta} WHERE meta_key LIKE '%wpo_smush_cache_%'");
1433 } else {
1434 $keys = $wpdb->get_col("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '%wpo_smush_cache_%'");
1435 }
1436
1437 if (!empty($keys)) {
1438 $transient_keys = array();
1439 foreach ($keys as $key) {
1440 preg_match('/wpo_smush_cache_.+/', $key, $option_name);
1441 $option_name = $option_name[0];
1442 $transient_keys[] = $option_name;
1443 }
1444
1445 // get unique keys.
1446 $transient_keys = array_unique($transient_keys);
1447
1448 // delete transients.
1449 foreach ($transient_keys as $key) {
1450 $this->delete_transient($key);
1451 }
1452 }
1453 }
1454
1455 /**
1456 * Delete recursively all smush backup files created more that $days_ago days.
1457 *
1458 * @param string $directory upload directory
1459 * @param int $days_ago
1460 */
1461 public function clear_backup_images_directory($directory, $days_ago = 30) {
1462
1463 $directory = trailingslashit($directory);
1464 $current_time = time();
1465
1466 if (preg_match('/(\d{4})\/(\d{2})\/$/', $directory, $match)) {
1467
1468 $check_date = false;
1469
1470 if ($days_ago > 0) {
1471 // check if it is end directory then scan for backup images.
1472 $year = (int) $match[1];
1473 $month = (int) $match[2];
1474
1475 $limit = strtotime('-'.$days_ago.' '.(($days_ago > 1) ? 'days' : 'day'));
1476 $year_limit = (int) date('Y', $limit);
1477 $month_limit = (int) date('m', $limit);
1478 $day_limit = (int) date('j', $limit);
1479
1480 // if current directory is newer than needed then we skip it.
1481 if ($year_limit < $year || ($year_limit == $year && $month_limit < $month)) {
1482 return;
1483 }
1484
1485 // we will check dates only in directory that contain limit date.
1486 $check_date = ($year_limit == $year && $month_limit == $month);
1487 }
1488
1489 // GLOB_BRACE isn't defined on some systems (Solaris, SunOS and more) > https://www.php.net/manual/en/function.glob.php
1490 $files = glob($directory . '*-updraft-pre-smush-original.*', (defined('GLOB_BRACE') ? GLOB_BRACE : 0));
1491
1492 foreach ($files as $file) {
1493 if ($check_date) {
1494 $filedate_day = (int) date('j', filectime($file));
1495 if ($filedate_day >= $day_limit) continue;
1496 }
1497
1498 unlink($file);
1499 }
1500
1501 } else {
1502 // scan directories recursively.
1503 $handle = opendir($directory);
1504
1505 if (false === $handle) return;
1506
1507 $file = readdir($handle);
1508
1509 while (false !== $file) {
1510
1511 if ('.' == $file || '..' == $file) {
1512 $file = readdir($handle);
1513 continue;
1514 }
1515
1516 if (is_dir($directory . $file)) {
1517 $this->clear_backup_images_directory($directory . $file, $days_ago);
1518 } elseif (is_file($directory . $file) && preg_match('/^.+-updraft-pre-smush-original\.\S{3,4}/i', $file)) {
1519 // check the file time and compare with $days_ago.
1520 $filedate_day = (int) filectime($directory . $file);
1521 if ($filedate_day > 0 && ($current_time - $filedate_day) / 86400 >= $days_ago) unlink($directory . $file);
1522 }
1523
1524 $file = readdir($handle);
1525 }
1526 }
1527
1528 }
1529
1530 /**
1531 * Clean backup smush images according to saved options.
1532 */
1533 public function clear_backup_images() {
1534 $back_up_delete_after = $this->options->get_option('back_up_delete_after', false);
1535
1536 if (!$back_up_delete_after) return;
1537
1538 $back_up_delete_after_days = $this->options->get_option('back_up_delete_after_days', 50);
1539
1540 $upload_dir = wp_upload_dir(null, false);
1541 $base_dir = $upload_dir['basedir'];
1542
1543 $this->clear_backup_images_directory($base_dir, $back_up_delete_after_days);
1544 }
1545
1546 /**
1547 * Check if attachment already compressed.
1548 *
1549 * @param int $attachment_id
1550 *
1551 * @return bool
1552 */
1553 public function is_compressed($attachment_id) {
1554 return (true == get_post_meta($attachment_id, 'smush-complete', true));
1555 }
1556
1557 /**
1558 * @param array $form_fields
1559 * @param WP_Post $post
1560 *
1561 * @return array
1562 */
1563 public function add_compress_button_to_media_modal($form_fields, $post) {
1564
1565 if (!is_admin() || !function_exists('get_current_screen')) return $form_fields;
1566
1567 /**
1568 * In media modal get_current_screen() return null or id = 'async-upload' We don't need add smush fields elsewhere.
1569 */
1570 $current_screen = get_current_screen();
1571 if (null !== $current_screen && 'async-upload' != $current_screen->id) return $form_fields;
1572
1573 /**
1574 * Don't show additional fields for non-image attachments.
1575 */
1576 if (!wp_attachment_is_image($post->ID)) return $form_fields;
1577
1578 ob_start();
1579 $this->render_smush_metabox($post);
1580 $smush_metabox = ob_get_contents();
1581 ob_end_clean();
1582
1583 $form_fields['wpo_compress_image'] = array(
1584 'value' => '',
1585 'label' => __('Compress image', 'wp-optimize'),
1586 'input' => 'html',
1587 'html' => $smush_metabox,
1588 );
1589
1590 return $form_fields;
1591 }
1592
1593 /**
1594 * Returns true if multisite
1595 *
1596 * @return bool
1597 */
1598 public function is_multisite_mode() {
1599 return WP_Optimize()->is_multisite_mode();
1600 }
1601
1602 /**
1603 * This callback function is triggered due to delete_attachment action (wp-includes/post.php) and is executed prior to deletion of post-type attachment
1604 *
1605 * @param int $post_id - WordPress Post ID
1606 */
1607 public function unscheduled_original_file_deletion($post_id) {
1608 $the_original_file = get_post_meta($post_id, 'original-file', true);
1609 $uploads_dir = wp_get_upload_dir();
1610 $the_original_file = trailingslashit($uploads_dir['basedir']) . $the_original_file;
1611 if ('' != $the_original_file && file_exists($the_original_file)) {
1612 @unlink($the_original_file);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress warning because of file permission issues
1613 }
1614 }
1615
1616 /**
1617 * Remove failed smush tasks from the wp_tm_tasks table
1618 */
1619 public function clear_failed_tasks() {
1620 $failed_tasks = $this->get_tasks('failed', 'smush');
1621 if (empty($failed_tasks)) return;
1622
1623 foreach ($failed_tasks as $task) {
1624 $task->delete_meta();
1625 $task->delete();
1626 }
1627 }
1628
1629 /**
1630 * Instance of WP_Optimize_Page_Cache_Preloader.
1631 *
1632 * @return self
1633 */
1634 public static function instance() {
1635 if (empty(self::$_instance)) {
1636 self::$_instance = new self();
1637 }
1638
1639 return self::$_instance;
1640 }
1641 }
1642
1643 /**
1644 * Returns a Updraft_Smush_Manager instance
1645 */
1646 function Updraft_Smush_Manager() {
1647 return Updraft_Smush_Manager::instance();
1648 }
1649
1650 endif;
1651