PluginProbe ʕ •ᴥ•ʔ
Smush – Image Optimization, Compression, Lazy Load, WebP & CDN / 2.1.4
Smush – Image Optimization, Compression, Lazy Load, WebP & CDN v2.1.4
4.1.0 4.0.3 4.0.2 2.8.1 2.9.1 3.0.0 3.0.1 3.0.2 3.1.1 3.10.1 3.10.2 3.10.3 3.11.1 3.12.3 3.12.4 3.12.5 3.12.6 3.13.0 3.13.1 3.13.2 3.14.0 3.14.1 3.14.2 3.15.0 3.15.1 3.15.2 3.15.3 3.15.4 3.15.5 3.16.2 3.16.4 3.16.5 3.16.6 3.17.0 3.17.1 3.18.0 3.18.1 3.2.0.1 3.2.1 3.2.2.1 3.2.4 3.20.0 3.21.1 3.22.1 3.22.3 3.23.0 3.23.1 3.23.2 3.23.3 3.23.4 3.24.0 3.24.0-beta.2 3.3.0 3.3.1 3.3.2 3.4.1 3.4.2 3.6.1 3.6.3 3.7.0 3.7.1 3.7.2 3.7.3 3.8.2 3.8.3 3.8.4 3.8.5 3.8.7 3.8.8 3.9.0 3.9.1 3.9.11 3.9.2 3.9.4 3.9.5 3.9.8 3.9.9 trunk 1.0.0 1.0.1 1.0.2 1.1 1.1.1 1.1.2 1.1.3 1.2 1.2.1 1.2.10 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 1.4.3 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.5.1 1.6.5.2 1.6.5.3 1.6.5.4 1.7 1.7.1 1.7.1.1 2.0 2.0.1 2.0.3 2.0.4 2.0.5 2.0.6 2.0.6.2 2.0.6.3 2.0.6.5 2.0.7 2.0.7.1 2.1 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.2 2.2.1 2.2.2 2.3 2.3.1 2.4 2.4.2 2.4.3 2.4.4 2.4.5 2.5.2 2.5.3 2.6.1 2.6.2 2.6.3 2.7 2.7.1 2.7.4 2.7.4.1 2.7.5 2.7.6 2.7.8 2.7.8.1 2.7.9.1 2.8.0 2.8.0.1
wp-smushit / lib / class-wp-smush.php
wp-smushit / lib Last commit date
nextgen-integration 10 years ago class-wp-smush-admin.php 10 years ago class-wp-smush-bulk.php 10 years ago class-wp-smush-migrate.php 11 years ago class-wp-smush-nextgen.php 10 years ago class-wp-smush.php 10 years ago
class-wp-smush.php
1275 lines
1 <?php
2 require_once WP_SMUSH_DIR . "lib/class-wp-smush-migrate.php";
3 if ( ! class_exists( 'WpSmush' ) ) {
4
5 class WpSmush {
6
7 var $version = WP_SMUSH_VERSION;
8
9 /**
10 * @var Stores the value of is_pro function
11 */
12 private $is_pro;
13
14 /**
15 * Api server url to check api key validity
16 *
17 */
18 var $api_server = 'https://premium.wpmudev.org/wdp-un.php?action=smushit_check';
19
20 /**
21 * Meta key to save smush result to db
22 *
23 *
24 */
25 var $smushed_meta_key = 'wp-smpro-smush-data';
26
27 /**
28 * Meta key to save migrated version
29 *
30 */
31 var $migrated_version_key = "wp-smush-migrated-version";
32
33 /**
34 * Constructor
35 */
36 function __construct() {
37 /**
38 * Hooks
39 */
40 //Check if auto is enabled
41 $auto_smush = get_option( WP_SMUSH_PREFIX . 'auto' );
42
43 //Keep the uto smush on by default
44 if ( $auto_smush === false ) {
45 $auto_smush = 1;
46 }
47
48 //Auto Smush the new image
49 if ( $auto_smush ) {
50 add_filter( 'wp_update_attachment_metadata', array(
51 $this,
52 'filter_generate_attachment_metadata'
53 ), 12, 2 );
54 }
55
56 //Optimise WP Retina 2x images
57 add_action( 'wr2x_retina_file_added', array( $this, 'smush_retina_image' ), 20, 3 );
58
59 //Add Smush Columns
60 add_filter( 'manage_media_columns', array( $this, 'columns' ) );
61 add_action( 'manage_media_custom_column', array( $this, 'custom_column' ), 10, 2 );
62 add_filter( 'manage_upload_sortable_columns', array( $this, 'sortable_column' ) );
63 //Manage column sorting
64 add_action( 'pre_get_posts', array( $this, 'smushit_orderby' ) );
65
66 //Enqueue Scripts
67 add_action( 'admin_init', array( $this, 'admin_init' ) );
68
69 //Old Smush stats migration
70 add_action( "admin_init", array( $this, "migrate" ) );
71
72 //Load Translation files
73 add_action( 'plugins_loaded', array( $this, 'i18n' ), 12 );
74
75 //Load NextGen Gallery, if hooked too late or early, auto smush doesn't works, also Load after settings have been saved on init action
76 add_action( 'plugins_loaded', array( $this, 'load_nextgen' ), 90 );
77 }
78
79 function i18n() {
80 load_plugin_textdomain( 'wp-smushit', false, WP_SMUSH_DIR . '/languages/' );
81 }
82
83 function admin_init() {
84 wp_enqueue_script( 'common' );
85 }
86
87 /**
88 * Process an image with Smush.
89 *
90 * Returns an array of the $file $results.
91 *
92 * @param string $file Full absolute path to the image file
93 * @param string $file_url Optional full URL to the image file
94 *
95 * @returns array
96 */
97 function do_smushit( $file_path = '' ) {
98 $errors = new WP_Error();
99 $dir_name = dirname( $file_path );
100 if ( empty( $file_path ) ) {
101 $errors->add( "empty_path", __( "File path is empty", 'wp-smushit' ) );
102 }
103
104 // check that the file exists
105 if ( ! file_exists( $file_path ) || ! is_file( $file_path ) ) {
106 $errors->add( "file_not_found", sprintf( __( "Could not find %s", 'wp-smushit' ), $file_path ) );
107 }
108
109 // check that the file is writable
110 if ( ! is_writable( $dir_name ) ) {
111 $errors->add( "not_writable", sprintf( __( "%s is not writable", 'wp-smushit' ), $dir_name ) );
112 }
113
114 $file_size = file_exists( $file_path ) ? filesize( $file_path ) : '';
115
116 //Check if premium user
117 $max_size = $this->is_pro() ? WP_SMUSH_PREMIUM_MAX_BYTES : WP_SMUSH_MAX_BYTES;
118
119 //Check if file exists
120 if ( $file_size == 0 ) {
121 $errors->add( "image_not_found", sprintf( __( 'Skipped (%s), image not found.', 'wp-smushit' ), $this->format_bytes( $file_size ) ) );
122 }
123
124 //Check size limit
125 if ( $file_size > $max_size ) {
126 $errors->add( "size_limit", sprintf( __( 'Skipped (%s), size limit exceeded.', 'wp-smushit' ), $this->format_bytes( $file_size ) ) );
127 }
128
129 if ( count( $errors->get_error_messages() ) ) {
130 return $errors;
131 }
132
133 /** Send image for smushing, and fetch the response */
134 $response = $this->_post( $file_path, $file_size );
135
136 if ( ! $response['success'] ) {
137 $errors->add( "false_response", $response['message'] );
138 }
139 //If there is no data
140 if ( empty( $response['data'] ) ) {
141 $errors->add( "no_data", __( 'Unknown API error', 'wp-smushit' ) );
142 }
143
144 if ( count( $errors->get_error_messages() ) ) {
145 return $errors;
146 }
147
148 //If there are no savings, or image returned is bigger in size
149 if ( ( ! empty( $response['data']->bytes_saved ) && intval( $response['data']->bytes_saved ) <= 0 )
150 || empty( $response['data']->image )
151 ) {
152 return $response;
153 }
154 $tempfile = $file_path . ".tmp";
155
156 //Add the file as tmp
157 file_put_contents( $tempfile, $response['data']->image );
158
159 //handle backups if enabled
160 $backup = get_option( WP_SMUSH_PREFIX . 'backup' );
161 if ( $backup && $this->is_pro() ) {
162 $path = pathinfo( $file_path );
163 $backup_name = trailingslashit( $path['dirname'] ) . $path['filename'] . ".bak." . $path['extension'];
164 @copy( $file_path, $backup_name );
165 }
166
167 //replace the file
168 $success = @rename( $tempfile, $file_path );
169
170 //if tempfile still exists, unlink it
171 if ( file_exists( $tempfile ) ) {
172 unlink( $tempfile );
173 }
174
175 //If file renaming failed
176 if ( ! $success ) {
177 @copy( $tempfile, $file_path );
178 unlink( $tempfile );
179 }
180
181 //Some servers are having issue with file permission, this should fix it
182 //Source: WordPress Core
183 $stat = stat( dirname( $file_path ) );
184 $perms = $stat['mode'] & 0000666; //same permissions as parent folder, strip off the executable bits
185 @ chmod( $file_path, $perms );
186
187
188 return $response;
189 }
190
191 /**
192 * Fills $placeholder array with values from $data array
193 *
194 * @param array $placeholders
195 * @param array $data
196 *
197 * @return array
198 */
199 function _array_fill_placeholders( array $placeholders, array $data ) {
200 $placeholders['percent'] = $data['compression'];
201 $placeholders['bytes'] = $data['bytes_saved'];
202 $placeholders['size_before'] = $data['before_size'];
203 $placeholders['size_after'] = $data['after_size'];
204 $placeholders['time'] = $data['time'];
205
206 return $placeholders;
207 }
208
209 /**
210 * Returns signature for single size of the smush api message to be saved to db;
211 *
212 * @return array
213 */
214 function _get_size_signature() {
215 return array(
216 'percent' => - 1,
217 'bytes' => - 1,
218 'size_before' => - 1,
219 'size_after' => - 1,
220 'time' => - 1
221 );
222 }
223
224 /**
225 * Read the image paths from an attachment's meta data and process each image
226 * with wp_smushit().
227 *
228 * This method also adds a `wp_smushit` meta key for use in the media library.
229 * Called after `wp_generate_attachment_metadata` is completed.
230 *
231 * @param $meta
232 * @param null $ID
233 *
234 * @return mixed
235 */
236 function resize_from_meta_data( $meta, $ID = null ) {
237
238 //Flag to check, if original size image should be smushed or not
239 $original = get_option( WP_SMUSH_PREFIX . 'original' );
240 $smush_full = ( $this->is_pro() && $original == 1 ) ? true : false;
241
242 $errors = new WP_Error();
243 $stats = array(
244 "stats" => array_merge( $this->_get_size_signature(), array(
245 'api_version' => - 1,
246 'lossy' => - 1
247 )
248 ),
249 'sizes' => array()
250 );
251
252 $size_before = $size_after = $compression = $total_time = $bytes_saved = 0;
253
254 if ( $ID && wp_attachment_is_image( $ID ) === false ) {
255 return $meta;
256 }
257
258 //File path and URL for original image
259 $attachment_file_path = get_attached_file( $ID );
260
261 // If images has other registered size, smush them first
262 if ( ! empty( $meta['sizes'] ) ) {
263
264 //if smush original is set to false, otherwise smush
265 //Check for large size, we will set a flag to leave the original untouched
266 if ( ! $smush_full ) {
267 if ( array_key_exists( 'large', $meta['sizes'] ) ) {
268 $smush_full = false;
269 } else {
270 $smush_full = true;
271 }
272 }
273
274 if( class_exists('finfo') ) {
275 $finfo = new finfo( FILEINFO_MIME_TYPE );
276 }else{
277 $finfo = false;
278 }
279 $image_path = trailingslashit( dirname( $attachment_file_path ) );
280 foreach ( $meta['sizes'] as $size_key => $size_data ) {
281
282 // We take the original image. The 'sizes' will all match the same URL and
283 // path. So just get the dirname and replace the filename.
284 $attachment_file_path_size = $image_path . $size_data['file'];
285
286 if ( $finfo ) {
287 $ext = file_exists( $attachment_file_path_size ) ? $finfo->file( $attachment_file_path_size ) : '';
288 } elseif ( function_exists( 'mime_content_type' ) ) {
289 $ext = mime_content_type( $attachment_file_path_size );
290 } else {
291 $ext = false;
292 }
293 if( $ext ) {
294 $valid_mime = array_search(
295 $ext,
296 array(
297 'jpg' => 'image/jpeg',
298 'png' => 'image/png',
299 'gif' => 'image/gif',
300 ),
301 true
302 );
303 if ( false === $valid_mime ) {
304 continue;
305 }
306 }
307 /**
308 * Allows to skip a image from smushing
309 *
310 * @param bool , Smush image or not
311 * @$size string, Size of image being smushed
312 */
313 $smush_image = apply_filters( 'wp_smush_media_image', true, $size_key );
314 if ( ! $smush_image ) {
315 continue;
316 }
317
318 //Store details for each size key
319 $response = $this->do_smushit( $attachment_file_path_size );
320
321 if ( is_wp_error( $response ) ) {
322 return $response;
323 }
324
325 if ( ! empty( $response['data'] ) ) {
326 $stats['sizes'][ $size_key ] = (object) $this->_array_fill_placeholders( $this->_get_size_signature(), (array) $response['data'] );
327 }
328
329 //Total Stats, store all data in bytes
330 if ( isset( $response['data'] ) ) {
331 list( $size_before, $size_after, $total_time, $compression, $bytes_saved )
332 = $this->_update_stats_data( $response['data'], $size_before, $size_after, $total_time, $bytes_saved );
333 } else {
334 $errors->add( "image_size_error" . $size_key, sprintf( __( "Size '%s' not processed correctly", 'wp-smushit' ), $size_key ) );
335 }
336
337 if ( empty( $stats['stats']['api_version'] ) || $stats['stats']['api_version'] == - 1 ) {
338 $stats['stats']['api_version'] = $response['data']->api_version;
339 $stats['stats']['lossy'] = $response['data']->lossy;
340 }
341 }
342 }else{
343 $smush_full = true;
344 }
345
346 /**
347 * Allows to skip a image from smushing
348 *
349 * @param bool , Smush image or not
350 * @$size string, Size of image being smushed
351 */
352 $smush_full_image = apply_filters( 'wp_smush_media_image', true, 'full' );
353
354 //If original size is supposed to be smushed
355 if ( $smush_full && $smush_full_image ) {
356
357 $full_image_response = $this->do_smushit( $attachment_file_path );
358
359 if ( is_wp_error( $full_image_response ) ) {
360 return $full_image_response;
361 }
362
363 if ( ! empty( $full_image_response['data'] ) ) {
364 $stats['sizes']['full'] = (object) $this->_array_fill_placeholders( $this->_get_size_signature(), (array) $full_image_response['data'] );
365 } else {
366 $errors->add( "image_size_error", __( "Size 'full' not processed correctly", 'wp-smushit' ) );
367 }
368
369 //Update stats
370 if ( isset( $full_image_response['data'] ) ) {
371 list( $size_before, $size_after, $total_time, $compression, $bytes_saved )
372 = $this->_update_stats_data( $full_image_response['data'], $size_before, $size_after, $total_time, $bytes_saved );
373 } else {
374 $errors->add( "image_size_error", __( "Size 'full' not processed correctly", 'wp-smushit' ) );
375 }
376
377 //Api version and lossy, for some images, full image i skipped and for other images only full exists
378 //so have to add code again
379 if ( empty( $stats['stats']['api_version'] ) || $stats['stats']['api_version'] == - 1 ) {
380 $stats['stats']['api_version'] = $full_image_response['data']->api_version;
381 $stats['stats']['lossy'] = $full_image_response['data']->lossy;
382 }
383
384
385 }
386
387 $has_errors = (bool) count( $errors->get_error_messages() );
388
389 list( $stats['stats']['size_before'], $stats['stats']['size_after'], $stats['stats']['time'], $stats['stats']['percent'], $stats['stats']['bytes'] ) =
390 array( $size_before, $size_after, $total_time, $compression, $bytes_saved );
391
392 //Set smush status for all the images, store it in wp-smpro-smush-data
393 if ( ! $has_errors ) {
394
395 $existing_stats = get_post_meta( $ID, $this->smushed_meta_key, true );
396
397 if ( ! empty( $existing_stats ) ) {
398 //Update total bytes saved, and compression percent
399 $stats['stats']['bytes'] = isset( $existing_stats['stats']['bytes'] ) ? $existing_stats['stats']['bytes'] + $stats['stats']['bytes'] : $stats['stats']['bytes'];
400 $stats['stats']['percent'] = isset( $existing_stats['stats']['percent'] ) ? $existing_stats['stats']['percent'] + $stats['stats']['percent'] : $stats['stats']['percent'];
401
402 //Update stats for each size
403 if ( isset( $existing_stats['sizes'] ) && ! empty( $stats['sizes'] ) ) {
404
405 foreach ( $existing_stats['sizes'] as $size_name => $size_stats ) {
406 //if stats for a particular size doesn't exists
407 if ( empty( $stats['sizes'][ $size_name ] ) ) {
408 $stats['sizes'][ $size_name ] = $existing_stats['sizes'][ $size_name ];
409 } else {
410 //Update compression percent and bytes saved for each size
411 $stats['sizes'][ $size_name ]->bytes = $stats['sizes'][ $size_name ]->bytes + $existing_stats['sizes'][ $size_name ]->bytes;
412 $stats['sizes'][ $size_name ]->percent = $stats['sizes'][ $size_name ]->percent + $existing_stats['sizes'][ $size_name ]->percent;
413 }
414 }
415 }
416 }
417 update_post_meta( $ID, $this->smushed_meta_key, $stats );
418 }
419
420 return $meta;
421 }
422
423 /**
424 * Read the image paths from an attachment's meta data and process each image
425 * with wp_smushit()
426 *
427 * Filters wp_generate_attachment_metadata
428 *
429 * @uses resize_from_meta_data
430 *
431 * @param $meta
432 * @param null $ID
433 *
434 * @return mixed
435 */
436 function filter_generate_attachment_metadata( $meta, $ID = null ) {
437 //Update API url for Hostgator
438
439 //Check for use of http url, (Hostgator mostly)
440 $use_http = wp_cache_get( WP_SMUSH_PREFIX.'use_http', 'smush' );
441 if( !$use_http ) {
442 $use_http = get_option( WP_SMUSH_PREFIX.'use_http' );
443 wp_cache_add( WP_SMUSH_PREFIX.'use_http', $use_http, 'smush' );
444 }
445 $use_http ? define( 'WP_SMUSH_API_HTTP', 'http://smushpro.wpmudev.org/1.0/') : '';
446
447 $this->resize_from_meta_data( $meta, $ID );
448
449 return $meta;
450 }
451
452
453 /**
454 * Posts an image to Smush.
455 *
456 * @param $file_path path of file to send to Smush
457 * @param $file_size
458 *
459 * @return bool|array array containing success status, and stats
460 */
461 function _post( $file_path, $file_size ) {
462
463 $data = false;
464
465 $file = @fopen( $file_path, 'r' );
466 $file_data = fread( $file, $file_size );
467 $headers = array(
468 'accept' => 'application/json', // The API returns JSON
469 'content-type' => 'application/binary', // Set content type to binary
470 );
471
472 //Check if premium member, add API key
473 $api_key = $this->_get_api_key();
474 if ( ! empty( $api_key ) ) {
475 $headers['apikey'] = $api_key;
476 }
477
478 //Check if lossy compression allowed and add it to headers
479 $lossy = get_option( WP_SMUSH_PREFIX . 'lossy' );
480
481 if ( $lossy && $this->is_pro() ) {
482 $headers['lossy'] = 'true';
483 } else {
484 $headers['lossy'] = 'false';
485 }
486
487 $api_url = defined( 'WP_SMUSH_API_HTTP' ) ? WP_SMUSH_API_HTTP : WP_SMUSH_API;
488 $args = array(
489 'headers' => $headers,
490 'body' => $file_data,
491 'timeout' => WP_SMUSH_TIMEOUT,
492 'user-agent' => WP_SMUSH_UA,
493 );
494 $result = wp_remote_post( $api_url, $args );
495
496 //Close file connection
497 fclose( $file );
498 unset( $file_data );//free memory
499 if ( is_wp_error( $result ) ) {
500
501 $er_msg = $result->get_error_message();
502
503 //Hostgator Issue
504 if ( ! empty( $er_msg ) && strpos( $er_msg, 'SSL CA cert' ) !== false ) {
505 //Update DB for using http protocol
506 update_option( WP_SMUSH_PREFIX . 'use_http', 1 );
507 }
508 //Handle error
509 $data['message'] = sprintf( __( 'Error posting to API: %s', 'wp-smushit' ), $result->get_error_message() );
510 $data['success'] = false;
511 unset( $result ); //free memory
512 return $data;
513 } else if ( '200' != wp_remote_retrieve_response_code( $result ) ) {
514 //Handle error
515 $data['message'] = sprintf( __( 'Error posting to API: %s %s', 'wp-smushit' ), wp_remote_retrieve_response_code( $result ), wp_remote_retrieve_response_message( $result ) );
516 $data['success'] = false;
517 unset( $result ); //free memory
518
519 return $data;
520 }
521
522 //If there is a response and image was successfully optimised
523 $response = json_decode( $result['body'] );
524 if ( $response && $response->success == true ) {
525
526 //If there is any savings
527 if ( $response->data->bytes_saved > 0 ) {
528 $image = base64_decode( $response->data->image ); //base64_decode is necessary to send binary img over JSON, no security problems here!
529 $image_md5 = md5( $response->data->image );
530 if ( $response->data->image_md5 != $image_md5 ) {
531 //Handle error
532 $data['message'] = __( 'Smush data corrupted, try again.', 'wp-smushit' );
533 $data['success'] = false;
534 unset( $image );//free memory
535 } else {
536 $data['success'] = true;
537 $data['data'] = $response->data;
538 $data['data']->image = $image;
539 unset( $image );//free memory
540 }
541 } else {
542 //just return the data
543 $data['success'] = true;
544 $data['data'] = $response->data;
545 }
546 } else {
547 //Server side error, get message from response
548 $data['message'] = ! empty( $response->data ) ? $response->data : __( "Image couldn't be smushed", 'wp-smushit' );
549 $data['success'] = false;
550 }
551
552 unset( $result );//free memory
553 unset( $response );//free memory
554 return $data;
555 }
556
557
558 /**
559 * Print column header for Smush results in the media library using
560 * the `manage_media_columns` hook.
561 */
562 function columns( $defaults ) {
563 $defaults['smushit'] = 'WP Smush';
564
565 return $defaults;
566 }
567
568 /**
569 * Return the filesize in a humanly readable format.
570 * Taken from http://www.php.net/manual/en/function.filesize.php#91477
571 */
572 function format_bytes( $bytes, $precision = 2 ) {
573 $units = array( 'B', 'KB', 'MB', 'GB', 'TB' );
574 $bytes = max( $bytes, 0 );
575 $pow = floor( ( $bytes ? log( $bytes ) : 0 ) / log( 1024 ) );
576 $pow = min( $pow, count( $units ) - 1 );
577 $bytes /= pow( 1024, $pow );
578
579 return round( $bytes, $precision ) . ' ' . $units[ $pow ];
580 }
581
582 /**
583 * Print column data for Smush results in the media library using
584 * the `manage_media_custom_column` hook.
585 */
586 function custom_column( $column_name, $id ) {
587 if ( 'smushit' == $column_name ) {
588 $this->set_status( $id );
589 }
590 }
591
592 /**
593 * Check if user is premium member, check for api key
594 *
595 * @return mixed|string
596 */
597 function is_pro() {
598
599 if ( isset( $this->is_pro ) ) {
600 return $this->is_pro;
601 }
602
603 //no api key set, always false
604 $api_key = $this->_get_api_key();
605
606 if ( empty( $api_key ) ) {
607 return false;
608 }
609
610 //Flag to check if we need to revalidate the key
611 $revalidate = false;
612
613 $api_auth = get_site_option('wp_smush_api_auth');
614
615 //Check if need to revalidate
616 if ( ! $api_auth || empty( $api_auth ) || empty( $api_auth[ $api_key ] ) ) {
617 $revalidate = true;
618 } else {
619 $last_checked = $api_auth[ $api_key ]['timestamp'];
620 $valid = $api_auth[ $api_key ]['validity'];
621
622 $diff = $last_checked - current_time( 'timestamp' );
623
624 //Difference in hours
625 $diff_h = $diff / 3600;
626
627 //Difference in minutes
628 $diff_m = $diff / 60;
629
630 switch ( $valid ) {
631 case 'valid':
632 //if last checked was more than 12 hours
633 if ( $diff_h > 12 ) {
634 $revalidate = true;
635 }
636 break;
637 case 'invalid':
638 //if last checked was more than 24 hours
639 if ( $diff_h > 24 ) {
640 $revalidate = true;
641 }
642 break;
643 case 'network_failure':
644 //if last checked was more than 5 minutes
645 if ( $diff_m > 5 ) {
646 $revalidate = true;
647 }
648 break;
649 }
650 }
651 //If we are suppose to validate api, update the results in options table
652 if ( $revalidate ) {
653 // call api
654 $url = $this->api_server . '&key=' . urlencode( $api_key );
655
656 $request = wp_remote_get( $url, array(
657 "user-agent" => WP_SMUSH_UA,
658 "timeout" => 10
659 )
660 );
661
662 if ( ! is_wp_error( $request ) && '200' == wp_remote_retrieve_response_code( $request ) ) {
663 $result = json_decode( wp_remote_retrieve_body( $request ) );
664 if ( $result && $result->success ) {
665 $valid = 'valid';
666 } else {
667 $valid = 'invalid';
668 }
669
670 } else {
671 $valid = 'network_failure';
672 }
673
674 //Reset Value
675 $api_auth = array();
676
677 //Update Timestamp
678 $timestamp = current_time( 'timestamp' );
679 $api_auth[ $api_key ] = array( 'validity' => $valid, 'timestamp' => $timestamp );
680
681 //Update API validity
682 update_site_option( 'wp_smush_api_auth', $api_auth );
683
684 }
685
686 $this->is_pro = ( 'valid' == $valid );
687
688 return $this->is_pro;
689 }
690
691 /**
692 * Returns api key
693 *
694 * @return mixed
695 */
696 function _get_api_key() {
697
698 if ( defined( 'WPMUDEV_APIKEY' ) && WPMUDEV_APIKEY ) {
699 $api_key = WPMUDEV_APIKEY;
700 } else {
701 $api_key = get_site_option( 'wpmudev_apikey' );
702 }
703
704 return $api_key;
705 }
706
707
708 /**
709 * Checks if image is already smushed
710 *
711 * @param int $id
712 * @param array $data
713 *
714 * @return bool|mixed
715 */
716 function is_smushed( $id, $data = null ) {
717
718 //For new images
719 $wp_is_smushed = get_post_meta( $id, 'wp-is-smushed', true );
720
721 //Not smushed, backward compatibility, check attachment metadata
722 if ( ! $wp_is_smushed && $data !== null ) {
723 if ( isset( $data['wp_smushit'] ) && ! empty( $data['wp_smushit'] ) ) {
724 $wp_is_smushed = true;
725 }
726 }
727
728 return $wp_is_smushed;
729 }
730
731 /**
732 * Returns size saved from the api call response
733 *
734 * @param string $message
735 *
736 * @return string|bool
737 */
738 function get_saved_size( $message ) {
739 if ( preg_match( '/\((.*)\)/', $message, $matches ) ) {
740 return isset( $matches[1] ) ? $matches[1] : false;
741 }
742
743 return false;
744 }
745
746 /**
747 * Set send button status
748 *
749 * @param $id
750 * @param bool $echo
751 * @param bool $text_only
752 *
753 * @return string|void
754 */
755 function set_status( $id, $echo = true, $text_only = false ) {
756 $status_txt = $button_txt = '';
757 $show_button = false;
758
759 $wp_smush_data = get_post_meta( $id, $this->smushed_meta_key, true );
760 $attachment_data = wp_get_attachment_metadata( $id );
761 //
762 // if the image is smushed
763 if ( ! empty( $wp_smush_data ) ) {
764
765 $image_count = count( $wp_smush_data['sizes'] );
766 $bytes = isset( $wp_smush_data['stats']['bytes'] ) ? $wp_smush_data['stats']['bytes'] : 0;
767 $bytes_readable = ! empty( $bytes ) ? $this->format_bytes( $bytes ) : '';
768 $percent = isset( $wp_smush_data['stats']['percent'] ) ? $wp_smush_data['stats']['percent'] : 0;
769 $percent = $percent < 0 ? 0 : $percent;
770
771 if ( isset( $wp_smush_data['stats']['size_before'] ) && $wp_smush_data['stats']['size_before'] == 0 && ! empty( $wp_smush_data['sizes'] ) ) {
772 $status_txt = __( 'Error processing request', 'wp-smushit' );
773 $show_button = true;
774 } else {
775 if ( $bytes == 0 || $percent == 0 ) {
776 $status_txt = __( 'Already Optimized', 'wp-smushit' );
777 } elseif ( ! empty( $percent ) && ! empty( $bytes_readable ) ) {
778 $status_txt = $image_count > 1 ? sprintf( __( "%d images reduced ", 'wp-smushit' ), $image_count ) : __( "Reduced ", 'wp-smushit' );
779 $status_txt .= sprintf( __( "by %s ( %01.1f%% )", 'wp-smushit' ), $bytes_readable, number_format_i18n( $percent, 2, '.', '' ) );
780
781 //Show detailed stats if available
782 if ( ! empty( $wp_smush_data['sizes'] ) ) {
783 //Detailed Stats Link
784 $status_txt .= '<br /><a href="#" class="smush-stats-details">' . esc_html__( "Smush stats", 'wp-smushit' ) . ' [<span class="stats-toggle">+</span>]</a>';
785
786 //Stats
787 $status_txt .= $this->get_detailed_stats( $id, $wp_smush_data, $attachment_data );
788 }
789 }
790 }
791
792 //IF current compression is lossy
793 if ( ! empty( $wp_smush_data ) && ! empty( $wp_smush_data['stats'] ) ) {
794 $lossy = ! empty( $wp_smush_data['stats']['lossy'] ) ? $wp_smush_data['stats']['lossy'] : '';
795 $is_lossy = $lossy == 1 ? true : false;
796 }
797
798 //Check if Lossy enabled
799 $opt_lossy = WP_SMUSH_PREFIX . 'lossy';
800 $opt_lossy_val = get_option( $opt_lossy, false );
801
802 //Check image type
803 $image_type = get_post_mime_type( $id );
804
805 //Check if premium user, compression was lossless, and lossy compression is enabled
806 if ( $this->is_pro() && ! $is_lossy && $opt_lossy_val && $image_type != 'image/gif' ) {
807 // the button text
808 $button_txt = __( 'Super-Smush', 'wp-smushit' );
809 $show_button = true;
810 }
811 } else {
812
813 // the status
814 $status_txt = __( 'Not processed', 'wp-smushit' );
815
816 // we need to show the smush button
817 $show_button = true;
818
819 // the button text
820 $button_txt = __( 'Smush Now!', 'wp-smushit' );
821 }
822 if ( $text_only ) {
823 return $status_txt;
824 }
825
826 $text = $this->column_html( $id, $status_txt, $button_txt, $show_button, $wp_smush_data, $echo );
827 if ( ! $echo ) {
828 return $text;
829 }
830 }
831
832 /**
833 * Print the column html
834 *
835 * @param string $id Media id
836 * @param string $status_txt Status text
837 * @param string $button_txt Button label
838 * @param boolean $show_button Whether to shoe the button
839 *
840 * @return null
841 */
842 function column_html( $id, $status_txt = "", $button_txt = "", $show_button = true, $smushed = false, $echo = true ) {
843 $allowed_images = array( 'image/jpeg', 'image/jpg', 'image/png', 'image/gif' );
844
845 // don't proceed if attachment is not image, or if image is not a jpg, png or gif
846 if ( ! wp_attachment_is_image( $id ) || ! in_array( get_post_mime_type( $id ), $allowed_images ) ) {
847 return;
848 }
849
850 $class = $smushed ? '' : ' hidden';
851 $html = '
852 <p class="smush-status' . $class . '">' . $status_txt . '</p>';
853 // if we aren't showing the button
854 if ( ! $show_button ) {
855 if ( $echo ) {
856 echo $html;
857
858 return;
859 } else {
860 if ( ! $smushed ) {
861 $class = ' currently-smushing';
862 } else {
863 $class = ' smushed';
864 }
865
866 return '<div class="smush-wrap' . $class . '">' . $html . '</div>';
867 }
868 }
869 if ( ! $echo ) {
870 $html .= '
871 <button class="button button-primary wp-smush-send" data-id="' . $id . '">
872 <span>' . $button_txt . '</span>
873 </button>';
874 if ( ! $smushed ) {
875 $class = ' unsmushed';
876 } else {
877 $class = ' smushed';
878 }
879
880 return '<div class="smush-wrap' . $class . '">' . $html . '</div>';
881 } else {
882 $html .= '<button class="button wp-smush-send" data-id="' . $id . '">
883 <span>' . $button_txt . '</span>
884 </button>';
885 echo $html;
886 }
887 }
888
889 /**
890 * Migrates smushit api message to the latest structure
891 *
892 *
893 * @return void
894 */
895 function migrate() {
896
897 if ( ! version_compare( $this->version, "1.7.1", "lte" ) ) {
898 return;
899 }
900
901 $migrated_version = get_option( $this->migrated_version_key );
902
903 if ( $migrated_version === $this->version ) {
904 return;
905 }
906
907 global $wpdb;
908
909 $q = $wpdb->prepare( "SELECT * FROM `" . $wpdb->postmeta . "` WHERE `meta_key`=%s AND `meta_value` LIKE %s ", "_wp_attachment_metadata", "%wp_smushit%" );
910 $results = $wpdb->get_results( $q );
911
912 if ( count( $results ) < 1 ) {
913 return;
914 }
915
916 $migrator = new WpSmushMigrate();
917 foreach ( $results as $attachment_meta ) {
918 $migrated_message = $migrator->migrate_api_message( maybe_unserialize( $attachment_meta->meta_value ) );
919 if ( $migrated_message !== array() ) {
920 update_post_meta( $attachment_meta->post_id, $this->smushed_meta_key, $migrated_message );
921 }
922 }
923
924 update_option( $this->migrated_version_key, $this->version );
925
926 }
927
928 /**
929 * @param Object $response_data
930 * @param $size_before
931 * @param $size_after
932 * @param $total_time
933 * @param $bytes_saved
934 *
935 * @return array
936 */
937 function _update_stats_data( $response_data, $size_before, $size_after, $total_time, $bytes_saved ) {
938 $size_before += ! empty( $response_data->before_size ) ? (int) $response_data->before_size : 0;
939 $size_after += ( ! empty( $response_data->after_size ) && $response_data->after_size > 0 ) ? (int) $response_data->after_size : (int) $response_data->before_size;
940 $total_time += ! empty( $response_data->time ) ? (float) $response_data->time : 0;
941 $bytes_saved += ( ! empty( $response_data->bytes_saved ) && $response_data->bytes_saved > 0 ) ? $response_data->bytes_saved : 0;
942 $compression = ( $bytes_saved > 0 && $size_before > 0 ) ? ( ( $bytes_saved / $size_before ) * 100 ) : 0;
943
944 return array( $size_before, $size_after, $total_time, $compression, $bytes_saved );
945 }
946
947 /**
948 * Updates the smush stats for a single image size
949 * @param $id
950 * @param $stats
951 * @param $image_size
952 */
953 function update_smush_stats_single( $id, $smush_stats, $image_size = '' ) {
954 //Return, if we don't have image id or stats for it
955 if ( empty( $id ) || empty( $smush_stats ) || empty( $image_size ) ) {
956 return false;
957 }
958 $image_size = $image_size . '@2x';
959 $data = $smush_stats['data'];
960 //Get existing Stats
961 $stats = get_post_meta( $id, $this->smushed_meta_key, true );
962 //Update existing Stats
963 if ( ! empty( $stats ) ) {
964 //Update total bytes saved, and compression percent
965 //Update Main Stats
966 list( $stats['stats']['size_before'], $stats['stats']['size_after'], $stats['stats']['time'], $stats['stats']['percent'], $stats['stats']['bytes'] ) =
967 $this->_update_stats_data( $data, $stats['stats']['size_before'], $stats['stats']['size_after'], $stats['stats']['time'], $stats['stats']['bytes'] );
968
969
970 //Update stats for each size
971 if ( isset( $stats['sizes'] ) ) {
972
973 //if stats for a particular size doesn't exists
974 if ( empty( $stats['sizes'][ $image_size ] ) ) {
975 //Update size wise details
976 $stats['sizes'][ $image_size ] = (object) $this->_array_fill_placeholders( $this->_get_size_signature(), (array) $data );
977 } else {
978 //Update compression percent and bytes saved for each size
979 $stats['sizes'][ $image_size ]->bytes = $stats['sizes'][ $image_size ]->bytes + $data->bytes_saved;
980 $stats['sizes'][ $image_size ]->percent = $stats['sizes'][ $image_size ]->percent + $data->compression;
981 }
982 }
983 } else {
984 //Create new stats
985 $stats = array(
986 "stats" => array_merge( $this->_get_size_signature(), array(
987 'api_version' => - 1,
988 'lossy' => - 1
989 )
990 ),
991 'sizes' => array()
992 );
993 $stats['stats']['api_version'] = $data->api_version;
994 $stats['stats']['lossy'] = $data->lossy;
995 //Update Main Stats
996 list( $stats['stats']['size_before'], $stats['stats']['size_after'], $stats['stats']['time'], $stats['stats']['percent'], $stats['stats']['bytes'] ) =
997 array( $data->before_size, $data->after_size, $data->time, $data->compression, $data->bytes_saved );
998 //Update size wise details
999 $stats['sizes'][ $image_size ] = (object) $this->_array_fill_placeholders( $this->_get_size_signature(), (array) $data );
1000 }
1001 //Calculate Percent
1002 update_post_meta( $id, $this->smushed_meta_key, $stats );
1003
1004 }
1005
1006 /**
1007 * Smush Retina images for WP Retina 2x, Update Stats
1008 *
1009 * @param $id
1010 * @param $retina_file
1011 * @param $image_size
1012 */
1013 function smush_retina_image( $id, $retina_file, $image_size ) {
1014
1015 /**
1016 * Allows to Enable/Disable WP Retina 2x Integration
1017 */
1018 $smush_retina_images = apply_filters( 'smush_retina_images', true );
1019
1020 //Check if Smush retina images is enbled
1021 if ( ! $smush_retina_images ) {
1022 return;
1023 }
1024 //Check for Empty fields
1025 if ( empty( $id ) || empty( $retina_file ) || empty( $image_size ) ) {
1026 return;
1027 }
1028
1029 /**
1030 * Allows to skip a image from smushing
1031 *
1032 * @param bool , Smush image or not
1033 * @$size string, Size of image being smushed
1034 */
1035 $smush_image = apply_filters( 'wp_smush_media_image', true, $image_size );
1036 if ( ! $smush_image ) {
1037 return;
1038 }
1039
1040 $stats = $this->do_smushit( $retina_file );
1041 //If we squeezed out something, Update stats
1042 if ( !is_wp_error( $stats ) && ! empty( $stats['data'] ) && isset( $stats['data'] ) && $stats['data']->bytes_saved > 0 ) {
1043 $this->update_smush_stats_single( $id, $stats, $image_size );
1044 }
1045 }
1046
1047 /**
1048 * Return a list of images not smushed and reason
1049 * @param $image_id
1050 * @param $size_stats
1051 * @param $attachment_metadata
1052 *
1053 * @return array
1054 */
1055 function get_skipped_images( $image_id, $size_stats, $attachment_metadata ) {
1056 $skipped = array();
1057
1058 //Get a list of all the sizes, Show skipped images
1059 $media_size = get_intermediate_image_sizes();
1060
1061 //Full size
1062 $full_image = get_attached_file( $image_id );
1063
1064 //If full image was not smushed, reason 1. Large Size logic, 2. Free and greater than 1Mb
1065 if ( ! array_key_exists( 'full', $size_stats ) ) {
1066 //For free version, Check the image size
1067 if ( ! $this->is_pro() ) {
1068 //For free version, check if full size is greater than 1 Mb, show the skipped status
1069 $file_size = file_exists( $full_image ) ? filesize( $full_image ) : '';
1070 if ( !empty( $file_size ) && ( $file_size / WP_SMUSH_MAX_BYTES ) > 1 ) {
1071 $skipped[] = array(
1072 'size' => 'full',
1073 'reason' => 'size_limit'
1074 );
1075 }else{
1076 $skipped[] = array(
1077 'size' => 'full',
1078 'reason' => 'large_size'
1079 );
1080 }
1081 } else {
1082 //Paid version, Check if we have large size
1083 if ( array_key_exists( 'large', $size_stats ) ) {
1084 $skipped[] = array(
1085 'size' => 'full',
1086 'reason' => 'large_size'
1087 );
1088 }
1089
1090 }
1091 }
1092 //For other sizes, check if the image was generated and not available in stats
1093 if ( is_array( $media_size ) ) {
1094 $dir_path = trailingslashit( dirname( $full_image ) );
1095 foreach ( $media_size as $size ) {
1096 if ( array_key_exists( $size, $attachment_metadata['sizes'] ) && ! array_key_exists( $size, $size_stats ) && ! empty( $size['file'] ) ) {
1097 //Image Path
1098 $img_path = $dir_path . $size['file'];
1099 $image_size = file_exists( $img_path ) ? filesize( $img_path ) : '';
1100 if ( ! empty( $image_size ) && ( $image_size / WP_SMUSH_MAX_BYTES ) > 1 ) {
1101 $skipped[] = array(
1102 'size' => 'full',
1103 'reason' => 'size_limit'
1104 );
1105 }
1106 }
1107 }
1108 }
1109 return $skipped;
1110 }
1111
1112 /**
1113 * Skip messages respective to their ids
1114 * @param $msg_id
1115 *
1116 * @return bool
1117 */
1118 function skip_reason( $msg_id ) {
1119 $count = count( get_intermediate_image_sizes() );
1120 $smush_orgnl_txt = sprintf( esc_html__("When you upload an image to WordPress it automatically creates %s thumbnail sizes that are commonly used in your pages. WordPress also stores the original full-size image, but because these are not usually embedded on your site we don’t Smush them. Pro users can override this.", 'wp_smushit'), $count );
1121 $skip_msg = array(
1122 'large_size' => $smush_orgnl_txt,
1123 'size_limit' => esc_html__( "Image couldn't be smushed as it exceeded the 1Mb size limit, Pro users can smush images with size upto 32Mb.", "wp-smushit" )
1124 );
1125 $skip_rsn = !empty( $skip_msg[$msg_id ] ) ? esc_html__(" Skipped", 'wp-smushit', 'wp-smushit'): '';
1126 $skip_rsn = ! empty( $skip_rsn ) ? $skip_rsn . '<span class="dashicons dashicons-editor-help" title="' . $skip_msg[ $msg_id ] . '"></span>' : '';
1127 return $skip_rsn;
1128 }
1129
1130 /**
1131 * Shows the image size and the compression for each of them
1132 * @param $image_id
1133 * @param $wp_smush_data
1134 *
1135 * @return string
1136 */
1137 function get_detailed_stats( $image_id, $wp_smush_data, $attachment_metadata ) {
1138
1139 $stats = '<div id="smush-stats-' . $image_id . '" class="smush-stats-wrapper hidden">
1140 <table class="wp-smush-stats-holder">
1141 <thead>
1142 <tr>
1143 <th><strong>' . esc_html__( 'Image size', 'wp-smushit' ) . '</strong></th>
1144 <th><strong>' . esc_html__( 'Savings', 'wp-smushit' ) . '</strong></th>
1145 </tr>
1146 </thead>
1147 <tbody>';
1148 $size_stats = $wp_smush_data['sizes'];
1149
1150 //Reorder Sizes as per the maximum savings
1151 uasort( $size_stats, array( $this, "cmp" ) );
1152
1153 if ( ! empty( $attachment_metadata['sizes'] ) ) {
1154 //Get skipped images
1155 $skipped = $this->get_skipped_images( $image_id, $size_stats, $attachment_metadata );
1156
1157 if ( ! empty( $skipped ) ) {
1158 foreach ( $skipped as $img_data ) {
1159 $skip_class = $img_data['reason'] == 'size_limit' ? ' error' : '';
1160 $stats .= '<tr>
1161 <td>' . strtoupper( $img_data['size'] ) . '</td>
1162 <td class="smush-skipped' . $skip_class . '">' . $this->skip_reason( $img_data['reason'] ) . '</td>
1163 </tr>';
1164 }
1165
1166 }
1167 }
1168 //Show Sizes and their compression
1169 foreach ( $size_stats as $size_key => $size_value ) {
1170 if ( $size_value->bytes > 0 ) {
1171 $stats .= '<tr>
1172 <td>' . strtoupper( $size_key ) . '</td>
1173 <td>' . $this->format_bytes( $size_value->bytes ) . ' ( ' . $size_value->percent . '% )</td>
1174 </tr>';
1175 }
1176 }
1177 $stats .= '</tbody>
1178 </table>
1179 </div>';
1180
1181 return $stats;
1182 }
1183
1184 /**
1185 * Compare Values
1186 *
1187 * @param $a
1188 * @param $b
1189 *
1190 * @return int
1191 */
1192 function cmp( $a, $b ) {
1193 return $a->bytes < $b->bytes;
1194 }
1195
1196 /**
1197 * Check if NextGen is active or not
1198 * Include and instantiate classes
1199 */
1200 function load_nextgen() {
1201 if ( ! class_exists( 'C_NextGEN_Bootstrap' ) || ! $this->is_pro() ) {
1202 return;
1203 }
1204 //Check if integration is Enabled or not
1205 //Smush NextGen key
1206 $opt_nextgen = WP_SMUSH_PREFIX . 'nextgen';
1207 $opt_nextgen_val = get_option( $opt_nextgen, 1 );
1208 if ( ! $opt_nextgen_val ) {
1209 return;
1210 }
1211
1212 require_once( WP_SMUSH_DIR . '/lib/class-wp-smush-nextgen.php' );
1213 require_once( WP_SMUSH_DIR . '/lib/nextgen-integration/class-wp-smush-nextgen-admin.php' );
1214 require_once( WP_SMUSH_DIR . '/lib/nextgen-integration/class-wp-smush-nextgen-stats.php' );
1215 require_once( WP_SMUSH_DIR . '/lib/nextgen-integration/class-wp-smush-nextgen-bulk.php' );
1216
1217 global $wpsmushnextgen, $wpsmushnextgenadmin, $wpsmushnextgenstats;
1218 //Initialize Nextgen support
1219 $wpsmushnextgen = new WpSmushNextGen();
1220 $wpsmushnextgenadmin = new WpSmushNextGenAdmin();
1221 $wpsmushnextgenstats = new WpSmushNextGenStats();
1222 new WPSmushNextGenBulk();
1223 }
1224
1225 /**
1226 * Add the Smushit Column to sortable list
1227 * @param $columns
1228 *
1229 * @return mixed
1230 */
1231 function sortable_column( $columns ) {
1232 $columns['smushit'] = 'smushit';
1233 return $columns;
1234 }
1235 /**
1236 * Orderby query for smush columns
1237 */
1238 function smushit_orderby( $query ) {
1239
1240 global $current_screen, $wpdb;
1241
1242 //Filter only media screen
1243 if ( ! is_admin() || ( !empty( $current_screen) && $current_screen->base != 'upload' ) ) {
1244 return;
1245 }
1246
1247 $orderby = $query->get( 'orderby' );
1248
1249 if ( isset( $orderby ) && 'smushit' == $orderby ) {
1250 $query->set( 'meta_query', array(
1251 'relation' => 'OR',
1252 array(
1253 'key' => $this->smushed_meta_key,
1254 'compare' => 'EXISTS'
1255 ),
1256 array(
1257 'key' => $this->smushed_meta_key,
1258 'compare' => 'NOT EXISTS'
1259 )
1260 ) );
1261 $query->set( 'orderby', 'meta_value_num' );
1262 }
1263 return $query;
1264
1265 }
1266 }
1267
1268 global $WpSmush;
1269 $WpSmush = new WpSmush();
1270
1271 }
1272
1273 //Include Admin classes
1274 require_once( WP_SMUSH_DIR . 'lib/class-wp-smush-bulk.php' );
1275 require_once( WP_SMUSH_DIR . 'lib/class-wp-smush-admin.php' );