PluginProbe ʕ •ᴥ•ʔ
TinyPNG – JPEG, PNG & WebP image compression / 2.2.4
TinyPNG – JPEG, PNG & WebP image compression v2.2.4
3.7.0 3.6.14 trunk 1.0.0 1.1.0 1.2.0 1.2.1 1.3.0 1.3.1 1.3.2 1.4.0 1.5.0 1.6.0 1.7.0 1.7.1 1.7.2 2.0.0 2.0.1 2.0.2 2.1.0 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 3.0.0 3.0.1 3.1.0 3.2.0 3.2.1 3.3 3.4 3.4.1 3.4.2 3.4.4 3.4.5 3.4.6 3.5.0 3.5.1 3.5.2 3.6.0 3.6.1 3.6.10 3.6.11 3.6.12 3.6.13 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.6.9
tiny-compress-images / src / class-tiny-image.php
tiny-compress-images / src Last commit date
config 9 years ago css 8 years ago data 9 years ago images 9 years ago js 8 years ago vendor 9 years ago views 8 years ago class-tiny-compress-client.php 9 years ago class-tiny-compress-fopen.php 9 years ago class-tiny-compress.php 9 years ago class-tiny-exception.php 9 years ago class-tiny-image-size.php 9 years ago class-tiny-image.php 9 years ago class-tiny-notices.php 9 years ago class-tiny-php.php 9 years ago class-tiny-plugin.php 8 years ago class-tiny-settings.php 8 years ago class-tiny-wp-base.php 9 years ago
class-tiny-image.php
480 lines
1 <?php
2 /*
3 * Tiny Compress Images - WordPress plugin.
4 * Copyright (C) 2015-2017 Voormedia B.V.
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the Free
8 * Software Foundation; either version 2 of the License, or (at your option)
9 * any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 * more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc., 51
18 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 class Tiny_Image {
22 const META_KEY = 'tiny_compress_images';
23 const ORIGINAL = 0;
24
25 private $settings;
26 private $id;
27 private $name;
28 private $wp_metadata;
29 private $sizes = array();
30 private $statistics = array();
31
32 public function __construct( $settings, $id, $wp_metadata = null, $tiny_metadata = null ) {
33 $this->settings = $settings;
34 $this->id = $id;
35 $this->wp_metadata = $wp_metadata;
36 $this->parse_wp_metadata();
37 $this->parse_tiny_metadata( $tiny_metadata );
38 $this->detect_duplicates();
39 }
40
41 private function parse_wp_metadata() {
42 if ( ! is_array( $this->wp_metadata ) ) {
43 $this->wp_metadata = wp_get_attachment_metadata( $this->id );
44 }
45 if ( ! is_array( $this->wp_metadata ) ) {
46 return;
47 }
48 $path_info = pathinfo( $this->wp_metadata['file'] );
49 $this->name = $path_info['basename'];
50
51 $upload_dir = wp_upload_dir();
52 $path_prefix = $upload_dir['basedir'] . '/';
53 if ( isset( $path_info['dirname'] ) ) {
54 $path_prefix .= $path_info['dirname'] . '/';
55 }
56
57 $filename = $path_prefix . $this->name;
58 $this->sizes[ self::ORIGINAL ] = new Tiny_Image_Size( $filename );
59
60 if ( isset( $this->wp_metadata['sizes'] ) && is_array( $this->wp_metadata['sizes'] ) ) {
61 foreach ( $this->wp_metadata['sizes'] as $size_name => $info ) {
62 $this->sizes[ $size_name ] = new Tiny_Image_Size( $path_prefix . $info['file'] );
63 }
64 }
65 }
66
67 private function detect_duplicates() {
68 $filenames = array();
69
70 if ( is_array( $this->wp_metadata )
71 && isset( $this->wp_metadata['sizes'] )
72 && is_array( $this->wp_metadata['sizes'] ) ) {
73
74 $active_sizes = $this->settings->get_sizes();
75 $active_tinify_sizes = $this->settings->get_active_tinify_sizes();
76
77 foreach ( $this->wp_metadata['sizes'] as $size_name => $size ) {
78 if ( $this->sizes[ $size_name ]->has_been_compressed()
79 && array_key_exists( $size_name, $active_sizes ) ) {
80 $filenames = $this->duplicate_check( $filenames, $size['file'], $size_name );
81 }
82 }
83 foreach ( $this->wp_metadata['sizes'] as $size_name => $size ) {
84 if ( in_array( $size_name, $active_tinify_sizes, true ) ) {
85 $filenames = $this->duplicate_check( $filenames, $size['file'], $size_name );
86 }
87 }
88 foreach ( $this->wp_metadata['sizes'] as $size_name => $size ) {
89 if ( array_key_exists( $size_name, $active_sizes ) ) {
90 $filenames = $this->duplicate_check( $filenames, $size['file'], $size_name );
91 }
92 }
93 foreach ( $this->wp_metadata['sizes'] as $size_name => $size ) {
94 $filenames = $this->duplicate_check( $filenames, $size['file'], $size_name );
95 }
96 }
97 }
98
99 private function duplicate_check( $filenames, $file, $size_name ) {
100 if ( isset( $filenames[ $file ] ) ) {
101 if ( $filenames[ $file ] != $size_name ) {
102 $this->sizes[ $size_name ]->mark_duplicate( $filenames[ $file ] );
103 }
104 } else {
105 $filenames[ $file ] = $size_name;
106 }
107 return $filenames;
108 }
109
110 private function parse_tiny_metadata( $tiny_metadata ) {
111 if ( is_null( $tiny_metadata ) ) {
112 $tiny_metadata = get_post_meta( $this->id, self::META_KEY, true );
113 }
114 if ( $tiny_metadata ) {
115 foreach ( $tiny_metadata as $size => $meta ) {
116 if ( ! isset( $this->sizes[ $size ] ) ) {
117 if ( self::is_retina( $size ) && Tiny_Settings::wr2x_active() ) {
118 $retina_path = wr2x_get_retina(
119 $this->sizes[ rtrim( $size, '_wr2x' ) ]->filename
120 );
121 $this->sizes[ $size ] = new Tiny_Image_Size( $retina_path );
122 } else {
123 $this->sizes[ $size ] = new Tiny_Image_Size();
124 }
125 }
126 $this->sizes[ $size ]->meta = $meta;
127 }
128 }
129 }
130
131 public function get_id() {
132 return $this->id;
133 }
134
135 public function get_name() {
136 return $this->name;
137 }
138
139 public function get_wp_metadata() {
140 return $this->wp_metadata;
141 }
142
143 public function file_type_allowed() {
144 return in_array( $this->get_mime_type(), array( 'image/jpeg', 'image/png' ) );
145 }
146
147 public function get_mime_type() {
148 return get_post_mime_type( $this->id );
149 }
150
151 public function compress() {
152 if ( $this->settings->get_compressor() === null || ! $this->file_type_allowed() ) {
153 return;
154 }
155
156 $success = 0;
157 $failed = 0;
158
159 $compressor = $this->settings->get_compressor();
160 $active_tinify_sizes = $this->settings->get_active_tinify_sizes();
161 $uncompressed_sizes = $this->filter_image_sizes( 'uncompressed', $active_tinify_sizes );
162
163 foreach ( $uncompressed_sizes as $size_name => $size ) {
164 if ( ! $size->is_duplicate() ) {
165 $size->add_tiny_meta_start();
166 $this->update_tiny_post_meta();
167 $resize = $this->settings->get_resize_options( $size_name );
168 $preserve = $this->settings->get_preserve_options( $size_name );
169 try {
170 $response = $compressor->compress_file( $size->filename, $resize, $preserve );
171 $size->add_tiny_meta( $response );
172 $success++;
173 } catch ( Tiny_Exception $e ) {
174 $size->add_tiny_meta_error( $e );
175 $failed++;
176 }
177 $this->add_wp_metadata( $size_name, $size );
178 $this->update_tiny_post_meta();
179 }
180 }
181 return array(
182 'success' => $success,
183 'failed' => $failed,
184 );
185 }
186
187 public function compress_retina( $size_name, $path ) {
188 if ( $this->settings->get_compressor() === null || ! $this->file_type_allowed() ) {
189 return;
190 }
191
192 if ( ! isset( $this->sizes[ $size_name ] ) ) {
193 $this->sizes[ $size_name ] = new Tiny_Image_Size( $path );
194 }
195 $size = $this->sizes[ $size_name ];
196
197 if ( ! $size->has_been_compressed() ) {
198 $size->add_tiny_meta_start();
199 $this->update_tiny_post_meta();
200 $compressor = $this->settings->get_compressor();
201 $preserve = $this->settings->get_preserve_options( $size_name );
202
203 try {
204 $response = $compressor->compress_file( $path, false, $preserve );
205 $size->add_tiny_meta( $response );
206 } catch ( Tiny_Exception $e ) {
207 $size->add_tiny_meta_error( $e );
208 }
209 $this->update_tiny_post_meta();
210 }
211 }
212
213 public function remove_retina_metadata() {
214 // Remove metadata from all sizes, as this callback only fires when all
215 // retina sizes are deleted.
216 foreach ( $this->sizes as $size_name => $size ) {
217 if ( self::is_retina( $size_name ) ) {
218 unset( $this->sizes[ $size_name ] );
219 }
220 }
221 $this->update_tiny_post_meta();
222 }
223
224 public function add_wp_metadata( $size_name, $size ) {
225 if ( self::is_original( $size_name ) ) {
226 if ( isset( $size->meta['output'] ) ) {
227 $output = $size->meta['output'];
228 if ( isset( $output['width'] ) && isset( $output['height'] ) ) {
229 $this->wp_metadata['width'] = $output['width'];
230 $this->wp_metadata['height'] = $output['height'];
231 }
232 }
233 }
234 }
235
236 public function update_tiny_post_meta() {
237 $tiny_metadata = array();
238 foreach ( $this->sizes as $size_name => $size ) {
239 $tiny_metadata[ $size_name ] = $size->meta;
240 }
241 update_post_meta( $this->id, self::META_KEY, $tiny_metadata );
242 }
243
244 public function get_image_sizes() {
245 $original = isset( $this->sizes[ self::ORIGINAL ] )
246 ? array(
247 self::ORIGINAL => $this->sizes[ self::ORIGINAL ],
248 )
249 : array();
250 $compressed = array();
251 $uncompressed = array();
252 foreach ( $this->sizes as $size_name => $size ) {
253 if ( self::is_original( $size_name ) ) {
254 continue;
255 }
256
257 if ( $size->has_been_compressed() ) {
258 $compressed[ $size_name ] = $size;
259 } else {
260 $uncompressed[ $size_name ] = $size;
261 }
262 }
263 ksort( $compressed );
264 ksort( $uncompressed );
265 return $original + $compressed + $uncompressed;
266 }
267
268 public function get_image_size( $size = self::ORIGINAL, $create = false ) {
269 if ( isset( $this->sizes[ $size ] ) ) {
270 return $this->sizes[ $size ];
271 } elseif ( $create ) {
272 return new Tiny_Image_Size();
273 } else {
274 return null;
275 }
276 }
277
278 public function filter_image_sizes( $method, $filter_sizes = null ) {
279 $selection = array();
280 if ( is_null( $filter_sizes ) ) {
281 $filter_sizes = array_keys( $this->sizes );
282 }
283 foreach ( $filter_sizes as $size_name ) {
284 if ( ! isset( $this->sizes[ $size_name ] ) ) {
285 continue;
286 }
287
288 $tiny_image_size = $this->sizes[ $size_name ];
289 if ( $tiny_image_size->$method() ) {
290 $selection[ $size_name ] = $tiny_image_size;
291 }
292 }
293 return $selection;
294 }
295
296 public function get_count( $methods, $count_sizes = null ) {
297 $stats = array_fill_keys( $methods, 0 );
298 if ( is_null( $count_sizes ) ) {
299 $count_sizes = array_keys( $this->sizes );
300 }
301 foreach ( $count_sizes as $size ) {
302 if ( ! isset( $this->sizes[ $size ] ) ) {
303 continue;
304 }
305
306 foreach ( $methods as $method ) {
307 if ( $this->sizes[ $size ]->$method() ) {
308 $stats[ $method ]++;
309 }
310 }
311 }
312 return $stats;
313 }
314
315 public function get_latest_error() {
316 $active_tinify_sizes = $this->settings->get_active_tinify_sizes();
317 $error_message = null;
318 $last_timestamp = null;
319 foreach ( $this->sizes as $size_name => $size ) {
320 if ( in_array( $size_name, $active_tinify_sizes, true ) ) {
321 if ( isset( $size->meta['error'] ) && isset( $size->meta['message'] ) ) {
322 if ( null === $last_timestamp || $last_timestamp < $size->meta['timestamp'] ) {
323 $last_timestamp = $size->meta['timestamp'];
324 $error_message = $size->meta['message'];
325 }
326 }
327 }
328 }
329 return $error_message;
330 }
331
332 public function get_savings( $stats ) {
333 $before = $stats['initial_total_size'];
334 $after = $stats['optimized_total_size'];
335 if ( 0 === $before ) {
336 $savings = 0;
337 } else {
338 $savings = ($before - $after) / $before * 100;
339 }
340 return '' . number_format( $savings, 1 );
341 }
342
343 public function get_statistics() {
344 if ( $this->statistics ) {
345 error_log( 'Strangely the image statistics are asked for again.' );
346 return $this->statistics;
347 }
348
349 $this->statistics['initial_total_size'] = 0;
350 $this->statistics['optimized_total_size'] = 0;
351 $this->statistics['image_sizes_optimized'] = 0;
352 $this->statistics['available_unoptimized_sizes'] = 0;
353
354 $active_sizes = $this->settings->get_sizes();
355 $active_tinify_sizes = $this->settings->get_active_tinify_sizes();
356
357 foreach ( $this->sizes as $size_name => $size ) {
358 if ( ! $size->is_duplicate() ) {
359 if ( array_key_exists( $size_name, $active_sizes ) ) {
360 if ( isset( $size->meta['input'] ) ) {
361 $input = $size->meta['input'];
362 $this->statistics['initial_total_size'] += intval( $input['size'] );
363
364 if ( isset( $size->meta['output'] ) ) {
365 $output = $size->meta['output'];
366 if ( $size->modified() ) {
367 $this->statistics['optimized_total_size'] += $size->filesize();
368 if ( in_array( $size_name, $active_tinify_sizes, true ) ) {
369 $this->statistics['available_unoptimized_sizes'] += 1;
370 }
371 } else {
372 $this->statistics['optimized_total_size']
373 += intval( $output['size'] );
374 $this->statistics['image_sizes_optimized'] += 1;
375 }
376 } else {
377 $this->statistics['optimized_total_size'] += intval( $input['size'] );
378 }
379 } elseif ( $size->exists() ) {
380 $this->statistics['initial_total_size'] += $size->filesize();
381 $this->statistics['optimized_total_size'] += $size->filesize();
382 if ( in_array( $size_name, $active_tinify_sizes, true ) ) {
383 $this->statistics['available_unoptimized_sizes'] += 1;
384 }
385 }
386 }
387 }
388 }
389
390 return $this->statistics;
391 }
392
393 public static function get_optimization_statistics( $settings, $result = null ) {
394 global $wpdb;
395
396 if ( is_null( $result ) ) {
397 // Select posts that have "_wp_attachment_metadata" image metadata
398 // and optionally contain "tiny_compress_images" metadata.
399 $query =
400 "SELECT
401 $wpdb->posts.ID,
402 $wpdb->posts.post_title,
403 $wpdb->postmeta.meta_value,
404 wp_postmeta_file.meta_value AS unique_attachment_name,
405 wp_postmeta_tiny.meta_value AS tiny_meta_value
406 FROM $wpdb->posts
407 LEFT JOIN $wpdb->postmeta
408 ON $wpdb->posts.ID = $wpdb->postmeta.post_id
409 LEFT JOIN $wpdb->postmeta AS wp_postmeta_file
410 ON $wpdb->posts.ID = wp_postmeta_file.post_id
411 AND wp_postmeta_file.meta_key = '_wp_attached_file'
412 LEFT JOIN $wpdb->postmeta AS wp_postmeta_tiny
413 ON $wpdb->posts.ID = wp_postmeta_tiny.post_id
414 AND wp_postmeta_tiny.meta_key = '" . self::META_KEY . "'
415 WHERE $wpdb->posts.post_type = 'attachment'
416 AND (
417 $wpdb->posts.post_mime_type = 'image/jpeg' OR
418 $wpdb->posts.post_mime_type = 'image/png'
419 )
420 AND $wpdb->postmeta.meta_key = '_wp_attachment_metadata'
421 GROUP BY unique_attachment_name
422 ORDER BY ID DESC";
423
424 $result = $wpdb->get_results( $query, ARRAY_A ); // WPCS: unprepared SQL OK.
425 }
426
427 $stats = array();
428 $stats['uploaded-images'] = 0;
429 $stats['optimized-image-sizes'] = 0;
430 $stats['available-unoptimised-sizes'] = 0;
431 $stats['optimized-library-size'] = 0;
432 $stats['unoptimized-library-size'] = 0;
433 $stats['available-for-optimization'] = array();
434
435 for ( $i = 0; $i < sizeof( $result ); $i++ ) {
436 $wp_metadata = unserialize( $result[ $i ]['meta_value'] );
437 $tiny_metadata = unserialize( $result[ $i ]['tiny_meta_value'] );
438 if ( ! is_array( $tiny_metadata ) ) {
439 $tiny_metadata = array();
440 }
441 $tiny_image = new Tiny_Image(
442 $settings,
443 $result[ $i ]['ID'],
444 $wp_metadata,
445 $tiny_metadata
446 );
447 $image_stats = $tiny_image->get_statistics();
448 $stats['uploaded-images']++;
449 $stats['available-unoptimised-sizes'] += $image_stats['available_unoptimized_sizes'];
450 $stats['optimized-image-sizes'] += $image_stats['image_sizes_optimized'];
451 $stats['optimized-library-size'] += $image_stats['optimized_total_size'];
452 $stats['unoptimized-library-size'] += $image_stats['initial_total_size'];
453 if ( $image_stats['available_unoptimized_sizes'] > 0 ) {
454 $stats['available-for-optimization'][] = array(
455 'ID' => $result[ $i ]['ID'],
456 'post_title' => $result[ $i ]['post_title'],
457 );
458 }
459 }
460
461 if ( 0 != $stats['unoptimized-library-size'] ) {
462 $stats['display-percentage'] = round(
463 100 -
464 ( $stats['optimized-library-size'] / $stats['unoptimized-library-size'] * 100 ), 1
465 );
466 } else {
467 $stats['display-percentage'] = 0;
468 }
469 return $stats;
470 }
471
472 public static function is_original( $size ) {
473 return self::ORIGINAL === $size;
474 }
475
476 public static function is_retina( $size ) {
477 return strrpos( $size, 'wr2x' ) === strlen( $size ) - strlen( 'wr2x' );
478 }
479 }
480