PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 4.5.3
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v4.5.3
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-task.php

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

450 lines 13.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * A sample implementation using the Resmush.it API and our tasks library
4 */
5
6 if (!defined('ABSPATH')) die('Access denied.');
7
8 if (!class_exists('Updraft_Task_1_2')) require_once(WPO_PLUGIN_MAIN_PATH . 'vendor/team-updraft/common-libs/src/updraft-tasks/class-updraft-task.php');
9
10 if (!class_exists('Updraft_Smush_Task')) :
11
12 abstract class Updraft_Smush_Task extends Updraft_Task_1_2 {
13
14 /**
15 * A flag indicating if the operation was successful
16 *
17 * @var bool
18 */
19 protected $success = false;
20
21 /**
22 * A text descriptor describing the stage of the task
23 *
24 * @var string
25 */
26 protected $stage;
27
28 /**
29 * Initialise the task
30 *
31 * @param array $options - options to use
32 */
33 public function initialise($options = array()) {
34 parent::initialise($options);
35 $this->set_current_stage('initialised');
36 do_action('ud_task_initialised', $this);
37 }
38
39 /**
40 * Runs the task
41 *
42 * @return bool - true if complete, false otherwise
43 */
44 public function run() {
45
46 $this->set_status('active');
47
48 do_action('ud_task_started', $this);
49
50 $attachment_id = $this->get_option('attachment_id');
51
52 if (is_multisite()) {
53 switch_to_blog($this->get_option('blog_id', 1));
54 $file_path = get_attached_file($attachment_id);
55 restore_current_blog();
56 } else {
57 $file_path = get_attached_file($attachment_id);
58 }
59
60 if (!$this->validate_file($file_path)) return false;
61
62 $api_endpoint = $this->get_option('api_endpoint');
63
64 if (false === filter_var($api_endpoint, FILTER_VALIDATE_URL)) {
65 $this->fail('invalid_api_url', "The API endpoint supplied $api_endpoint is invalid");
66 return false;
67 }
68
69 $original_image = $file_path;
70 $backup_original_image = $this->get_option('keep_original', true);
71
72 // add possibility to exclude certain image sizes from smush.
73 $dont_smush_sizes = apply_filters('wpo_dont_smush_sizes', array());
74
75 $this->update_option('original_filesize', filesize($file_path));
76
77 // build list of files for smush.
78 if (is_multisite()) {
79 switch_to_blog($this->get_option('blog_id', 1));
80 $files = array_merge(array('full' => $file_path), WPO_Image_Utils::get_attachment_files($attachment_id));
81 restore_current_blog();
82 } else {
83 $files = array_merge(array('full' => $file_path), WPO_Image_Utils::get_attachment_files($attachment_id));
84 }
85
86 $sizes_info = array();
87 $webp_tools_available = true;
88 $lossy = $this->get_option('lossy_compression');
89 if ($lossy) {
90 $quality = $this->get_option('image_quality');
91 } else {
92 $quality = 92;
93 }
94 $this->log($this->get_description());
95 $this->log("File: " . basename($file_path) . ", Compression quality: $quality");
96
97 foreach ($files as $size => $file_path) {
98
99 if (in_array($size, $dont_smush_sizes)) continue;
100
101 $file_size = filesize($file_path);
102
103 if ($file_size > 5242880) {
104 $this->update_option('request_timeout', 180);
105 }
106
107 $ext = WPO_Image_Utils::get_extension($file_path);
108 $allowed_extensions = WPO_Image_Utils::get_allowed_extensions();
109 $allowed_extensions = array_diff($allowed_extensions, array('gif'));
110 if (WPO_WebP_Utils::can_do_webp_conversion()) {
111 if (WPO_Image_Utils::is_supported_extension($ext, $allowed_extensions)) {
112 WPO_WebP_Utils::do_webp_conversion($file_path);
113 }
114 } else {
115 $webp_tools_available = false;
116 }
117
118
119 /**
120 * Filters the options for a single image to compress.
121 * Currently, supports:
122 * - 'quality': Will use the image quality set in this filter, instead of the one defined in the settings.
123 *
124 * @param array $options - The options (default: empty array)
125 * @param integer $attachment_id - The attachment post ID
126 * @param string $file_path - The path to the file being compressed
127 * @param string $size - The size name (e.g. 'thumbnail')
128 */
129 $options = apply_filters('wpo_image_compression_single_image_options', array(), $attachment_id, $file_path, $size);
130
131 $post_data = $this->prepare_post_request($file_path, $options);
132
133 $response = $this->post_to_remote_server($api_endpoint, $post_data);
134 $optimised_image = $this->process_server_response($response);
135
136 if ($optimised_image) {
137 $backup_image = ($original_image === $file_path) ? $backup_original_image : false;
138 $this->save_optimised_image($file_path, $optimised_image, $backup_image);
139
140 clearstatcache($file_path);
141
142 $sizes_info[$size] = array(
143 'original' => $file_size,
144 'compressed' => filesize($file_path),
145 );
146 }
147
148 }
149
150 $this->update_option('smush-sizes-info', $sizes_info);
151
152 if (!$webp_tools_available) {
153 $this->log('There were no WebP conversion tools found on your server.');
154 }
155
156 $webp_converter = new WPO_WebP_Convert();
157 $destination = $webp_converter->get_destination_path($file_path);
158 if ($webp_tools_available && file_exists($destination)) {
159 if (is_multisite()) {
160 switch_to_blog($this->get_option('blog_id', 1));
161 update_post_meta($attachment_id, 'wpo-webp-conversion-complete', true);
162 restore_current_blog();
163 } else {
164 update_post_meta($attachment_id, 'wpo-webp-conversion-complete', true);
165 }
166 }
167
168 return $this->success;
169 }
170
171 /**
172 * Posts the supplied data to the API url and returns a response
173 *
174 * @param string $api_endpoint - the url to post the form to
175 * @param array $post_data - the post data as specified by the server
176 * @return array|false|WP_Error - the response
177 */
178 public function post_to_remote_server($api_endpoint, $post_data) {
179
180 $this->set_current_stage('connecting');
181 $response = wp_remote_post($api_endpoint, $post_data);
182
183 if (is_wp_error($response)) {
184 $this->fail($response->get_error_code(), $response->get_error_message());
185 return false;
186 }
187
188 return $response;
189 }
190
191 /**
192 * Processes the response received from the remote server
193 *
194 * @param mixed $response - the response object
195 * @return mixed - the response
196 */
197 public function process_server_response($response) {
198 $this->set_current_stage('processing_response');
199 return $response;
200 }
201
202 /**
203 * Checks if a file is valid and capable of being smushed
204 *
205 * @param string $file_path - the path of the original image
206 * @return bool - true on success, false otherwise
207 */
208 public function validate_file($file_path) {
209
210 $allowed_file_types = $this->get_option('allowed_file_types');
211
212 if (!file_exists($file_path)) {
213 $this->fail("invalid_file_path", "The linked attachment ID does not have a valid file path");
214 return false;
215 }
216
217 if (filesize($file_path) > $this->get_option('max_filesize')) {
218 $this->fail("exceeded_max_filesize", "$file_path - cannot be optimized, file size is above service provider limit");
219 return false;
220 }
221
222 if (!in_array(strtolower(pathinfo($file_path, PATHINFO_EXTENSION)), $allowed_file_types)) {
223 $this->fail("invalid_file_type", "$file_path - cannot be optimized, it has an invalid file type");
224 return false;
225 }
226
227 return true;
228 }
229
230 /**
231 * Creates a backup of the original image
232 *
233 * @param string $file_path - the path of the original image
234 * @return bool - true on success, false otherwise
235 */
236 public function backup_original_image($file_path) {
237
238 $this->set_current_stage('backup_original');
239
240 if (is_multisite()) {
241 switch_to_blog($this->get_option('blog_id', 1));
242 }
243
244 $file = pathinfo($file_path);
245 $back_up = wp_normalize_path($file['dirname'].'/'.basename($file['filename'].$this->get_option('backup_prefix').$file['extension']));
246 $uploads_dir = wp_upload_dir();
247
248 // Make path relative and safe for migrations
249 $back_up_relative_path = preg_replace('#^'.wp_normalize_path($uploads_dir['basedir'].'/').'#', '', $back_up);
250
251 update_post_meta($this->get_option('attachment_id'), 'original-file', $back_up_relative_path);
252
253 if (is_multisite()) {
254 restore_current_blog();
255 }
256
257 $this->log("Backing up the original image - $back_up_relative_path");
258
259 return copy($file_path, $back_up);
260 }
261
262 /**
263 * Creates a backup of the original image
264 *
265 * @param string $file_path - the path of the original image
266 * @param mixed $optimised_image - the contents of the image
267 * @param bool $backup_original - backup original image
268 *
269 * @return bool - true on success, false otherwise
270 */
271 private function save_optimised_image($file_path, $optimised_image, $backup_original) {
272
273 $this->set_current_stage('saving_image');
274
275 if ($backup_original)
276 $this->backup_original_image($file_path);
277
278 if (false !== file_put_contents($file_path, $optimised_image)) {
279 $this->success = true;
280 } else {
281 $this->success = false;
282 }
283
284 return $this->success;
285 }
286
287 /**
288 * Fires if the task succeeds, any clean up code and logging goes here
289 */
290 public function complete() {
291
292 $attachment_id = $this->get_option('attachment_id');
293
294 if (is_multisite()) {
295 switch_to_blog($this->get_option('blog_id', 1));
296 $file_path = get_attached_file($attachment_id);
297 restore_current_blog();
298 } else {
299 $file_path = get_attached_file($attachment_id);
300 }
301
302 $original_size = (int) $this->get_option('original_filesize');
303 $this->set_current_stage('completed');
304
305 clearstatcache(true, $file_path);
306 if (0 === $original_size) {
307 $saved = '';
308 // translators: %s is a file size
309 $info = sprintf(__("The file was compressed to %s using WP-Optimize", 'wp-optimize'), WP_Optimize()->format_size(filesize($file_path)));
310 } else {
311 $saved = round((($original_size - filesize($file_path)) / $original_size * 100), 2);
312 // translators: %1$s is a file size, %2$s is a file size, %3$s is a percentage
313 $info = sprintf(__('The file was compressed from %1$s to %2$s, saving %3$s percent, using WP-Optimize', 'wp-optimize'), WP_Optimize()->format_size($original_size), WP_Optimize()->format_size(filesize($file_path)), $saved);
314 }
315
316 $stats = array(
317 'smushed-with' => $this->label,
318 'original-size' => $original_size,
319 'smushed-size' => filesize($file_path),
320 'savings-percent' => $saved,
321 'sizes-info' => $this->get_option('smush-sizes-info'),
322 );
323
324 if (is_multisite()) {
325 switch_to_blog($this->get_option('blog_id', 1));
326 update_post_meta($attachment_id, 'smush-complete', true);
327 update_post_meta($attachment_id, 'smush-info', $info);
328 update_post_meta($attachment_id, 'smush-stats', $stats);
329 restore_current_blog();
330 } else {
331 update_post_meta($attachment_id, 'smush-complete', true);
332 update_post_meta($attachment_id, 'smush-info', $info);
333 update_post_meta($attachment_id, 'smush-stats', $stats);
334 }
335
336 $this->log("Successfully optimized the image - $file_path." . $info);
337 $this->set_status('complete');
338
339 return parent::complete();
340 }
341
342 /**
343 * Fires if the task fails, any clean up code and logging goes here
344 *
345 * @param string $error_code - A code for the failure
346 * @param string $error_message - A description for the failure
347 */
348 public function fail($error_code = "Unknown", $error_message = "Unknown") {
349
350 $attachment_id = $this->get_option('attachment_id');
351
352 // translators: %1$s is the error code, %2$s is the error message
353 $info = sprintf(__('Failed with error code %1$s - %2$s', 'wp-optimize'), $error_code, $error_message);
354
355 if (is_multisite()) {
356 switch_to_blog($this->get_option('blog_id', 1));
357 update_post_meta($attachment_id, 'smush-info', $info);
358 update_post_meta($attachment_id, 'smush-complete', false);
359 restore_current_blog();
360 } else {
361 update_post_meta($attachment_id, 'smush-info', $info);
362 update_post_meta($attachment_id, 'smush-complete', false);
363 }
364
365
366 do_action('ud_smush_task_failed', $this, $error_code, $error_message);
367
368 return parent::fail($error_code, $error_message);
369 }
370
371 /**
372 * Get all the supported task stages.
373 *
374 * @return array - list of task stages.
375 */
376 public function get_allowed_stages() {
377
378 $stages = array(
379 'initialised' => __('Initialised', 'wp-optimize'),
380 'connecting' => __('Connecting to API server', 'wp-optimize'),
381 'processing_response' => __('Processing response', 'wp-optimize'),
382 'backup_original' => __('Backing up original image', 'wp-optimize'),
383 'saving_image' => __('Saving optimized image', 'wp-optimize'),
384 'completed' => __('Successful', 'wp-optimize'),
385 );
386
387 $filtered_stages = apply_filters('allowed_task_stages', $stages);
388 if (!empty($filtered_stages) && is_array($filtered_stages)) return $filtered_stages;
389 return $stages;
390 }
391
392 /**
393 * Retrieve default options for this task.
394 * This method should normally be over-ridden by the child.
395 *
396 * @return array - an array of options
397 */
398 public function get_default_options() {
399
400 return array(
401 'allowed_file_types' => WPO_Image_Utils::get_allowed_extensions(),
402 'request_timeout' => 15,
403 'image_quality' => 92,
404 'backup_prefix' => '-updraft-pre-smush-original.'
405 );
406 }
407
408 /**
409 * Sets the task stage.
410 *
411 * @param string $stage - the current stage of the task
412 * @return bool - the result of the update
413 */
414 public function set_current_stage($stage) {
415
416 if (array_key_exists($stage, self::get_allowed_stages())) {
417 $this->stage = $stage;
418 return (bool) $this->update_option('current_stage', $this->stage);
419 }
420
421 return false;
422 }
423
424 /**
425 * Gets the task stage
426 *
427 * @return string $stage - the current stage of the task
428 */
429 public function get_current_stage() {
430 return $this->stage ?? $this->get_option('current_stage');
431 }
432
433 /**
434 * Check the mime type of downloaded file, returns true if it is a valid image mime type.
435 *
436 * @param string $file_buffer The buffer string downloaded from the compression service
437 * @return boolean
438 */
439 protected function is_downloaded_image_buffer_mime_type_valid($file_buffer) {
440 // If the required class does not exist, return true to avoid breaking the functionality
441 if (!class_exists('finfo')) return true;
442 $accepted_types = apply_filters('wpo_image_compression_accepted_mime_types', array('image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/webp'));
443 // The ignore rule below is added because "finfo" doesn't exist in PHP5.2.
444 $finfo = new finfo(FILEINFO_MIME_TYPE);
445 $mime_type = $finfo->buffer($file_buffer);
446 return in_array($mime_type, $accepted_types);
447 }
448 }
449 endif;
450