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

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