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

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