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

470 lines 14.2 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 succesful
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 $files = array_merge(array('full' => $file_path), $this->get_attachment_files($attachment_id));
79
80 $sizes_info = array();
81
82 foreach ($files as $size => $file_path) {
83
84 if (in_array($size, $dont_smush_sizes)) continue;
85
86 $file_size = filesize($file_path);
87
88 if ($file_size > 5242880) {
89 $this->update_option('request_timeout', 180);
90 }
91
92 $this->log($this->get_description());
93
94 if (defined('WPO_USE_WEBP_CONVERSION') && true === WPO_USE_WEBP_CONVERSION) {
95 $this->maybe_do_webp_conversion($file_path);
96 }
97
98 /**
99 * Filters the options for a single image to compress.
100 * Currently supports:
101 * - 'quality': Will use the image quality set in this filter, instead of the one defined in the settings.
102 *
103 * @param array $options - The options (default: empty array)
104 * @param integer $attachment_id - The attachment post ID
105 * @param string $file_path - The path to the file being compressed
106 * @param string $size - The size name (e.g. 'thumbnail')
107 */
108 $options = apply_filters('wpo_image_compression_single_image_options', array(), $attachment_id, $file_path, $size);
109
110 $post_data = $this->prepare_post_request($file_path, $options);
111
112 $response = $this->post_to_remote_server($api_endpoint, $post_data);
113 $optimised_image = $this->process_server_response($response);
114
115 if ($optimised_image) {
116 $backup_image = ($original_image == $file_path) ? $backup_original_image : false;
117 $this->save_optimised_image($file_path, $optimised_image, $backup_image);
118
119 clearstatcache($file_path);
120
121 $sizes_info[$size] = array(
122 'original' => $file_size,
123 'compressed' => filesize($file_path),
124 );
125 }
126
127 }
128
129 $this->update_option('smush-sizes-info', $sizes_info);
130
131 return $this->success;
132 }
133
134 /**
135 * Converts to WebP format, if possible
136 *
137 * @param string $source Source image file path
138 */
139 public function maybe_do_webp_conversion($source) {
140 $webp_conversion = WP_Optimize()->get_options()->get_option('webp_conversion', false);
141 if (!empty($webp_conversion)) {
142 if (!class_exists('WPO_WebP_Convert')) include_once(WPO_PLUGIN_MAIN_PATH . 'webp/class-wpo-webp-convert.php');
143 $webp_converter = new WPO_WebP_Convert();
144 $webp_converter->convert($source);
145 } else {
146 $this->log('There were no WebP conversion tools found on your server.');
147 }
148 }
149
150 /**
151 * Posts the supplied data to the API url and returns a response
152 *
153 * @param String $api_endpoint - the url to post the form to
154 * @param String $post_data - the post data as specified by the server
155 * @return mixed - the response
156 */
157 public function post_to_remote_server($api_endpoint, $post_data) {
158
159 $this->set_current_stage('connecting');
160 $response = wp_remote_post($api_endpoint, $post_data);
161
162 if (is_wp_error($response)) {
163 $this->fail($response->get_error_code(), $response->get_error_message());
164 return false;
165 }
166
167 return $response;
168 }
169
170 /**
171 * Processes the response recieved from the remote server
172 *
173 * @param mixed $response - the response object
174 * @return mixed - the response
175 */
176 public function process_server_response($response) {
177 $this->set_current_stage('processing_response');
178 return $response;
179 }
180
181 /**
182 * Checks if a file is valid and capable of being smushed
183 *
184 * @param String $file_path - the path of the original image
185 * @return bool - true on success, false otherwise
186 */
187 public function validate_file($file_path) {
188
189 $allowed_file_types = $this->get_option('allowed_file_types');
190
191 if (!file_exists($file_path)) {
192 $this->fail("invalid_file_path", "The linked attachment ID does not have a valid file path");
193 return false;
194 }
195
196 if (filesize($file_path) > $this->get_option('max_filesize')) {
197 $this->fail("exceeded_max_filesize", "$file_path - cannot be optimized, file size is above service provider limit");
198 return false;
199 }
200
201 if (!in_array(strtolower(pathinfo($file_path, PATHINFO_EXTENSION)), $allowed_file_types)) {
202 $this->fail("invalid_file_type", "$file_path - cannot be optimized, it has an invalid file type");
203 return false;
204 }
205
206 return true;
207 }
208
209 /**
210 * Creates a backup of the original image
211 *
212 * @param String $file_path - the path of the original image
213 * @return bool - true on success, false otherwise
214 */
215 public function backup_original_image($file_path) {
216
217 $this->set_current_stage('backup_original');
218
219 if (is_multisite()) {
220 switch_to_blog($this->get_option('blog_id', 1));
221 }
222
223 $file = pathinfo($file_path);
224 $back_up = wp_normalize_path($file['dirname'].'/'.basename($file['filename'].$this->get_option('backup_prefix').$file['extension']));
225 $uploads_dir = wp_upload_dir();
226
227 // Make path relative and safe for migrations
228 $back_up_relative_path = preg_replace('#^'.wp_normalize_path($uploads_dir['basedir'].'/').'#', '', $back_up);
229
230 update_post_meta($this->get_option('attachment_id'), 'original-file', $back_up_relative_path);
231
232 if (is_multisite()) {
233 restore_current_blog();
234 }
235
236 $this->log("Backing up the original image - {$back_up_relative_path}");
237
238 return copy($file_path, $back_up);
239 }
240
241 /**
242 * Creates a backup of the original image
243 *
244 * @param String $file_path - the path of the original image
245 * @param Mixes $optimised_image - the contents of the image
246 * @param bool $backup_original - backup original image
247 *
248 * @return bool - true on success, false otherwise
249 */
250 private function save_optimised_image($file_path, $optimised_image, $backup_original) {
251
252 $this->set_current_stage('saving_image');
253
254 if ($backup_original)
255 $this->backup_original_image($file_path);
256
257 if (false !== file_put_contents($file_path, $optimised_image)) {
258 $this->success = true;
259 } else {
260 $this->success = false;
261 }
262
263 return $this->success;
264 }
265
266 /**
267 * Fires if the task succeds, any clean up code and logging goes here
268 */
269 public function complete() {
270
271 $attachment_id = $this->get_option('attachment_id');
272
273 if (is_multisite()) {
274 switch_to_blog($this->get_option('blog_id', 1));
275 $file_path = get_attached_file($attachment_id);
276 restore_current_blog();
277 } else {
278 $file_path = get_attached_file($attachment_id);
279 }
280
281 $original_size = $this->get_option('original_filesize');
282 $this->set_current_stage('completed');
283
284 clearstatcache(true, $file_path); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctionParameters.clearstatcache_clear_realpath_cacheFound,PHPCompatibility.FunctionUse.NewFunctionParameters.clearstatcache_filenameFound
285 if (0 == $original_size) {
286 $saved = '';
287 $info = sprintf(__("The file was compressed to %s using WP-Optimize", 'wp-optimize'), WP_Optimize()->format_size(filesize($file_path)));
288 } else {
289 $saved = round((($original_size - filesize($file_path)) / $original_size * 100), 2);
290 $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(filesize($file_path)), $saved);
291 }
292
293 $stats = array(
294 'smushed-with' => $this->label,
295 'original-size' => $original_size,
296 'smushed-size' => filesize($file_path),
297 'savings-percent' => $saved,
298 'sizes-info' => $this->get_option('smush-sizes-info'),
299 );
300
301 if (is_multisite()) {
302 switch_to_blog($this->get_option('blog_id', 1));
303 update_post_meta($attachment_id, 'smush-complete', true);
304 update_post_meta($attachment_id, 'smush-info', $info);
305 update_post_meta($attachment_id, 'smush-stats', $stats);
306 restore_current_blog();
307 } else {
308 update_post_meta($attachment_id, 'smush-complete', true);
309 update_post_meta($attachment_id, 'smush-info', $info);
310 update_post_meta($attachment_id, 'smush-stats', $stats);
311 }
312
313 $this->log("Successfully optimized the image - {$file_path}." . $info);
314 $this->set_status('complete');
315
316 return parent::complete();
317 }
318
319 /**
320 * Fires if the task fails, any clean up code and logging goes here
321 *
322 * @param String $error_code - A code for the failure
323 * @param String $error_message - A description for the failure
324 */
325 public function fail($error_code = "Unknown", $error_message = "Unknown") {
326
327 $attachment_id = $this->get_option('attachment_id');
328
329 $info = sprintf(__("Failed with error code %s - %s", 'wp-optimize'), $error_code, $error_message);
330
331 if (is_multisite()) {
332 switch_to_blog($this->get_option('blog_id', 1));
333 update_post_meta($attachment_id, 'smush-info', $info);
334 update_post_meta($attachment_id, 'smush-complete', false);
335 restore_current_blog();
336 } else {
337 update_post_meta($attachment_id, 'smush-info', $info);
338 update_post_meta($attachment_id, 'smush-complete', false);
339 }
340
341
342 do_action('ud_smush_task_failed', $this, $error_code, $error_message);
343
344 return parent::fail($error_code, $error_message);
345 }
346
347 /**
348 * Get all the supported task stages.
349 *
350 * @return array - list of task stages.
351 */
352 public function get_allowed_stages() {
353
354 $stages = array(
355 'initialised' => __('Initialised', 'wp-optimize'),
356 'connecting' => __('Connecting to API server', 'wp-optimize'),
357 'processing_response' => __('Processing response', 'wp-optimize'),
358 'backup_original' => __('Backing up original image', 'wp-optimize'),
359 'saving_image' => __('Saving optimized image', 'wp-optimize'),
360 'completed' => __('Successful', 'wp-optimize'),
361 );
362
363 return apply_filters('allowed_task_stages', $stages);
364 }
365
366 /**
367 * Get features available with this service
368 *
369 * @return Array - an array of features
370 */
371 public static function get_features() {
372 return array(
373 'max_filesize' => self::MAX_FILESIZE,
374 'lossy_compression' => true,
375 'preserve_exif' => true,
376 );
377 }
378
379 /**
380 * Retrieve default options for this task.
381 * This method should normally be over-ridden by the child.
382 *
383 * @return Array - an array of options
384 */
385 public function get_default_options() {
386
387 return array(
388 'allowed_file_types' => array('gif', 'png', 'jpg', 'tif', 'jpeg'),
389 'request_timeout' => 15,
390 'image_quality' => 90,
391 'backup_prefix' => '-updraft-pre-smush-original.'
392 );
393 }
394
395 /**
396 * Sets the task stage.
397 *
398 * @param String $stage - the current stage of the task
399 * @return bool - the result of the update
400 */
401 public function set_current_stage($stage) {
402
403 if (array_key_exists($stage, self::get_allowed_stages())) {
404 $this->stage = $stage;
405 return $this->update_option('current_stage', $this->stage);
406 }
407
408 return false;
409 }
410
411 /**
412 * Gets the task stage
413 *
414 * @return String $stage - the current stage of the task
415 */
416 public function get_current_stage() {
417 if (isset($this->stage))
418 return $this->stage;
419 else return $this->get_option('current_stage');
420 }
421
422 /**
423 * Get image paths to resized attachment images.
424 *
425 * @param int $attachment_id
426 * @return array
427 */
428 private function get_attachment_files($attachment_id) {
429 $attachment_images = array();
430 $upload_dir = function_exists('wp_get_upload_dir') ? wp_get_upload_dir() : wp_upload_dir(null, false);
431
432 // get sizes info from attachment meta data.
433 $meta = wp_get_attachment_metadata($attachment_id);
434 if (!is_array($meta) || !array_key_exists('sizes', $meta)) return $attachment_images;
435
436 $image_sizes = array_keys($meta['sizes']);
437
438 // build list of resized images.
439 foreach ($image_sizes as $size) {
440 $image = image_get_intermediate_size($attachment_id, $size);
441
442 if (is_array($image)) {
443 $file = trailingslashit($upload_dir['basedir']) . $image['path'];
444 if (is_file($file) && !in_array($file, $attachment_images)) {
445 $attachment_images[$size] = $file;
446 }
447 }
448 }
449
450 return $attachment_images;
451 }
452
453 /**
454 * Check the mime type of a downloaded file, returns true if it is a valid image mime type.
455 *
456 * @param string $file_buffer The buffer string downloaded from the compression service
457 * @return boolean
458 */
459 protected function is_downloaded_image_buffer_mime_type_valid($file_buffer) {
460 // If the required class does not exist, return true to avoid breaking the functionality
461 if (!class_exists('finfo')) return true;
462 $accepted_types = apply_filters('wpo_image_compression_accepted_mime_types', array('image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/webp'));
463 // The ignore rule below is added because "finfo" doesn't exist in PHP5.2.
464 $finfo = new finfo(FILEINFO_MIME_TYPE); // phpcs:ignore PHPCompatibility.Classes.NewClasses.finfoFound, PHPCompatibility.Constants.NewConstants.fileinfo_mime_typeFound
465 $mime_type = $finfo->buffer($file_buffer);
466 return in_array($mime_type, $accepted_types);
467 }
468 }
469 endif;
470