PluginProbe ʕ •ᴥ•ʔ
TinyPNG – JPEG, PNG & WebP image compression / 3.6.0
TinyPNG – JPEG, PNG & WebP image compression v3.6.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
compatibility 1 year ago config 4 years ago css 1 year ago data 4 years ago images 4 years ago js 1 year ago vendor 1 year ago views 1 year ago class-tiny-bulk-optimization.php 1 year ago class-tiny-compress-client.php 1 year ago class-tiny-compress-fopen.php 1 year ago class-tiny-compress.php 1 year ago class-tiny-exception.php 4 years ago class-tiny-helpers.php 1 year ago class-tiny-image-size.php 1 year ago class-tiny-image.php 1 year ago class-tiny-notices.php 1 year ago class-tiny-php.php 4 years ago class-tiny-picture.php 1 year ago class-tiny-plugin.php 1 year ago class-tiny-settings.php 1 year ago class-tiny-wp-base.php 1 year ago
class-tiny-image.php
475 lines
1 <?php
2 /*
3 * Tiny Compress Images - WordPress plugin.
4 * Copyright (C) 2015-2018 Tinify 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 ORIGINAL = 0;
23
24 private $settings;
25 private $id;
26 private $name;
27 private $wp_metadata;
28 private $sizes = array();
29 private $statistics = array();
30
31 public function __construct(
32 $settings,
33 $id,
34 $wp_metadata = null,
35 $tiny_metadata = null,
36 $active_sizes = null,
37 $active_tinify_sizes = null
38 ) {
39 $this->settings = $settings;
40 $this->id = $id;
41 $this->wp_metadata = $wp_metadata;
42 $this->parse_wp_metadata();
43 $this->parse_tiny_metadata( $tiny_metadata );
44 $this->detect_duplicates( $active_sizes, $active_tinify_sizes );
45 }
46
47 private function parse_wp_metadata() {
48 if ( ! is_array( $this->wp_metadata ) ) {
49 $this->wp_metadata = wp_get_attachment_metadata( $this->id );
50 }
51 if ( ! is_array( $this->wp_metadata ) ) {
52 return;
53 }
54 if ( ! isset( $this->wp_metadata['file'] ) ) {
55 /* No file metadata found, this might be another plugin messing with
56 metadata. Simply ignore this! */
57 return;
58 }
59
60 $upload_dir = wp_upload_dir();
61 $path_prefix = $upload_dir['basedir'] . '/';
62 $path_info = pathinfo( $this->wp_metadata['file'] );
63 if ( isset( $path_info['dirname'] ) ) {
64 $path_prefix .= $path_info['dirname'] . '/';
65 }
66
67 /* Do not use pathinfo for getting the filename.
68 It doesn't work when the filename starts with a special character. */
69 $path_parts = explode( '/', $this->wp_metadata['file'] );
70 $this->name = end( $path_parts );
71 $filename = $path_prefix . $this->name;
72 $this->sizes[ self::ORIGINAL ] = new Tiny_Image_Size( $filename );
73
74 if ( isset( $this->wp_metadata['sizes'] ) && is_array( $this->wp_metadata['sizes'] ) ) {
75 foreach ( $this->wp_metadata['sizes'] as $size_name => $info ) {
76 $this->sizes[ $size_name ] = new Tiny_Image_Size( $path_prefix . $info['file'] );
77 }
78 }
79 }
80
81 private function detect_duplicates( $active_sizes, $active_tinify_sizes ) {
82 $filenames = array();
83
84 if ( is_array( $this->wp_metadata )
85 && isset( $this->wp_metadata['file'] )
86 && isset( $this->wp_metadata['sizes'] )
87 && is_array( $this->wp_metadata['sizes'] ) ) {
88
89 if ( null == $active_sizes ) {
90 $active_sizes = $this->settings->get_sizes();
91 }
92 if ( null == $active_tinify_sizes ) {
93 $active_tinify_sizes = $this->settings->get_active_tinify_sizes();
94 }
95
96 foreach ( $this->wp_metadata['sizes'] as $size_name => $size ) {
97 if ( $this->sizes[ $size_name ]->has_been_compressed()
98 && array_key_exists( $size_name, $active_sizes ) ) {
99 $filenames = $this->duplicate_check( $filenames, $size['file'], $size_name );
100 }
101 }
102 foreach ( $this->wp_metadata['sizes'] as $size_name => $size ) {
103 if ( in_array( $size_name, $active_tinify_sizes, true ) ) {
104 $filenames = $this->duplicate_check( $filenames, $size['file'], $size_name );
105 }
106 }
107 foreach ( $this->wp_metadata['sizes'] as $size_name => $size ) {
108 if ( array_key_exists( $size_name, $active_sizes ) ) {
109 $filenames = $this->duplicate_check( $filenames, $size['file'], $size_name );
110 }
111 }
112 foreach ( $this->wp_metadata['sizes'] as $size_name => $size ) {
113 $filenames = $this->duplicate_check( $filenames, $size['file'], $size_name );
114 }
115 }
116 }
117
118 private function duplicate_check( $filenames, $file, $size_name ) {
119 if ( isset( $filenames[ $file ] ) ) {
120 if ( $filenames[ $file ] != $size_name ) {
121 $this->sizes[ $size_name ]->mark_duplicate( $filenames[ $file ] );
122 }
123 } else {
124 $filenames[ $file ] = $size_name;
125 }
126 return $filenames;
127 }
128
129 private function parse_tiny_metadata( $tiny_metadata ) {
130 if ( is_null( $tiny_metadata ) ) {
131 $tiny_metadata = get_post_meta( $this->id, Tiny_Config::META_KEY, true );
132 }
133 if ( $tiny_metadata ) {
134 foreach ( $tiny_metadata as $size => $meta ) {
135 if ( ! isset( $this->sizes[ $size ] ) ) {
136 if ( self::is_retina( $size ) && Tiny_Settings::wr2x_active() ) {
137 $size_name = rtrim( $size, '_wr2x' );
138 if ( 'original' === $size_name ) {
139 $size_name = '0';
140 }
141 $retina_path = wr2x_get_retina(
142 $this->sizes[ $size_name ]->filename
143 );
144 $this->sizes[ $size ] = new Tiny_Image_Size( $retina_path );
145 } else {
146 $this->sizes[ $size ] = new Tiny_Image_Size();
147 }
148 }
149 $this->sizes[ $size ]->meta = $meta;
150 }
151 }
152 }
153
154 public function get_id() {
155 return $this->id;
156 }
157
158 public function get_name() {
159 return $this->name;
160 }
161
162 public function get_wp_metadata() {
163 return $this->wp_metadata;
164 }
165
166 public function file_type_allowed() {
167 return in_array( $this->get_mime_type(), array( 'image/jpeg', 'image/png', 'image/webp' ) );
168 }
169
170 public function get_mime_type() {
171 return get_post_mime_type( $this->id );
172 }
173
174 public function compress() {
175 if ( $this->settings->get_compressor() === null || ! $this->file_type_allowed() ) {
176 return;
177 }
178
179 $success = 0;
180 $failed = 0;
181
182 $compressor = $this->settings->get_compressor();
183 $active_tinify_sizes = $this->settings->get_active_tinify_sizes();
184
185 if ( $this->settings->get_conversion_enabled() ) {
186 $uncompressed_sizes = $this->filter_image_sizes( 'uncompressed', $active_tinify_sizes );
187 $unconverted_sizes = $this->filter_image_sizes( 'unconverted', $active_tinify_sizes );
188
189 $unprocessed_sizes = $uncompressed_sizes + $unconverted_sizes;
190 } else {
191 $unprocessed_sizes = $this->filter_image_sizes( 'uncompressed', $active_tinify_sizes );
192 }
193
194 foreach ( $unprocessed_sizes as $size_name => $size ) {
195 if ( ! $size->is_duplicate() ) {
196 $size->add_tiny_meta_start();
197 $this->update_tiny_post_meta();
198 $resize = $this->settings->get_resize_options( $size_name );
199 $preserve = $this->settings->get_preserve_options( $size_name );
200 $convert_opts = $this->settings->get_conversion_options();
201 try {
202 $response = $compressor->compress_file(
203 $size->filename,
204 $resize,
205 $preserve,
206 $convert_opts
207 );
208 $size->add_tiny_meta( $response );
209 $success++;
210 } catch ( Tiny_Exception $e ) {
211 $size->add_tiny_meta_error( $e );
212 $failed++;
213 }
214 $this->add_wp_metadata( $size_name, $size );
215 $this->update_tiny_post_meta();
216 }
217 }
218
219 /*
220 Other plugins can hook into this action to execute custom logic
221 after the image sizes have been compressed, ie. cache flushing.
222 */
223 do_action( 'tiny_image_after_compression', $this->id, $success );
224
225 return array(
226 'success' => $success,
227 'failed' => $failed,
228 );
229 }
230
231 public function delete_converted_image() {
232 $sizes = $this->get_image_sizes();
233 foreach ( $sizes as $size ) {
234 $size->delete_converted_image_size();
235 }
236 }
237
238 public function compress_retina( $size_name, $path ) {
239 if ( $this->settings->get_compressor() === null || ! $this->file_type_allowed() ) {
240 return;
241 }
242
243 if ( ! isset( $this->sizes[ $size_name ] ) ) {
244 $this->sizes[ $size_name ] = new Tiny_Image_Size( $path );
245 }
246 $size = $this->sizes[ $size_name ];
247
248 if ( ! $size->has_been_compressed() ) {
249 $size->add_tiny_meta_start();
250 $this->update_tiny_post_meta();
251 $compressor = $this->settings->get_compressor();
252 $preserve = $this->settings->get_preserve_options( $size_name );
253 $conversion = $this->settings->get_conversion_options();
254
255 try {
256 $response = $compressor->compress_file( $path, false, $preserve, $conversion );
257 $size->add_tiny_meta( $response );
258 } catch ( Tiny_Exception $e ) {
259 $size->add_tiny_meta_error( $e );
260 }
261 $this->update_tiny_post_meta();
262 }
263 }
264
265 public function remove_retina_metadata() {
266 // Remove metadata from all sizes, as this callback only fires when all
267 // retina sizes are deleted.
268 foreach ( $this->sizes as $size_name => $size ) {
269 if ( self::is_retina( $size_name ) ) {
270 unset( $this->sizes[ $size_name ] );
271 }
272 }
273 $this->update_tiny_post_meta();
274 }
275
276 public function add_wp_metadata( $size_name, $size ) {
277 if ( self::is_original( $size_name ) ) {
278 if ( isset( $size->meta['output'] ) ) {
279 $output = $size->meta['output'];
280 if ( isset( $output['width'] ) && isset( $output['height'] ) ) {
281 $this->wp_metadata['width'] = $output['width'];
282 $this->wp_metadata['height'] = $output['height'];
283 $this->wp_metadata['filesize'] = $output['size'];
284 }
285 }
286 }
287 }
288
289 public function update_tiny_post_meta() {
290 $tiny_metadata = array();
291 foreach ( $this->sizes as $size_name => $size ) {
292 $tiny_metadata[ $size_name ] = $size->meta;
293 }
294 update_post_meta( $this->id, Tiny_Config::META_KEY, $tiny_metadata );
295 /*
296 This action is being used by WPML:
297 https://gist.github.com/srdjan-jcc/5c47685cda4da471dff5757ba3ce5ab1
298 */
299 do_action( 'updated_tiny_postmeta', $this->id, Tiny_Config::META_KEY, $tiny_metadata );
300 }
301
302 public function get_image_sizes() {
303 $original = isset( $this->sizes[ self::ORIGINAL ] )
304 ? array(
305 self::ORIGINAL => $this->sizes[ self::ORIGINAL ],
306 )
307 : array();
308 $compressed = array();
309 $uncompressed = array();
310 foreach ( $this->sizes as $size_name => $size ) {
311 if ( self::is_original( $size_name ) ) {
312 continue;
313 }
314
315 if ( $size->has_been_compressed() ) {
316 $compressed[ $size_name ] = $size;
317 } else {
318 $uncompressed[ $size_name ] = $size;
319 }
320 }
321 ksort( $compressed );
322 ksort( $uncompressed );
323 return $original + $compressed + $uncompressed;
324 }
325
326 public function get_image_size( $size = self::ORIGINAL, $create = false ) {
327 if ( isset( $this->sizes[ $size ] ) ) {
328 return $this->sizes[ $size ];
329 } elseif ( $create ) {
330 return new Tiny_Image_Size();
331 } else {
332 return null;
333 }
334 }
335
336 public function filter_image_sizes( $method, $filter_sizes = null ) {
337 $selection = array();
338 if ( is_null( $filter_sizes ) ) {
339 $filter_sizes = array_keys( $this->sizes );
340 }
341 foreach ( $filter_sizes as $size_name ) {
342 if ( ! isset( $this->sizes[ $size_name ] ) ) {
343 continue;
344 }
345
346 $tiny_image_size = $this->sizes[ $size_name ];
347 if ( $tiny_image_size->$method() ) {
348 $selection[ $size_name ] = $tiny_image_size;
349 }
350 }
351 return $selection;
352 }
353
354 public function get_count( $methods, $count_sizes = null ) {
355 $stats = array_fill_keys( $methods, 0 );
356 if ( is_null( $count_sizes ) ) {
357 $count_sizes = array_keys( $this->sizes );
358 }
359 foreach ( $count_sizes as $size ) {
360 if ( ! isset( $this->sizes[ $size ] ) ) {
361 continue;
362 }
363
364 foreach ( $methods as $method ) {
365 if ( $this->sizes[ $size ]->$method() ) {
366 $stats[ $method ]++;
367 }
368 }
369 }
370 return $stats;
371 }
372
373 public function get_latest_error() {
374 $active_tinify_sizes = $this->settings->get_active_tinify_sizes();
375 $error_message = null;
376 $last_timestamp = null;
377 foreach ( $this->sizes as $size_name => $size ) {
378 if ( in_array( $size_name, $active_tinify_sizes, true ) ) {
379 if ( isset( $size->meta['error'] ) && isset( $size->meta['message'] ) ) {
380 if ( null === $last_timestamp || $last_timestamp < $size->meta['timestamp'] ) {
381 $last_timestamp = $size->meta['timestamp'];
382 $error_message = Tiny_Helpers::truncate_text( $size->meta['message'], 140 );
383 }
384 }
385 }
386 }
387 return $error_message;
388 }
389
390 public function get_savings( $stats ) {
391 $before = $stats['initial_total_size'];
392 $after = $stats['compressed_total_size'];
393 if ( 0 === $before ) {
394 $savings = 0;
395 } else {
396 $savings = ($before - $after) / $before * 100;
397 }
398 return '' . number_format( $savings, 1 );
399 }
400
401 public function get_statistics( $active_sizes, $active_tinify_sizes ) {
402 if ( $this->statistics ) {
403 error_log( 'Strangely the image statistics are asked for again.' );
404 return $this->statistics;
405 }
406
407 $this->statistics['initial_total_size'] = 0;
408 $this->statistics['compressed_total_size'] = 0;
409 $this->statistics['image_sizes_compressed'] = 0;
410 $this->statistics['available_uncompressed_sizes'] = 0;
411 $this->statistics['image_sizes_converted'] = 0;
412 $this->statistics['available_unconverted_sizes'] = 0;
413
414 foreach ( $this->sizes as $size_name => $size ) {
415 // skip duplicates or inactive sizes
416 if ( $size->is_duplicate() || ! isset( $active_sizes[ $size_name ] ) ) {
417 continue;
418 }
419
420 $file_size = $size->filesize();
421 $is_active_size = in_array( $size_name, $active_tinify_sizes, true );
422
423 if ( isset( $size->meta['input'] ) ) {
424 $input_size = (int) $size->meta['input']['size'];
425 $this->statistics['initial_total_size'] += $input_size;
426
427 if ( isset( $size->meta['output'] ) ) {
428 $output_size = (int) $size->meta['output']['size'];
429
430 if ( $size->modified() ) {
431 $this->statistics['compressed_total_size'] += $file_size;
432 if ( $is_active_size ) {
433 $this->statistics['available_uncompressed_sizes']++;
434 }
435 } else {
436 $this->statistics['compressed_total_size'] += $output_size;
437 $this->statistics['image_sizes_compressed']++;
438 }
439 } else {
440 $this->statistics['compressed_total_size'] += $input_size;
441 }
442 } elseif ( $size->exists() ) {
443 $this->statistics['initial_total_size'] += $file_size;
444 $this->statistics['compressed_total_size'] += $file_size;
445 if ( $is_active_size ) {
446 $this->statistics['available_uncompressed_sizes']++;
447 }
448 }
449
450 if ( $is_active_size ) {
451 if ( $size->has_been_converted() ) {
452 $this->statistics['image_sizes_converted']++;
453 } else {
454 $this->statistics['available_unconverted_sizes']++;
455 }
456 }
457 }// End foreach().
458
459 return $this->statistics;
460 }
461
462
463 public static function is_original( $size ) {
464 return self::ORIGINAL === $size;
465 }
466
467 public static function is_retina( $size ) {
468 return strrpos( $size, 'wr2x' ) === strlen( $size ) - strlen( 'wr2x' );
469 }
470
471 public function can_be_converted() {
472 return $this->settings->get_conversion_enabled() && $this->file_type_allowed();
473 }
474 }
475