PluginProbe ʕ •ᴥ•ʔ
TinyPNG – JPEG, PNG & WebP image compression / 3.3
TinyPNG – JPEG, PNG & WebP image compression v3.3
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 7 years ago config 7 years ago css 6 years ago data 4 years ago images 9 years ago js 7 years ago vendor 4 years ago views 4 years ago class-tiny-bulk-optimization.php 4 years ago class-tiny-compress-client.php 7 years ago class-tiny-compress-fopen.php 7 years ago class-tiny-compress.php 7 years ago class-tiny-exception.php 7 years ago class-tiny-image-size.php 7 years ago class-tiny-image.php 4 years ago class-tiny-notices.php 4 years ago class-tiny-php.php 7 years ago class-tiny-plugin.php 4 years ago class-tiny-settings.php 4 years ago class-tiny-wp-base.php 7 years ago
class-tiny-image.php
478 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 download_missing_image_sizes() {
175 global $as3cf;
176
177 if ( ! $as3cf || ! $as3cf->is_plugin_setup() ) {
178 error_log( 'offload s3 plugin not configured..' );
179 return;
180 }
181
182 $s3_data = get_post_meta( $this->id, 'amazonS3_info', true );
183 if ( ! $s3_data ) {
184 return;
185 }
186
187 $path = dirname( $s3_data['key'] );
188
189 foreach ( $this->sizes as $size_name => $size ) {
190 $local_file_path = get_home_path() . $s3_data['key'];
191
192 $s3_data['key'] = wp_normalize_path( $path . '/' . basename( $size->filename ) );
193 $as3cf->plugin_compat->copy_s3_file_to_server( $s3_data, $size->filename );
194 }
195 }
196
197 public function compress() {
198 if ( $this->settings->get_compressor() === null || ! $this->file_type_allowed() ) {
199 return;
200 }
201
202 /* Integration tests need to be written before this can be enabled. */
203 // if ( $this->settings->has_offload_s3_installed() ) {
204 // $this->download_missing_image_sizes();
205 // }
206
207 $success = 0;
208 $failed = 0;
209
210 $compressor = $this->settings->get_compressor();
211 $active_tinify_sizes = $this->settings->get_active_tinify_sizes();
212 $uncompressed_sizes = $this->filter_image_sizes( 'uncompressed', $active_tinify_sizes );
213
214 foreach ( $uncompressed_sizes as $size_name => $size ) {
215 if ( ! $size->is_duplicate() ) {
216 $size->add_tiny_meta_start();
217 $this->update_tiny_post_meta();
218 $resize = $this->settings->get_resize_options( $size_name );
219 $preserve = $this->settings->get_preserve_options( $size_name );
220 try {
221 $response = $compressor->compress_file( $size->filename, $resize, $preserve );
222 $size->add_tiny_meta( $response );
223 $success++;
224 } catch ( Tiny_Exception $e ) {
225 $size->add_tiny_meta_error( $e );
226 $failed++;
227 }
228 $this->add_wp_metadata( $size_name, $size );
229 $this->update_tiny_post_meta();
230 }
231 }
232
233 /*
234 Other plugins can hook into this action to execute custom logic
235 after the image sizes have been compressed, ie. cache flushing.
236 */
237 do_action( 'tiny_image_after_compression', $this->id, $success );
238
239 return array(
240 'success' => $success,
241 'failed' => $failed,
242 );
243 }
244
245 public function compress_retina( $size_name, $path ) {
246 if ( $this->settings->get_compressor() === null || ! $this->file_type_allowed() ) {
247 return;
248 }
249
250 if ( ! isset( $this->sizes[ $size_name ] ) ) {
251 $this->sizes[ $size_name ] = new Tiny_Image_Size( $path );
252 }
253 $size = $this->sizes[ $size_name ];
254
255 if ( ! $size->has_been_compressed() ) {
256 $size->add_tiny_meta_start();
257 $this->update_tiny_post_meta();
258 $compressor = $this->settings->get_compressor();
259 $preserve = $this->settings->get_preserve_options( $size_name );
260
261 try {
262 $response = $compressor->compress_file( $path, false, $preserve );
263 $size->add_tiny_meta( $response );
264 } catch ( Tiny_Exception $e ) {
265 $size->add_tiny_meta_error( $e );
266 }
267 $this->update_tiny_post_meta();
268 }
269 }
270
271 public function remove_retina_metadata() {
272 // Remove metadata from all sizes, as this callback only fires when all
273 // retina sizes are deleted.
274 foreach ( $this->sizes as $size_name => $size ) {
275 if ( self::is_retina( $size_name ) ) {
276 unset( $this->sizes[ $size_name ] );
277 }
278 }
279 $this->update_tiny_post_meta();
280 }
281
282 public function add_wp_metadata( $size_name, $size ) {
283 if ( self::is_original( $size_name ) ) {
284 if ( isset( $size->meta['output'] ) ) {
285 $output = $size->meta['output'];
286 if ( isset( $output['width'] ) && isset( $output['height'] ) ) {
287 $this->wp_metadata['width'] = $output['width'];
288 $this->wp_metadata['height'] = $output['height'];
289 }
290 }
291 }
292 }
293
294 public function update_tiny_post_meta() {
295 $tiny_metadata = array();
296 foreach ( $this->sizes as $size_name => $size ) {
297 $tiny_metadata[ $size_name ] = $size->meta;
298 }
299 update_post_meta( $this->id, Tiny_Config::META_KEY, $tiny_metadata );
300 /*
301 This action is being used by WPML:
302 https://gist.github.com/srdjan-jcc/5c47685cda4da471dff5757ba3ce5ab1
303 */
304 do_action( 'updated_tiny_postmeta', $this->id, Tiny_Config::META_KEY, $tiny_metadata );
305 }
306
307 public function get_image_sizes() {
308 $original = isset( $this->sizes[ self::ORIGINAL ] )
309 ? array(
310 self::ORIGINAL => $this->sizes[ self::ORIGINAL ],
311 )
312 : array();
313 $compressed = array();
314 $uncompressed = array();
315 foreach ( $this->sizes as $size_name => $size ) {
316 if ( self::is_original( $size_name ) ) {
317 continue;
318 }
319
320 if ( $size->has_been_compressed() ) {
321 $compressed[ $size_name ] = $size;
322 } else {
323 $uncompressed[ $size_name ] = $size;
324 }
325 }
326 ksort( $compressed );
327 ksort( $uncompressed );
328 return $original + $compressed + $uncompressed;
329 }
330
331 public function get_image_size( $size = self::ORIGINAL, $create = false ) {
332 if ( isset( $this->sizes[ $size ] ) ) {
333 return $this->sizes[ $size ];
334 } elseif ( $create ) {
335 return new Tiny_Image_Size();
336 } else {
337 return null;
338 }
339 }
340
341 public function filter_image_sizes( $method, $filter_sizes = null ) {
342 $selection = array();
343 if ( is_null( $filter_sizes ) ) {
344 $filter_sizes = array_keys( $this->sizes );
345 }
346 foreach ( $filter_sizes as $size_name ) {
347 if ( ! isset( $this->sizes[ $size_name ] ) ) {
348 continue;
349 }
350
351 $tiny_image_size = $this->sizes[ $size_name ];
352 if ( $tiny_image_size->$method() ) {
353 $selection[ $size_name ] = $tiny_image_size;
354 }
355 }
356 return $selection;
357 }
358
359 public function get_count( $methods, $count_sizes = null ) {
360 $stats = array_fill_keys( $methods, 0 );
361 if ( is_null( $count_sizes ) ) {
362 $count_sizes = array_keys( $this->sizes );
363 }
364 foreach ( $count_sizes as $size ) {
365 if ( ! isset( $this->sizes[ $size ] ) ) {
366 continue;
367 }
368
369 foreach ( $methods as $method ) {
370 if ( $this->sizes[ $size ]->$method() ) {
371 $stats[ $method ]++;
372 }
373 }
374 }
375 return $stats;
376 }
377
378 public function get_latest_error() {
379 $active_tinify_sizes = $this->settings->get_active_tinify_sizes();
380 $error_message = null;
381 $last_timestamp = null;
382 foreach ( $this->sizes as $size_name => $size ) {
383 if ( in_array( $size_name, $active_tinify_sizes, true ) ) {
384 if ( isset( $size->meta['error'] ) && isset( $size->meta['message'] ) ) {
385 if ( null === $last_timestamp || $last_timestamp < $size->meta['timestamp'] ) {
386 $last_timestamp = $size->meta['timestamp'];
387 $error_message = mb_strimwidth( $size->meta['message'], 0 , 140, '...' );
388 }
389 }
390 }
391 }
392 return $error_message;
393 }
394
395 public function get_savings( $stats ) {
396 $before = $stats['initial_total_size'];
397 $after = $stats['optimized_total_size'];
398 if ( 0 === $before ) {
399 $savings = 0;
400 } else {
401 $savings = ($before - $after) / $before * 100;
402 }
403 return '' . number_format( $savings, 1 );
404 }
405
406 public function get_statistics( $active_sizes, $active_tinify_sizes ) {
407 if ( $this->statistics ) {
408 error_log( 'Strangely the image statistics are asked for again.' );
409 return $this->statistics;
410 }
411
412 $this->statistics['initial_total_size'] = 0;
413 $this->statistics['optimized_total_size'] = 0;
414 $this->statistics['image_sizes_optimized'] = 0;
415 $this->statistics['available_unoptimized_sizes'] = 0;
416
417 foreach ( $this->sizes as $size_name => $size ) {
418 if ( ! $size->is_duplicate() ) {
419 if ( array_key_exists( $size_name, $active_sizes ) ) {
420 if ( isset( $size->meta['input'] ) ) {
421 $input = $size->meta['input'];
422 $this->statistics['initial_total_size'] += intval( $input['size'] );
423 if ( isset( $size->meta['output'] ) ) {
424 $output = $size->meta['output'];
425 if ( $size->modified() ) {
426 $this->statistics['optimized_total_size'] += $size->filesize();
427 if ( in_array( $size_name, $active_tinify_sizes, true ) ) {
428 $this->statistics['available_unoptimized_sizes'] += 1;
429 }
430 } else {
431 $this->statistics['optimized_total_size']
432 += intval( $output['size'] );
433 $this->statistics['image_sizes_optimized'] += 1;
434 }
435 } else {
436 $this->statistics['optimized_total_size'] += intval( $input['size'] );
437 }
438 } elseif ( $size->exists() ) {
439 $this->statistics['initial_total_size'] += $size->filesize();
440 $this->statistics['optimized_total_size'] += $size->filesize();
441 if ( in_array( $size_name, $active_tinify_sizes, true ) ) {
442 $this->statistics['available_unoptimized_sizes'] += 1;
443 }
444 }
445 }
446 }
447 }// End foreach().
448
449 /*
450 When an image hasn't yet been optimized but only exists on S3, we still need to
451 know the total size of the image sizes for the bulk optimization tool.
452 TODO: First write integration tests before enabling this again.
453
454 if (
455 0 === $this->statistics['initial_total_size'] &&
456 0 === $this->statistics['optimized_total_size'] &&
457 $this->settings->has_offload_s3_installed()
458 ) {
459 $s3_data = get_post_meta( $this->id, 'wpos3_filesize_total', true );
460 if ( $s3_data ) {
461 $this->statistics['initial_total_size'] = $s3_data;
462 $this->statistics['optimized_total_size'] = $s3_data;
463 }
464 }
465 */
466
467 return $this->statistics;
468 }
469
470 public static function is_original( $size ) {
471 return self::ORIGINAL === $size;
472 }
473
474 public static function is_retina( $size ) {
475 return strrpos( $size, 'wr2x' ) === strlen( $size ) - strlen( 'wr2x' );
476 }
477 }
478