PluginProbe ʕ •ᴥ•ʔ
TinyPNG – JPEG, PNG & WebP image compression / 2.2.0
TinyPNG – JPEG, PNG & WebP image compression v2.2.0
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 9 years ago data 9 years ago images 9 years ago js 9 years ago vendor 9 years ago views 9 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 9 years ago class-tiny-settings.php 9 years ago class-tiny-wp-base.php 9 years ago
class-tiny-image.php
475 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( 'success' => $success, 'failed' => $failed );
182 }
183
184 public function compress_retina( $size_name, $path ) {
185 if ( $this->settings->get_compressor() === null || ! $this->file_type_allowed() ) {
186 return;
187 }
188
189 if ( ! isset( $this->sizes[ $size_name ] ) ) {
190 $this->sizes[ $size_name ] = new Tiny_Image_Size( $path );
191 }
192 $size = $this->sizes[ $size_name ];
193
194 if ( ! $size->has_been_compressed() ) {
195 $size->add_tiny_meta_start();
196 $this->update_tiny_post_meta();
197 $compressor = $this->settings->get_compressor();
198 $preserve = $this->settings->get_preserve_options( $size_name );
199
200 try {
201 $response = $compressor->compress_file( $path, false, $preserve );
202 $size->add_tiny_meta( $response );
203 } catch (Tiny_Exception $e) {
204 $size->add_tiny_meta_error( $e );
205 }
206 $this->update_tiny_post_meta();
207 }
208 }
209
210 public function remove_retina_metadata() {
211 // Remove metadata from all sizes, as this callback only fires when all
212 // retina sizes are deleted.
213 foreach ( $this->sizes as $size_name => $size ) {
214 if ( self::is_retina( $size_name ) ) {
215 unset( $this->sizes[ $size_name ] );
216 }
217 }
218 $this->update_tiny_post_meta();
219 }
220
221 public function add_wp_metadata( $size_name, $size ) {
222 if ( self::is_original( $size_name ) ) {
223 if ( isset( $size->meta['output'] ) ) {
224 $output = $size->meta['output'];
225 if ( isset( $output['width'] ) && isset( $output['height'] ) ) {
226 $this->wp_metadata['width'] = $output['width'];
227 $this->wp_metadata['height'] = $output['height'];
228 }
229 }
230 }
231 }
232
233 public function update_tiny_post_meta() {
234 $tiny_metadata = array();
235 foreach ( $this->sizes as $size_name => $size ) {
236 $tiny_metadata[ $size_name ] = $size->meta;
237 }
238 update_post_meta( $this->id, self::META_KEY, $tiny_metadata );
239 }
240
241 public function get_image_sizes() {
242 $original = isset( $this->sizes[ self::ORIGINAL ] )
243 ? array( self::ORIGINAL => $this->sizes[ self::ORIGINAL ] )
244 : array();
245 $compressed = array();
246 $uncompressed = array();
247 foreach ( $this->sizes as $size_name => $size ) {
248 if ( self::is_original( $size_name ) ) {
249 continue;
250 }
251
252 if ( $size->has_been_compressed() ) {
253 $compressed[ $size_name ] = $size;
254 } else {
255 $uncompressed[ $size_name ] = $size;
256 }
257 }
258 ksort( $compressed );
259 ksort( $uncompressed );
260 return $original + $compressed + $uncompressed;
261 }
262
263 public function get_image_size( $size = self::ORIGINAL, $create = false ) {
264 if ( isset( $this->sizes[ $size ] ) ) {
265 return $this->sizes[ $size ];
266 } elseif ( $create ) {
267 return new Tiny_Image_Size();
268 } else {
269 return null;
270 }
271 }
272
273 public function filter_image_sizes( $method, $filter_sizes = null ) {
274 $selection = array();
275 if ( is_null( $filter_sizes ) ) {
276 $filter_sizes = array_keys( $this->sizes );
277 }
278 foreach ( $filter_sizes as $size_name ) {
279 if ( ! isset( $this->sizes[ $size_name ] ) ) {
280 continue;
281 }
282
283 $tiny_image_size = $this->sizes[ $size_name ];
284 if ( $tiny_image_size->$method() ) {
285 $selection[ $size_name ] = $tiny_image_size;
286 }
287 }
288 return $selection;
289 }
290
291 public function get_count( $methods, $count_sizes = null ) {
292 $stats = array_fill_keys( $methods, 0 );
293 if ( is_null( $count_sizes ) ) {
294 $count_sizes = array_keys( $this->sizes );
295 }
296 foreach ( $count_sizes as $size ) {
297 if ( ! isset( $this->sizes[ $size ] ) ) {
298 continue;
299 }
300
301 foreach ( $methods as $method ) {
302 if ( $this->sizes[ $size ]->$method() ) {
303 $stats[ $method ]++;
304 }
305 }
306 }
307 return $stats;
308 }
309
310 public function get_latest_error() {
311 $active_tinify_sizes = $this->settings->get_active_tinify_sizes();
312 $error_message = null;
313 $last_timestamp = null;
314 foreach ( $this->sizes as $size_name => $size ) {
315 if ( in_array( $size_name, $active_tinify_sizes, true ) ) {
316 if ( isset( $size->meta['error'] ) && isset( $size->meta['message'] ) ) {
317 if ( null === $last_timestamp || $last_timestamp < $size->meta['timestamp'] ) {
318 $last_timestamp = $size->meta['timestamp'];
319 $error_message = $size->meta['message'];
320 }
321 }
322 }
323 }
324 return $error_message;
325 }
326
327 public function get_savings( $stats ) {
328 $before = $stats['initial_total_size'];
329 $after = $stats['optimized_total_size'];
330 if ( 0 === $before ) {
331 $savings = 0;
332 } else {
333 $savings = ($before - $after) / $before * 100;
334 }
335 return '' . number_format( $savings, 1 );
336 }
337
338 public function get_statistics() {
339 if ( $this->statistics ) {
340 error_log( 'Strangely the image statistics are asked for again.' );
341 return $this->statistics;
342 }
343
344 $this->statistics['initial_total_size'] = 0;
345 $this->statistics['optimized_total_size'] = 0;
346 $this->statistics['image_sizes_optimized'] = 0;
347 $this->statistics['available_unoptimized_sizes'] = 0;
348
349 $active_sizes = $this->settings->get_sizes();
350 $active_tinify_sizes = $this->settings->get_active_tinify_sizes();
351
352 foreach ( $this->sizes as $size_name => $size ) {
353 if ( ! $size->is_duplicate() ) {
354 if ( array_key_exists( $size_name, $active_sizes ) ) {
355 if ( isset( $size->meta['input'] ) ) {
356 $input = $size->meta['input'];
357 $this->statistics['initial_total_size'] += intval( $input['size'] );
358
359 if ( isset( $size->meta['output'] ) ) {
360 $output = $size->meta['output'];
361 if ( $size->modified() ) {
362 $this->statistics['optimized_total_size'] += $size->filesize();
363 if ( in_array( $size_name, $active_tinify_sizes, true ) ) {
364 $this->statistics['available_unoptimized_sizes'] += 1;
365 }
366 } else {
367 $this->statistics['optimized_total_size']
368 += intval( $output['size'] );
369 $this->statistics['image_sizes_optimized'] += 1;
370 }
371 } else {
372 $this->statistics['optimized_total_size'] += intval( $input['size'] );
373 }
374 } elseif ( $size->exists() ) {
375 $this->statistics['initial_total_size'] += $size->filesize();
376 $this->statistics['optimized_total_size'] += $size->filesize();
377 if ( in_array( $size_name, $active_tinify_sizes, true ) ) {
378 $this->statistics['available_unoptimized_sizes'] += 1;
379 }
380 }
381 }
382 }
383 }
384
385 return $this->statistics;
386 }
387
388 public static function get_optimization_statistics( $settings, $result = null ) {
389 global $wpdb;
390
391 if ( is_null( $result ) ) {
392 // Select posts that have "_wp_attachment_metadata" image metadata
393 // and optionally contain "tiny_compress_images" metadata.
394 $query =
395 "SELECT
396 $wpdb->posts.ID,
397 $wpdb->posts.post_title,
398 $wpdb->postmeta.meta_value,
399 wp_postmeta_file.meta_value AS unique_attachment_name,
400 wp_postmeta_tiny.meta_value AS tiny_meta_value
401 FROM $wpdb->posts
402 LEFT JOIN $wpdb->postmeta
403 ON $wpdb->posts.ID = $wpdb->postmeta.post_id
404 LEFT JOIN $wpdb->postmeta AS wp_postmeta_file
405 ON $wpdb->posts.ID = wp_postmeta_file.post_id
406 AND wp_postmeta_file.meta_key = '_wp_attached_file'
407 LEFT JOIN $wpdb->postmeta AS wp_postmeta_tiny
408 ON $wpdb->posts.ID = wp_postmeta_tiny.post_id
409 AND wp_postmeta_tiny.meta_key = '" . self::META_KEY . "'
410 WHERE $wpdb->posts.post_type = 'attachment'
411 AND (
412 $wpdb->posts.post_mime_type = 'image/jpeg' OR
413 $wpdb->posts.post_mime_type = 'image/png'
414 )
415 AND $wpdb->postmeta.meta_key = '_wp_attachment_metadata'
416 GROUP BY unique_attachment_name
417 ORDER BY ID DESC";
418
419 $result = $wpdb->get_results( $query, ARRAY_A ); // WPCS: unprepared SQL OK.
420 }
421
422 $stats = array();
423 $stats['uploaded-images'] = 0;
424 $stats['optimized-image-sizes'] = 0;
425 $stats['available-unoptimised-sizes'] = 0;
426 $stats['optimized-library-size'] = 0;
427 $stats['unoptimized-library-size'] = 0;
428 $stats['available-for-optimization'] = array();
429
430 for ( $i = 0; $i < sizeof( $result ); $i++ ) {
431 $wp_metadata = unserialize( $result[ $i ]['meta_value'] );
432 $tiny_metadata = unserialize( $result[ $i ]['tiny_meta_value'] );
433 if ( ! is_array( $tiny_metadata ) ) {
434 $tiny_metadata = array();
435 }
436 $tiny_image = new Tiny_Image(
437 $settings,
438 $result[ $i ]['ID'],
439 $wp_metadata,
440 $tiny_metadata
441 );
442 $image_stats = $tiny_image->get_statistics();
443 $stats['uploaded-images']++;
444 $stats['available-unoptimised-sizes'] += $image_stats['available_unoptimized_sizes'];
445 $stats['optimized-image-sizes'] += $image_stats['image_sizes_optimized'];
446 $stats['optimized-library-size'] += $image_stats['optimized_total_size'];
447 $stats['unoptimized-library-size'] += $image_stats['initial_total_size'];
448 if ( $image_stats['available_unoptimized_sizes'] > 0 ) {
449 $stats['available-for-optimization'][] = array(
450 'ID' => $result[ $i ]['ID'],
451 'post_title' => $result[ $i ]['post_title'],
452 );
453 }
454 }
455
456 if ( 0 != $stats['unoptimized-library-size'] ) {
457 $stats['display-percentage'] = round(
458 100 -
459 ( $stats['optimized-library-size'] / $stats['unoptimized-library-size'] * 100 ), 1
460 );
461 } else {
462 $stats['display-percentage'] = 0;
463 }
464 return $stats;
465 }
466
467 public static function is_original( $size ) {
468 return self::ORIGINAL === $size;
469 }
470
471 public static function is_retina( $size ) {
472 return strrpos( $size, 'wr2x' ) === strlen( $size ) - strlen( 'wr2x' );
473 }
474 }
475