PluginProbe ʕ •ᴥ•ʔ
TinyPNG – JPEG, PNG & WebP image compression / 3.0.0
TinyPNG – JPEG, PNG & WebP image compression v3.0.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 7 years ago css 7 years ago data 9 years ago images 9 years ago js 7 years ago vendor 7 years ago views 7 years ago class-tiny-bulk-optimization.php 7 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 7 years ago class-tiny-notices.php 7 years ago class-tiny-php.php 7 years ago class-tiny-plugin.php 7 years ago class-tiny-settings.php 7 years ago class-tiny-wp-base.php 7 years ago
class-tiny-image.php
460 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 $path_info = pathinfo( $this->wp_metadata['file'] );
61 $this->name = $path_info['basename'];
62
63 $upload_dir = wp_upload_dir();
64 $path_prefix = $upload_dir['basedir'] . '/';
65 if ( isset( $path_info['dirname'] ) ) {
66 $path_prefix .= $path_info['dirname'] . '/';
67 }
68
69 $filename = $path_prefix . $this->name;
70 $this->sizes[ self::ORIGINAL ] = new Tiny_Image_Size( $filename );
71
72 if ( isset( $this->wp_metadata['sizes'] ) && is_array( $this->wp_metadata['sizes'] ) ) {
73 foreach ( $this->wp_metadata['sizes'] as $size_name => $info ) {
74 $this->sizes[ $size_name ] = new Tiny_Image_Size( $path_prefix . $info['file'] );
75 }
76 }
77 }
78
79 private function detect_duplicates( $active_sizes, $active_tinify_sizes ) {
80 $filenames = array();
81
82 if ( is_array( $this->wp_metadata )
83 && isset( $this->wp_metadata['file'] )
84 && isset( $this->wp_metadata['sizes'] )
85 && is_array( $this->wp_metadata['sizes'] ) ) {
86
87 if ( null == $active_sizes ) {
88 $active_sizes = $this->settings->get_sizes();
89 }
90 if ( null == $active_tinify_sizes ) {
91 $active_tinify_sizes = $this->settings->get_active_tinify_sizes();
92 }
93
94 foreach ( $this->wp_metadata['sizes'] as $size_name => $size ) {
95 if ( $this->sizes[ $size_name ]->has_been_compressed()
96 && array_key_exists( $size_name, $active_sizes ) ) {
97 $filenames = $this->duplicate_check( $filenames, $size['file'], $size_name );
98 }
99 }
100 foreach ( $this->wp_metadata['sizes'] as $size_name => $size ) {
101 if ( in_array( $size_name, $active_tinify_sizes, true ) ) {
102 $filenames = $this->duplicate_check( $filenames, $size['file'], $size_name );
103 }
104 }
105 foreach ( $this->wp_metadata['sizes'] as $size_name => $size ) {
106 if ( array_key_exists( $size_name, $active_sizes ) ) {
107 $filenames = $this->duplicate_check( $filenames, $size['file'], $size_name );
108 }
109 }
110 foreach ( $this->wp_metadata['sizes'] as $size_name => $size ) {
111 $filenames = $this->duplicate_check( $filenames, $size['file'], $size_name );
112 }
113 }
114 }
115
116 private function duplicate_check( $filenames, $file, $size_name ) {
117 if ( isset( $filenames[ $file ] ) ) {
118 if ( $filenames[ $file ] != $size_name ) {
119 $this->sizes[ $size_name ]->mark_duplicate( $filenames[ $file ] );
120 }
121 } else {
122 $filenames[ $file ] = $size_name;
123 }
124 return $filenames;
125 }
126
127 private function parse_tiny_metadata( $tiny_metadata ) {
128 if ( is_null( $tiny_metadata ) ) {
129 $tiny_metadata = get_post_meta( $this->id, Tiny_Config::META_KEY, true );
130 }
131 if ( $tiny_metadata ) {
132 foreach ( $tiny_metadata as $size => $meta ) {
133 if ( ! isset( $this->sizes[ $size ] ) ) {
134 if ( self::is_retina( $size ) && Tiny_Settings::wr2x_active() ) {
135 $retina_path = wr2x_get_retina(
136 $this->sizes[ rtrim( $size, '_wr2x' ) ]->filename
137 );
138 $this->sizes[ $size ] = new Tiny_Image_Size( $retina_path );
139 } else {
140 $this->sizes[ $size ] = new Tiny_Image_Size();
141 }
142 }
143 $this->sizes[ $size ]->meta = $meta;
144 }
145 }
146 }
147
148 public function get_id() {
149 return $this->id;
150 }
151
152 public function get_name() {
153 return $this->name;
154 }
155
156 public function get_wp_metadata() {
157 return $this->wp_metadata;
158 }
159
160 public function file_type_allowed() {
161 return in_array( $this->get_mime_type(), array( 'image/jpeg', 'image/png' ) );
162 }
163
164 public function get_mime_type() {
165 return get_post_mime_type( $this->id );
166 }
167
168 public function download_missing_image_sizes() {
169 global $as3cf;
170
171 if ( ! $as3cf || ! $as3cf->is_plugin_setup() ) {
172 error_log( 'offload s3 plugin not configured..' );
173 return;
174 }
175
176 $s3_data = get_post_meta( $this->id, 'amazonS3_info', true );
177 if ( ! $s3_data ) {
178 return;
179 }
180
181 $path = dirname( $s3_data['key'] );
182
183 foreach ( $this->sizes as $size_name => $size ) {
184 $local_file_path = get_home_path() . $s3_data['key'];
185
186 $s3_data['key'] = wp_normalize_path( $path . '/' . basename( $size->filename ) );
187 $as3cf->plugin_compat->copy_s3_file_to_server( $s3_data, $size->filename );
188 }
189 }
190
191 public function compress() {
192 if ( $this->settings->get_compressor() === null || ! $this->file_type_allowed() ) {
193 return;
194 }
195
196 /* Integration tests need to be written before this can be enabled. */
197 // if ( $this->settings->has_offload_s3_installed() ) {
198 // $this->download_missing_image_sizes();
199 // }
200
201 $success = 0;
202 $failed = 0;
203
204 $compressor = $this->settings->get_compressor();
205 $active_tinify_sizes = $this->settings->get_active_tinify_sizes();
206 $uncompressed_sizes = $this->filter_image_sizes( 'uncompressed', $active_tinify_sizes );
207
208 foreach ( $uncompressed_sizes as $size_name => $size ) {
209 if ( ! $size->is_duplicate() ) {
210 $size->add_tiny_meta_start();
211 $this->update_tiny_post_meta();
212 $resize = $this->settings->get_resize_options( $size_name );
213 $preserve = $this->settings->get_preserve_options( $size_name );
214 try {
215 $response = $compressor->compress_file( $size->filename, $resize, $preserve );
216 $size->add_tiny_meta( $response );
217 $success++;
218 } catch ( Tiny_Exception $e ) {
219 $size->add_tiny_meta_error( $e );
220 $failed++;
221 }
222 $this->add_wp_metadata( $size_name, $size );
223 $this->update_tiny_post_meta();
224 }
225 }
226 return array(
227 'success' => $success,
228 'failed' => $failed,
229 );
230 }
231
232 public function compress_retina( $size_name, $path ) {
233 if ( $this->settings->get_compressor() === null || ! $this->file_type_allowed() ) {
234 return;
235 }
236
237 if ( ! isset( $this->sizes[ $size_name ] ) ) {
238 $this->sizes[ $size_name ] = new Tiny_Image_Size( $path );
239 }
240 $size = $this->sizes[ $size_name ];
241
242 if ( ! $size->has_been_compressed() ) {
243 $size->add_tiny_meta_start();
244 $this->update_tiny_post_meta();
245 $compressor = $this->settings->get_compressor();
246 $preserve = $this->settings->get_preserve_options( $size_name );
247
248 try {
249 $response = $compressor->compress_file( $path, false, $preserve );
250 $size->add_tiny_meta( $response );
251 } catch ( Tiny_Exception $e ) {
252 $size->add_tiny_meta_error( $e );
253 }
254 $this->update_tiny_post_meta();
255 }
256 }
257
258 public function remove_retina_metadata() {
259 // Remove metadata from all sizes, as this callback only fires when all
260 // retina sizes are deleted.
261 foreach ( $this->sizes as $size_name => $size ) {
262 if ( self::is_retina( $size_name ) ) {
263 unset( $this->sizes[ $size_name ] );
264 }
265 }
266 $this->update_tiny_post_meta();
267 }
268
269 public function add_wp_metadata( $size_name, $size ) {
270 if ( self::is_original( $size_name ) ) {
271 if ( isset( $size->meta['output'] ) ) {
272 $output = $size->meta['output'];
273 if ( isset( $output['width'] ) && isset( $output['height'] ) ) {
274 $this->wp_metadata['width'] = $output['width'];
275 $this->wp_metadata['height'] = $output['height'];
276 }
277 }
278 }
279 }
280
281 public function update_tiny_post_meta() {
282 $tiny_metadata = array();
283 foreach ( $this->sizes as $size_name => $size ) {
284 $tiny_metadata[ $size_name ] = $size->meta;
285 }
286 update_post_meta( $this->id, Tiny_Config::META_KEY, $tiny_metadata );
287 }
288
289 public function get_image_sizes() {
290 $original = isset( $this->sizes[ self::ORIGINAL ] )
291 ? array(
292 self::ORIGINAL => $this->sizes[ self::ORIGINAL ],
293 )
294 : array();
295 $compressed = array();
296 $uncompressed = array();
297 foreach ( $this->sizes as $size_name => $size ) {
298 if ( self::is_original( $size_name ) ) {
299 continue;
300 }
301
302 if ( $size->has_been_compressed() ) {
303 $compressed[ $size_name ] = $size;
304 } else {
305 $uncompressed[ $size_name ] = $size;
306 }
307 }
308 ksort( $compressed );
309 ksort( $uncompressed );
310 return $original + $compressed + $uncompressed;
311 }
312
313 public function get_image_size( $size = self::ORIGINAL, $create = false ) {
314 if ( isset( $this->sizes[ $size ] ) ) {
315 return $this->sizes[ $size ];
316 } elseif ( $create ) {
317 return new Tiny_Image_Size();
318 } else {
319 return null;
320 }
321 }
322
323 public function filter_image_sizes( $method, $filter_sizes = null ) {
324 $selection = array();
325 if ( is_null( $filter_sizes ) ) {
326 $filter_sizes = array_keys( $this->sizes );
327 }
328 foreach ( $filter_sizes as $size_name ) {
329 if ( ! isset( $this->sizes[ $size_name ] ) ) {
330 continue;
331 }
332
333 $tiny_image_size = $this->sizes[ $size_name ];
334 if ( $tiny_image_size->$method() ) {
335 $selection[ $size_name ] = $tiny_image_size;
336 }
337 }
338 return $selection;
339 }
340
341 public function get_count( $methods, $count_sizes = null ) {
342 $stats = array_fill_keys( $methods, 0 );
343 if ( is_null( $count_sizes ) ) {
344 $count_sizes = array_keys( $this->sizes );
345 }
346 foreach ( $count_sizes as $size ) {
347 if ( ! isset( $this->sizes[ $size ] ) ) {
348 continue;
349 }
350
351 foreach ( $methods as $method ) {
352 if ( $this->sizes[ $size ]->$method() ) {
353 $stats[ $method ]++;
354 }
355 }
356 }
357 return $stats;
358 }
359
360 public function get_latest_error() {
361 $active_tinify_sizes = $this->settings->get_active_tinify_sizes();
362 $error_message = null;
363 $last_timestamp = null;
364 foreach ( $this->sizes as $size_name => $size ) {
365 if ( in_array( $size_name, $active_tinify_sizes, true ) ) {
366 if ( isset( $size->meta['error'] ) && isset( $size->meta['message'] ) ) {
367 if ( null === $last_timestamp || $last_timestamp < $size->meta['timestamp'] ) {
368 $last_timestamp = $size->meta['timestamp'];
369 $error_message = $size->meta['message'];
370 }
371 }
372 }
373 }
374 return $error_message;
375 }
376
377 public function get_savings( $stats ) {
378 $before = $stats['initial_total_size'];
379 $after = $stats['optimized_total_size'];
380 if ( 0 === $before ) {
381 $savings = 0;
382 } else {
383 $savings = ($before - $after) / $before * 100;
384 }
385 return '' . number_format( $savings, 1 );
386 }
387
388 public function get_statistics( $active_sizes, $active_tinify_sizes ) {
389 if ( $this->statistics ) {
390 error_log( 'Strangely the image statistics are asked for again.' );
391 return $this->statistics;
392 }
393
394 $this->statistics['initial_total_size'] = 0;
395 $this->statistics['optimized_total_size'] = 0;
396 $this->statistics['image_sizes_optimized'] = 0;
397 $this->statistics['available_unoptimized_sizes'] = 0;
398
399 foreach ( $this->sizes as $size_name => $size ) {
400 if ( ! $size->is_duplicate() ) {
401 if ( array_key_exists( $size_name, $active_sizes ) ) {
402 if ( isset( $size->meta['input'] ) ) {
403 $input = $size->meta['input'];
404 $this->statistics['initial_total_size'] += intval( $input['size'] );
405 if ( isset( $size->meta['output'] ) ) {
406 $output = $size->meta['output'];
407 if ( $size->modified() ) {
408 $this->statistics['optimized_total_size'] += $size->filesize();
409 if ( in_array( $size_name, $active_tinify_sizes, true ) ) {
410 $this->statistics['available_unoptimized_sizes'] += 1;
411 }
412 } else {
413 $this->statistics['optimized_total_size']
414 += intval( $output['size'] );
415 $this->statistics['image_sizes_optimized'] += 1;
416 }
417 } else {
418 $this->statistics['optimized_total_size'] += intval( $input['size'] );
419 }
420 } elseif ( $size->exists() ) {
421 $this->statistics['initial_total_size'] += $size->filesize();
422 $this->statistics['optimized_total_size'] += $size->filesize();
423 if ( in_array( $size_name, $active_tinify_sizes, true ) ) {
424 $this->statistics['available_unoptimized_sizes'] += 1;
425 }
426 }
427 }
428 }
429 }// End foreach().
430
431 /*
432 When an image hasn't yet been optimized but only exists on S3, we still need to
433 know the total size of the image sizes for the bulk optimization tool.
434 TODO: First write integration tests before enabling this again.
435
436 if (
437 0 === $this->statistics['initial_total_size'] &&
438 0 === $this->statistics['optimized_total_size'] &&
439 $this->settings->has_offload_s3_installed()
440 ) {
441 $s3_data = get_post_meta( $this->id, 'wpos3_filesize_total', true );
442 if ( $s3_data ) {
443 $this->statistics['initial_total_size'] = $s3_data;
444 $this->statistics['optimized_total_size'] = $s3_data;
445 }
446 }
447 */
448
449 return $this->statistics;
450 }
451
452 public static function is_original( $size ) {
453 return self::ORIGINAL === $size;
454 }
455
456 public static function is_retina( $size ) {
457 return strrpos( $size, 'wr2x' ) === strlen( $size ) - strlen( 'wr2x' );
458 }
459 }
460