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

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