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