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

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