PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 3.4.0
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v3.4.0
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / includes / class-updraft-smush-manager.php

class-updraft-smush-manager.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 3.4.0, at includes/class-updraft-smush-manager.php

1,706 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 // load scripts and styles only on WP-Optimize pages or if show_smush_metabox option enabled.
1109 if (!preg_match('/wp\-optimize|attachment|upload/i', $current_screen->id) && 'show' != $this->options->get_option('show_smush_metabox', 'show')) return;
1110
1111 $enqueue_version = WP_Optimize()->get_enqueue_version();
1112 $min_or_not = WP_Optimize()->get_min_or_not_string();
1113 $min_or_not_internal = WP_Optimize()->get_min_or_not_internal_string();
1114
1115 $js_variables = $this->smush_js_translations();
1116 $js_variables['ajaxurl'] = admin_url('admin-ajax.php');
1117 $js_variables['features'] = $this->get_features();
1118
1119 $js_variables['smush_ajax_nonce'] = wp_create_nonce('updraft-task-manager-ajax-nonce');
1120 $js_variables['smush_settings'] = $this->get_smush_options();
1121 $js_variables['blog_id'] = get_current_blog_id();
1122 $js_variables['compress'] = esc_html__('Compress', 'wp-optimize');
1123 $js_variables['cancel'] = esc_html__('Cancel', 'wp-optimize');
1124 $js_variables['cancelling'] = esc_html__('Cancelling...', 'wp-optimize');
1125 $js_variables['images_restored_successfully'] = esc_html__('The images were restored successfully', 'wp-optimize');
1126
1127 wp_enqueue_script('block-ui-js', WPO_PLUGIN_URL.'includes/blockui/jquery.blockUI'.$min_or_not.'.js', array('jquery'), $enqueue_version);
1128 wp_enqueue_script('wp-optimize-heartbeat-js', WPO_PLUGIN_URL.'js/heartbeat'.$min_or_not_internal.'.js', array('jquery'), $enqueue_version);
1129 wp_localize_script('wp-optimize-heartbeat-js', 'wpo_heartbeat_ajax', array(
1130 'ajaxurl' => admin_url('admin-ajax.php'),
1131 'nonce' => wp_create_nonce('heartbeat-nonce'),
1132 'interval' => WPO_Ajax::HEARTBEAT_INTERVAL
1133 ));
1134 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);
1135 wp_enqueue_style('smush-css', WPO_PLUGIN_URL.'css/smush'.$min_or_not_internal.'.css', array(), $enqueue_version);
1136 wp_localize_script('smush-js', 'wposmush', $js_variables);
1137 }
1138
1139 /**
1140 * Gets default service provider for smush
1141 *
1142 * @return string - service name
1143 */
1144 public function get_default_webservice() {
1145 return 'resmushit';
1146 }
1147
1148 /**
1149 * Sets default options for smush
1150 */
1151 public function set_default_options() {
1152
1153 $options = array(
1154 'compression_server' => $this->get_default_webservice(),
1155 'image_quality' => 92,
1156 'lossy_compression' => false,
1157 'back_up_original' => true,
1158 'preserve_exif' => false,
1159 'autosmush' => false,
1160 'back_up_delete_after' => $this->options->get_option('back_up_delete_after', true),
1161 'back_up_delete_after_days' => $this->options->get_option('back_up_delete_after_days', 50),
1162 'webp_conversion' => false,
1163 );
1164
1165 $this->update_smush_options($options);
1166 }
1167
1168 /**
1169 * Gets default service provider for smush
1170 *
1171 * @param string $server - The name of the server
1172 * @return string - associated task type, default if none found
1173 */
1174 public function get_associated_task($server) {
1175 $allowed = $this->get_allowed_services();
1176
1177 if (key_exists($server, $allowed))
1178 return $allowed[$server];
1179
1180 $default = $this->get_default_webservice();
1181 return $allowed[$default];
1182 }
1183
1184 /**
1185 * Gets allowed service providers for smush
1186 *
1187 * @return array - key value pair of service name => task name
1188 */
1189 public function get_allowed_services() {
1190 return array(
1191 'resmushit' => 'Re_Smush_It_Task',
1192 );
1193 }
1194
1195 /**
1196 * Gets allowed service provider features smush
1197 *
1198 * @return array - key value pair of service name => features exposed
1199 */
1200 public function get_features() {
1201 $features = array();
1202 foreach ($this->get_allowed_services() as $service => $class_name) {
1203 $features[$service] = call_user_func(array($class_name, 'get_features'));
1204 }
1205 return $features;
1206 }
1207
1208 /**
1209 * Returns the path to the logfile
1210 *
1211 * @return string - file path
1212 */
1213 public function get_logfile_path() {
1214 return WP_Optimize_Utils::get_log_file_path('smush');
1215 }
1216
1217 /**
1218 * Delete all smush log files
1219 */
1220 public function delete_log_files() {
1221 if (!function_exists('glob')) return;
1222 $upload_dir = wp_get_upload_dir();
1223 $upload_base = $upload_dir['basedir'];
1224 $files = glob($upload_base . '/smush-*.log');
1225 if (false === $files) return;
1226 foreach ($files as $file) {
1227 if (is_file($file)) {
1228 @unlink($file); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress error due to file permission issues
1229 }
1230 }
1231
1232 }
1233
1234 /**
1235 * Adds a logger to the task
1236 *
1237 * @param Mixed $task - a task object
1238 */
1239 public function set_task_logger($task) {
1240 if (!$this->logger) {
1241 $this->logger = new Updraft_File_Logger($this->get_logfile_path());
1242 }
1243
1244 if (!$task->get_loggers()) {
1245 $task->add_logger($this->logger);
1246 }
1247 }
1248
1249 /**
1250 * Writes a standardised header to the log file
1251 */
1252 public function write_log_header() {
1253 global $wpdb;
1254
1255 // phpcs:disable
1256 $wp_version = $this->get_wordpress_version();
1257 $mysql_version = $wpdb->db_version();
1258 $disabled_functions = ini_get('disable_functions');
1259 $max_execution_time = (int) @ini_get("max_execution_time");
1260
1261 $memory_limit = ini_get('memory_limit');
1262 $memory_usage = round(@memory_get_usage(false)/1048576, 1);
1263 $total_memory_usage = round(@memory_get_usage(true)/1048576, 1);
1264
1265 // Attempt to raise limit
1266 @set_time_limit(330);
1267
1268 $log_header = array();
1269
1270 // phpcs:enable
1271 $log_header[] = "\n";
1272 $log_header[] = "Header for logs at time: ".date('r')." on ".network_site_url();
1273 $log_header[] = "WP: ".$wp_version;
1274 $php_uname = '';
1275 if (function_exists('php_uname')) {
1276 $php_uname = ", " . php_uname();
1277 }
1278 $log_header[] = "PHP: ".phpversion()." (".PHP_SAPI.$php_uname.")";
1279 $log_header[] = "MySQL: $mysql_version";
1280 $log_header[] = "WPLANG: ".get_locale();
1281 $log_header[] = "Server: ".$_SERVER["SERVER_SOFTWARE"];
1282 $log_header[] = "Outbound connections: ".(defined('WP_HTTP_BLOCK_EXTERNAL') ? 'Y' : 'N');
1283 $log_header[] = "Disabled Functions: $disabled_functions";
1284 $log_header[] = "max_execution_time: $max_execution_time";
1285 $log_header[] = "memory_limit: $memory_limit (used: {$memory_usage}M | {$total_memory_usage}M)";
1286 $log_header[] = "multisite: ".(is_multisite() ? 'Y' : 'N');
1287 $log_header[] = "openssl: ".(defined('OPENSSL_VERSION_TEXT') ? OPENSSL_VERSION_TEXT : 'N');
1288
1289 if (apply_filters("wpo_write_server_info_in_smush_log", false)) {
1290 foreach ($log_header as $log_entry) {
1291 $this->log($log_entry);
1292 }
1293 }
1294
1295 $memlim = $this->memory_check_current();
1296
1297 if ($memlim<65 && $memlim>0) {
1298 $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');
1299 }
1300
1301 if ($max_execution_time>0 && $max_execution_time<20) {
1302 $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');
1303 }
1304 }
1305
1306 /**
1307 * Prunes the log file
1308 */
1309 public function prune_smush_logs() {
1310 $this->log("Pruning the smush log file");
1311 $this->logger->prune_logs();
1312 }
1313
1314 /**
1315 * Get the WordPress version
1316 *
1317 * @return String - the version
1318 */
1319 public function get_wordpress_version() {
1320 static $got_wp_version = false;
1321
1322 if (!$got_wp_version) {
1323 global $wp_version;
1324 @include(ABSPATH.WPINC.'/version.php');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress warning if `version.php` does not exists
1325 $got_wp_version = $wp_version;
1326 }
1327
1328 return $got_wp_version;
1329 }
1330
1331 /**
1332 * Get the current memory limit
1333 *
1334 * @return String - memory limit in megabytes
1335 */
1336 public function memory_check_current($memory_limit = false) {
1337 // Returns in megabytes
1338 if (false == $memory_limit) $memory_limit = ini_get('memory_limit');
1339 $memory_limit = rtrim($memory_limit);
1340 $memory_unit = $memory_limit[strlen($memory_limit)-1];
1341 if (0 == (int) $memory_unit && '0' !== $memory_unit) {
1342 $memory_limit = substr($memory_limit, 0, strlen($memory_limit)-1);
1343 } else {
1344 $memory_unit = '';
1345 }
1346 switch ($memory_unit) {
1347 case '':
1348 $memory_limit = floor($memory_limit/1048576);
1349 break;
1350 case 'K':
1351 case 'k':
1352 $memory_limit = floor($memory_limit/1024);
1353 break;
1354 case 'G':
1355 $memory_limit = $memory_limit*1024;
1356 break;
1357 case 'M':
1358 // assumed size, no change needed
1359 break;
1360 }
1361 return $memory_limit;
1362 }
1363
1364 /**
1365 * Saves a value to the cache.
1366 *
1367 * @param string $key
1368 * @param mixed $value
1369 * @param int $blog_id
1370 */
1371 public function save_to_cache($key, $value, $blog_id = 1) {
1372 $transient_limit = 3600 * 48;
1373 $key = 'wpo_smush_cache_' . $blog_id . '_'. $key;
1374
1375 WP_Optimize_Transients_Cache::get_instance()->set_transient($key, $value, $transient_limit);
1376 }
1377
1378 /**
1379 * Gets value from the cache.
1380 *
1381 * @param string $key
1382 * @param int $blog_id
1383 * @return mixed
1384 */
1385 public function get_from_cache($key, $blog_id = 1) {
1386 $key = 'wpo_smush_cache_' . $blog_id . '_'. $key;
1387
1388 return WP_Optimize_Transients_Cache::get_instance()->get($key);
1389 }
1390
1391 /**
1392 * Deletes a value from the cache.
1393 *
1394 * @param string $key
1395 * @param int $blog_id
1396 */
1397 public function delete_from_cache($key, $blog_id = 1) {
1398 $key = 'wpo_smush_cache_' . $blog_id . '_'. $key;
1399
1400 WP_Optimize_Transients_Cache::get_instance()->delete($key);
1401
1402 $this->delete_transient($key);
1403 }
1404
1405 /**
1406 * Wrapper for deleting a transient
1407 *
1408 * @param string $key
1409 */
1410 public function delete_transient($key) {
1411 if ($this->is_multisite_mode()) {
1412 delete_site_transient($key);
1413 } else {
1414 delete_transient($key);
1415 }
1416 }
1417
1418 /**
1419 * Removes all cached data
1420 */
1421 public function clear_cached_data() {
1422 global $wpdb;
1423
1424 // get list of cached data by optimization.
1425 if ($this->is_multisite_mode()) {
1426 $keys = $wpdb->get_col("SELECT meta_key FROM {$wpdb->sitemeta} WHERE meta_key LIKE '%wpo_smush_cache_%'");
1427 } else {
1428 $keys = $wpdb->get_col("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '%wpo_smush_cache_%'");
1429 }
1430
1431 if (!empty($keys)) {
1432 $transient_keys = array();
1433 foreach ($keys as $key) {
1434 preg_match('/wpo_smush_cache_.+/', $key, $option_name);
1435 $option_name = $option_name[0];
1436 $transient_keys[] = $option_name;
1437 }
1438
1439 // get unique keys.
1440 $transient_keys = array_unique($transient_keys);
1441
1442 // delete transients.
1443 foreach ($transient_keys as $key) {
1444 $this->delete_transient($key);
1445 }
1446 }
1447 }
1448
1449 /**
1450 * Delete recursively all smush backup files created more that $days_ago days.
1451 *
1452 * @param string $directory upload directory
1453 * @param int $days_ago
1454 */
1455 public function clear_backup_images_directory($directory, $days_ago = 30) {
1456
1457 $directory = trailingslashit($directory);
1458 $current_time = time();
1459
1460 if (preg_match('/(\d{4})\/(\d{2})\/$/', $directory, $match)) {
1461
1462 $check_date = false;
1463
1464 if ($days_ago > 0) {
1465 // check if it is end directory then scan for backup images.
1466 $year = (int) $match[1];
1467 $month = (int) $match[2];
1468
1469 $limit = strtotime('-'.$days_ago.' '.(($days_ago > 1) ? 'days' : 'day'));
1470 $year_limit = (int) date('Y', $limit);
1471 $month_limit = (int) date('m', $limit);
1472 $day_limit = (int) date('j', $limit);
1473
1474 // if current directory is newer than needed then we skip it.
1475 if ($year_limit < $year || ($year_limit == $year && $month_limit < $month)) {
1476 return;
1477 }
1478
1479 // we will check dates only in directory that contain limit date.
1480 $check_date = ($year_limit == $year && $month_limit == $month);
1481 }
1482
1483 // GLOB_BRACE isn't defined on some systems (Solaris, SunOS and more) > https://www.php.net/manual/en/function.glob.php
1484 $files = glob($directory . '*-updraft-pre-smush-original.*', (defined('GLOB_BRACE') ? GLOB_BRACE : 0));
1485
1486 foreach ($files as $file) {
1487 if ($check_date) {
1488 $filedate_day = (int) date('j', filectime($file));
1489 if ($filedate_day >= $day_limit) continue;
1490 }
1491
1492 unlink($file);
1493 }
1494
1495 } else {
1496 // scan directories recursively.
1497 $handle = opendir($directory);
1498
1499 if (false === $handle) return;
1500
1501 $file = readdir($handle);
1502
1503 while (false !== $file) {
1504
1505 if ('.' == $file || '..' == $file) {
1506 $file = readdir($handle);
1507 continue;
1508 }
1509
1510 if (is_dir($directory . $file)) {
1511 $this->clear_backup_images_directory($directory . $file, $days_ago);
1512 } elseif (is_file($directory . $file) && preg_match('/^.+-updraft-pre-smush-original\.\S{3,4}/i', $file)) {
1513 // check the file time and compare with $days_ago.
1514 $filedate_day = (int) filectime($directory . $file);
1515 if ($filedate_day > 0 && ($current_time - $filedate_day) / 86400 >= $days_ago) unlink($directory . $file);
1516 }
1517
1518 $file = readdir($handle);
1519 }
1520 }
1521
1522 }
1523
1524 /**
1525 * Clean backup smush images according to saved options.
1526 */
1527 public function clear_backup_images() {
1528 $back_up_delete_after = $this->options->get_option('back_up_delete_after', false);
1529
1530 if (!$back_up_delete_after) return;
1531
1532 $back_up_delete_after_days = $this->options->get_option('back_up_delete_after_days', 50);
1533
1534 $upload_dir = wp_upload_dir(null, false);
1535 $base_dir = $upload_dir['basedir'];
1536
1537 $this->clear_backup_images_directory($base_dir, $back_up_delete_after_days);
1538 }
1539
1540 /**
1541 * Check if attachment already compressed.
1542 *
1543 * @param int $attachment_id
1544 *
1545 * @return bool
1546 */
1547 public function is_compressed($attachment_id) {
1548 return (true == get_post_meta($attachment_id, 'smush-complete', true));
1549 }
1550
1551 /**
1552 * @param array $form_fields
1553 * @param WP_Post $post
1554 *
1555 * @return array
1556 */
1557 public function add_compress_button_to_media_modal($form_fields, $post) {
1558
1559 if (!is_admin() || !function_exists('get_current_screen')) return $form_fields;
1560
1561 /**
1562 * In media modal get_current_screen() return null or id = 'async-upload' We don't need add smush fields elsewhere.
1563 */
1564 $current_screen = get_current_screen();
1565 if (null !== $current_screen && 'async-upload' != $current_screen->id) return $form_fields;
1566
1567 /**
1568 * Don't show additional fields for non-image attachments.
1569 */
1570 if (!wp_attachment_is_image($post->ID)) return $form_fields;
1571
1572 ob_start();
1573 $this->render_smush_metabox($post);
1574 $smush_metabox = ob_get_contents();
1575 ob_end_clean();
1576
1577 $form_fields['wpo_compress_image'] = array(
1578 'value' => '',
1579 'label' => __('Compress image', 'wp-optimize'),
1580 'input' => 'html',
1581 'html' => $smush_metabox,
1582 );
1583
1584 return $form_fields;
1585 }
1586
1587 /**
1588 * Returns true if multisite
1589 *
1590 * @return bool
1591 */
1592 public function is_multisite_mode() {
1593 return WP_Optimize()->is_multisite_mode();
1594 }
1595
1596 /**
1597 * This callback function is triggered due to delete_attachment action (wp-includes/post.php) and is executed prior to deletion of post-type attachment
1598 *
1599 * @param int $post_id - WordPress Post ID
1600 */
1601 public function unscheduled_original_file_deletion($post_id) {
1602 $the_original_file = get_post_meta($post_id, 'original-file', true);
1603 $uploads_dir = wp_get_upload_dir();
1604 $the_original_file = trailingslashit($uploads_dir['basedir']) . $the_original_file;
1605 if ('' != $the_original_file && file_exists($the_original_file)) {
1606 @unlink($the_original_file);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress warning because of file permission issues
1607 }
1608 }
1609
1610 /**
1611 * Remove failed smush tasks from the wp_tm_tasks table
1612 */
1613 public function clear_failed_tasks() {
1614 $failed_tasks = $this->get_tasks('failed', 'smush');
1615 if (empty($failed_tasks)) return;
1616
1617 foreach ($failed_tasks as $task) {
1618 $task->delete_meta();
1619 $task->delete();
1620 }
1621 }
1622
1623 /**
1624 * Instance of WP_Optimize_Page_Cache_Preloader.
1625 *
1626 * @return self
1627 */
1628 public static function instance() {
1629 if (empty(self::$_instance)) {
1630 self::$_instance = new self();
1631 }
1632
1633 return self::$_instance;
1634 }
1635
1636 /**
1637 * Meta query array for getting uncompressed images
1638 *
1639 * @return array
1640 */
1641 public function get_uncompressed_images_meta_query() {
1642 return array(
1643 'relation' => 'AND',
1644 array(
1645 'relation' => 'OR',
1646 array(
1647 'key' => 'smush-complete',
1648 'compare' => '!=',
1649 'value' => '1',
1650 ),
1651 array(
1652 'key' => 'smush-complete',
1653 'compare' => 'NOT EXISTS',
1654 'value' => '',
1655 ),
1656 ),
1657 // ShortPixel Image Optimizer plugin
1658 array(
1659 'relation' => 'OR',
1660 array(
1661 'key' => '_shortpixel_status',
1662 'compare' => '<',
1663 'value' => '2',
1664 ),
1665 array(
1666 'key' => '_shortpixel_status',
1667 'compare' => '>=',
1668 'value' => '3',
1669 ),
1670 array(
1671 'key' => '_shortpixel_status',
1672 'compare' => 'NOT EXISTS',
1673 'value' => '',
1674 ),
1675 ),
1676 // Smush plugin
1677 array(
1678 'key' => 'wp-smpro-smush-data',
1679 'compare' => 'NOT EXISTS',
1680 'value' => '',
1681 ),
1682 // Imagify
1683 array(
1684 'key' => '_imagify_optimization_level',
1685 'compare' => 'NOT EXISTS',
1686 'value' => '',
1687 ),
1688 // Compress JPEG & PNG images by TinyPNG
1689 array(
1690 'key' => 'tiny_compress_images',
1691 'compare' => 'NOT EXISTS',
1692 'value' => '',
1693 ),
1694 );
1695 }
1696 }
1697
1698 /**
1699 * Returns a Updraft_Smush_Manager instance
1700 */
1701 function Updraft_Smush_Manager() {
1702 return Updraft_Smush_Manager::instance();
1703 }
1704
1705 endif;
1706