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

449 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 $destination = WPO_WebP_Utils::get_destination_path($file_path);
157 if ($webp_tools_available && file_exists($destination)) {
158 if (is_multisite()) {
159 switch_to_blog($this->get_option('blog_id', 1));
160 update_post_meta($attachment_id, 'wpo-webp-conversion-complete', true);
161 restore_current_blog();
162 } else {
163 update_post_meta($attachment_id, 'wpo-webp-conversion-complete', true);
164 }
165 }
166
167 return $this->success;
168 }
169
170 /**
171 * Posts the supplied data to the API url and returns a response
172 *
173 * @param string $api_endpoint - the url to post the form to
174 * @param array $post_data - the post data as specified by the server
175 * @return array|false|WP_Error - the response
176 */
177 public function post_to_remote_server($api_endpoint, $post_data) {
178
179 $this->set_current_stage('connecting');
180 $response = wp_remote_post($api_endpoint, $post_data);
181
182 if (is_wp_error($response)) {
183 $this->fail($response->get_error_code(), $response->get_error_message());
184 return false;
185 }
186
187 return $response;
188 }
189
190 /**
191 * Processes the response received from the remote server
192 *
193 * @param mixed $response - the response object
194 * @return mixed - the response
195 */
196 public function process_server_response($response) {
197 $this->set_current_stage('processing_response');
198 return $response;
199 }
200
201 /**
202 * Checks if a file is valid and capable of being smushed
203 *
204 * @param string $file_path - the path of the original image
205 * @return bool - true on success, false otherwise
206 */
207 public function validate_file($file_path) {
208
209 $allowed_file_types = $this->get_option('allowed_file_types');
210
211 if (!file_exists($file_path)) {
212 $this->fail("invalid_file_path", "The linked attachment ID does not have a valid file path");
213 return false;
214 }
215
216 if (filesize($file_path) > $this->get_option('max_filesize')) {
217 $this->fail("exceeded_max_filesize", "$file_path - cannot be optimized, file size is above service provider limit");
218 return false;
219 }
220
221 if (!in_array(strtolower(pathinfo($file_path, PATHINFO_EXTENSION)), $allowed_file_types)) {
222 $this->fail("invalid_file_type", "$file_path - cannot be optimized, it has an invalid file type");
223 return false;
224 }
225
226 return true;
227 }
228
229 /**
230 * Creates a backup of the original image
231 *
232 * @param string $file_path - the path of the original image
233 * @return bool - true on success, false otherwise
234 */
235 public function backup_original_image($file_path) {
236
237 $this->set_current_stage('backup_original');
238
239 if (is_multisite()) {
240 switch_to_blog($this->get_option('blog_id', 1));
241 }
242
243 $file = pathinfo($file_path);
244 $back_up = wp_normalize_path($file['dirname'].'/'.basename($file['filename'].$this->get_option('backup_prefix').$file['extension']));
245 $uploads_dir = wp_upload_dir();
246
247 // Make path relative and safe for migrations
248 $back_up_relative_path = preg_replace('#^'.wp_normalize_path($uploads_dir['basedir'].'/').'#', '', $back_up);
249
250 update_post_meta($this->get_option('attachment_id'), 'original-file', $back_up_relative_path);
251
252 if (is_multisite()) {
253 restore_current_blog();
254 }
255
256 $this->log("Backing up the original image - $back_up_relative_path");
257
258 return copy($file_path, $back_up);
259 }
260
261 /**
262 * Creates a backup of the original image
263 *
264 * @param string $file_path - the path of the original image
265 * @param mixed $optimised_image - the contents of the image
266 * @param bool $backup_original - backup original image
267 *
268 * @return bool - true on success, false otherwise
269 */
270 private function save_optimised_image($file_path, $optimised_image, $backup_original) {
271
272 $this->set_current_stage('saving_image');
273
274 if ($backup_original)
275 $this->backup_original_image($file_path);
276
277 if (false !== file_put_contents($file_path, $optimised_image)) {
278 $this->success = true;
279 } else {
280 $this->success = false;
281 }
282
283 return $this->success;
284 }
285
286 /**
287 * Fires if the task succeeds, any clean up code and logging goes here
288 */
289 public function complete() {
290
291 $attachment_id = $this->get_option('attachment_id');
292
293 if (is_multisite()) {
294 switch_to_blog($this->get_option('blog_id', 1));
295 $file_path = get_attached_file($attachment_id);
296 restore_current_blog();
297 } else {
298 $file_path = get_attached_file($attachment_id);
299 }
300
301 $original_size = (int) $this->get_option('original_filesize');
302 $this->set_current_stage('completed');
303
304 clearstatcache(true, $file_path);
305 if (0 === $original_size) {
306 $saved = '';
307 // translators: %s is a file size
308 $info = sprintf(__("The file was compressed to %s using WP-Optimize", 'wp-optimize'), WP_Optimize()->format_size(filesize($file_path)));
309 } else {
310 $saved = round((($original_size - filesize($file_path)) / $original_size * 100), 2);
311 // translators: %1$s is a file size, %2$s is a file size, %3$s is a percentage
312 $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);
313 }
314
315 $stats = array(
316 'smushed-with' => $this->label,
317 'original-size' => $original_size,
318 'smushed-size' => filesize($file_path),
319 'savings-percent' => $saved,
320 'sizes-info' => $this->get_option('smush-sizes-info'),
321 );
322
323 if (is_multisite()) {
324 switch_to_blog($this->get_option('blog_id', 1));
325 update_post_meta($attachment_id, 'smush-complete', true);
326 update_post_meta($attachment_id, 'smush-info', $info);
327 update_post_meta($attachment_id, 'smush-stats', $stats);
328 restore_current_blog();
329 } else {
330 update_post_meta($attachment_id, 'smush-complete', true);
331 update_post_meta($attachment_id, 'smush-info', $info);
332 update_post_meta($attachment_id, 'smush-stats', $stats);
333 }
334
335 $this->log("Successfully optimized the image - $file_path." . $info);
336 $this->set_status('complete');
337
338 return parent::complete();
339 }
340
341 /**
342 * Fires if the task fails, any clean up code and logging goes here
343 *
344 * @param string $error_code - A code for the failure
345 * @param string $error_message - A description for the failure
346 */
347 public function fail($error_code = "Unknown", $error_message = "Unknown") {
348
349 $attachment_id = $this->get_option('attachment_id');
350
351 // translators: %1$s is the error code, %2$s is the error message
352 $info = sprintf(__('Failed with error code %1$s - %2$s', 'wp-optimize'), $error_code, $error_message);
353
354 if (is_multisite()) {
355 switch_to_blog($this->get_option('blog_id', 1));
356 update_post_meta($attachment_id, 'smush-info', $info);
357 update_post_meta($attachment_id, 'smush-complete', false);
358 restore_current_blog();
359 } else {
360 update_post_meta($attachment_id, 'smush-info', $info);
361 update_post_meta($attachment_id, 'smush-complete', false);
362 }
363
364
365 do_action('ud_smush_task_failed', $this, $error_code, $error_message);
366
367 return parent::fail($error_code, $error_message);
368 }
369
370 /**
371 * Get all the supported task stages.
372 *
373 * @return array - list of task stages.
374 */
375 public function get_allowed_stages() {
376
377 $stages = array(
378 'initialised' => __('Initialised', 'wp-optimize'),
379 'connecting' => __('Connecting to API server', 'wp-optimize'),
380 'processing_response' => __('Processing response', 'wp-optimize'),
381 'backup_original' => __('Backing up original image', 'wp-optimize'),
382 'saving_image' => __('Saving optimized image', 'wp-optimize'),
383 'completed' => __('Successful', 'wp-optimize'),
384 );
385
386 $filtered_stages = apply_filters('allowed_task_stages', $stages);
387 if (!empty($filtered_stages) && is_array($filtered_stages)) return $filtered_stages;
388 return $stages;
389 }
390
391 /**
392 * Retrieve default options for this task.
393 * This method should normally be over-ridden by the child.
394 *
395 * @return array - an array of options
396 */
397 public function get_default_options() {
398
399 return array(
400 'allowed_file_types' => WPO_Image_Utils::get_allowed_extensions(),
401 'request_timeout' => 15,
402 'image_quality' => 92,
403 'backup_prefix' => '-updraft-pre-smush-original.'
404 );
405 }
406
407 /**
408 * Sets the task stage.
409 *
410 * @param string $stage - the current stage of the task
411 * @return bool - the result of the update
412 */
413 public function set_current_stage($stage) {
414
415 if (array_key_exists($stage, self::get_allowed_stages())) {
416 $this->stage = $stage;
417 return (bool) $this->update_option('current_stage', $this->stage);
418 }
419
420 return false;
421 }
422
423 /**
424 * Gets the task stage
425 *
426 * @return string $stage - the current stage of the task
427 */
428 public function get_current_stage() {
429 return $this->stage ?? $this->get_option('current_stage');
430 }
431
432 /**
433 * Check the mime type of downloaded file, returns true if it is a valid image mime type.
434 *
435 * @param string $file_buffer The buffer string downloaded from the compression service
436 * @return boolean
437 */
438 protected function is_downloaded_image_buffer_mime_type_valid($file_buffer) {
439 // If the required class does not exist, return true to avoid breaking the functionality
440 if (!class_exists('finfo')) return true;
441 $accepted_types = apply_filters('wpo_image_compression_accepted_mime_types', array('image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/webp'));
442 // The ignore rule below is added because "finfo" doesn't exist in PHP5.2.
443 $finfo = new finfo(FILEINFO_MIME_TYPE);
444 $mime_type = $finfo->buffer($file_buffer);
445 return in_array($mime_type, $accepted_types);
446 }
447 }
448 endif;
449