PluginProbe ʕ •ᴥ•ʔ
TinyPNG – JPEG, PNG & WebP image compression / 3.8.0
TinyPNG – JPEG, PNG & WebP image compression v3.8.0
3.8.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-plugin.php
tiny-compress-images / src Last commit date
compatibility 2 months ago config 2 months ago css 7 months ago data 4 years ago images 4 years ago js 1 week ago vendor 6 months ago views 1 week ago class-tiny-apache-rewrite.php 2 months ago class-tiny-bulk-optimization.php 2 months ago class-tiny-cli.php 2 months ago class-tiny-compress-client.php 1 week ago class-tiny-compress-fopen.php 2 months ago class-tiny-compress.php 1 week ago class-tiny-conversion.php 4 months ago class-tiny-diagnostics.php 7 months ago class-tiny-exception.php 7 months ago class-tiny-helpers.php 2 months ago class-tiny-image-size.php 1 week ago class-tiny-image.php 1 week ago class-tiny-logger.php 2 months ago class-tiny-migrate.php 2 months ago class-tiny-notices.php 2 months ago class-tiny-php.php 2 months ago class-tiny-picture.php 2 months ago class-tiny-plugin.php 1 week ago class-tiny-settings.php 1 week ago class-tiny-source-base.php 1 week ago class-tiny-source-image.php 7 months ago class-tiny-source-picture.php 7 months ago class-tiny-wp-base.php 2 months ago
class-tiny-plugin.php
1008 lines
1 <?php
2 /*
3 * Tiny Compress Images - WordPress plugin.
4 * Copyright (C) 2015-2023 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 class Tiny_Plugin extends Tiny_WP_Base {
21 const VERSION = '3.8.0';
22 const MEDIA_COLUMN = self::NAME;
23 const DATETIME_FORMAT = 'Y-m-d G:i:s';
24
25 private static $version;
26
27 private $settings;
28 private $twig;
29
30 public static function jpeg_quality() {
31 return 85;
32 }
33
34 public static function version() {
35 /*
36 Avoid using get_plugin_data() because it is not loaded early enough
37 in xmlrpc.php. */
38 return self::VERSION;
39 }
40
41 public function __construct() {
42 parent::__construct();
43 $this->settings = new Tiny_Settings();
44 new Tiny_Conversion( $this->settings );
45 }
46
47 public function set_compressor( $compressor ) {
48 $this->settings->set_compressor( $compressor );
49 }
50
51 public function init() {
52 add_filter(
53 'jpeg_quality',
54 $this->get_static_method( 'jpeg_quality' )
55 );
56
57 add_filter(
58 'wp_editor_set_quality',
59 $this->get_static_method( 'jpeg_quality' )
60 );
61
62 add_filter(
63 'wp_generate_attachment_metadata',
64 $this->get_method( 'process_attachment' ),
65 10,
66 2
67 );
68
69 add_action( 'delete_attachment', $this->get_method( 'clean_attachment' ), 10, 2 );
70
71 add_action(
72 'tiny_image_before_compression',
73 $this->get_method( 'backup_original_image' ),
74 10,
75 1
76 );
77
78 load_plugin_textdomain(
79 self::NAME,
80 false,
81 dirname( plugin_basename( __FILE__ ) ) . '/languages'
82 );
83
84 $this->tiny_compatibility();
85 }
86
87 public function cli_init() {
88 Tiny_CLI::register_command( $this->settings );
89 }
90
91 public function ajax_init() {
92 add_filter(
93 'wp_ajax_tiny_async_optimize_upload_new_media',
94 $this->get_method( 'compress_on_upload' )
95 );
96
97 add_action(
98 'wp_ajax_tiny_compress_image_from_library',
99 $this->get_method( 'compress_image_from_library' )
100 );
101
102 add_action(
103 'wp_ajax_tiny_compress_image_for_bulk',
104 $this->get_method( 'compress_image_for_bulk' )
105 );
106
107 add_action(
108 'wp_ajax_tiny_get_optimization_statistics',
109 $this->get_method( 'ajax_optimization_statistics' )
110 );
111
112 add_action(
113 'wp_ajax_tiny_get_compression_status',
114 $this->get_method( 'ajax_compression_status' )
115 );
116
117 add_action(
118 'wp_ajax_tiny_mark_image_as_compressed',
119 $this->get_method( 'mark_image_as_compressed' )
120 );
121
122 /*
123 When touching any functionality linked to image compressions when
124 uploading images make sure it also works with XML-RPC. See README. */
125 add_filter(
126 'wp_ajax_nopriv_tiny_rpc',
127 $this->get_method( 'process_rpc_request' )
128 );
129
130 if ( $this->settings->compress_wr2x_images() ) {
131 add_action(
132 'wr2x_upload_retina',
133 $this->get_method( 'compress_original_retina_image' ),
134 10,
135 2
136 );
137
138 add_action(
139 'wr2x_retina_file_added',
140 $this->get_method( 'compress_retina_image' ),
141 10,
142 3
143 );
144
145 add_action(
146 'wr2x_retina_file_removed',
147 $this->get_method( 'remove_retina_image' ),
148 10,
149 2
150 );
151 }
152 }
153
154 public function admin_init() {
155 add_action(
156 'wp_dashboard_setup',
157 $this->get_method( 'add_dashboard_widget' )
158 );
159
160 add_action(
161 'admin_enqueue_scripts',
162 $this->get_method( 'enqueue_scripts' )
163 );
164
165 add_action(
166 'admin_action_tiny_bulk_action',
167 $this->get_method( 'media_library_bulk_action' )
168 );
169
170 add_action(
171 'admin_action_-1',
172 $this->get_method( 'media_library_bulk_action' )
173 );
174
175 add_action(
176 'admin_action_tiny_bulk_mark_compressed',
177 $this->get_method( 'media_library_bulk_action' )
178 );
179
180 add_filter(
181 'manage_media_columns',
182 $this->get_method( 'add_media_columns' )
183 );
184
185 add_action(
186 'manage_media_custom_column',
187 $this->get_method( 'render_media_column' ),
188 10,
189 2
190 );
191
192 add_action(
193 'attachment_submitbox_misc_actions',
194 $this->get_method( 'show_media_info' )
195 );
196
197 $plugin = plugin_basename(
198 dirname( __DIR__ ) . '/tiny-compress-images.php'
199 );
200
201 add_filter(
202 "plugin_action_links_$plugin",
203 $this->get_method( 'add_plugin_links' )
204 );
205
206 $this->tiny_compatibility();
207
208 add_thickbox();
209 Tiny_Logger::init();
210 }
211
212 public function admin_menu() {
213 add_media_page(
214 __( 'Bulk Optimization', 'tiny-compress-images' ),
215 esc_html__( 'Bulk TinyPNG', 'tiny-compress-images' ),
216 'upload_files',
217 'tiny-bulk-optimization',
218 $this->get_method( 'render_bulk_optimization_page' )
219 );
220 }
221
222 public function add_plugin_links( $current_links ) {
223 $additional = array(
224 'settings' => sprintf(
225 '<a href="options-general.php?page=tinify">%s</a>',
226 esc_html__( 'Settings', 'tiny-compress-images' )
227 ),
228 'bulk' => sprintf(
229 '<a href="upload.php?page=tiny-bulk-optimization">%s</a>',
230 esc_html__( 'Bulk TinyPNG', 'tiny-compress-images' )
231 ),
232 );
233 return array_merge( $additional, $current_links );
234 }
235
236 public function tiny_compatibility() {
237 if ( defined( 'ICL_SITEPRESS_VERSION' ) ) {
238 new Tiny_WPML();
239 }
240
241 if ( Tiny_AS3CF::is_active() ) {
242 new Tiny_AS3CF( $this->settings );
243 }
244
245 new Tiny_WooCommerce();
246 }
247
248 public function compress_original_retina_image( $attachment_id, $path ) {
249 $tiny_image = new Tiny_Image( $this->settings, $attachment_id );
250 $tiny_image->compress_retina( 'original_wr2x', $path );
251 }
252
253 public function compress_retina_image( $attachment_id, $path, $size_name ) {
254 $tiny_image = new Tiny_Image( $this->settings, $attachment_id );
255 $tiny_image->compress_retina( $size_name . '_wr2x', $path );
256 }
257
258 public function remove_retina_image( $attachment_id, $path ) {
259 $tiny_image = new Tiny_Image( $this->settings, $attachment_id );
260 $tiny_image->remove_retina_metadata();
261 }
262
263 public function enqueue_scripts( $hook ) {
264 wp_enqueue_style(
265 self::NAME . '_admin',
266 plugins_url( '/css/admin.css', __FILE__ ),
267 array(),
268 self::version()
269 );
270
271 wp_enqueue_style(
272 self::NAME . '_chart',
273 plugins_url( '/css/optimization-chart.css', __FILE__ ),
274 array(),
275 self::version()
276 );
277
278 wp_register_script(
279 self::NAME . '_admin',
280 plugins_url( '/js/admin.js', __FILE__ ),
281 array(),
282 self::version(),
283 true
284 );
285
286 // WordPress < 3.3 does not handle multidimensional arrays
287 wp_localize_script(
288 self::NAME . '_admin',
289 'tinyCompress',
290 array(
291 'nonce' => wp_create_nonce( 'tiny-compress' ),
292 'wpVersion' => self::wp_version(),
293 'pluginVersion' => self::version(),
294 'L10nAllDone' => __(
295 'All images are processed',
296 'tiny-compress-images'
297 ),
298 'L10nNoActionTaken' => __(
299 'No action taken',
300 'tiny-compress-images'
301 ),
302 'L10nDuplicate' => __(
303 'Image was already processed',
304 'tiny-compress-images'
305 ),
306 'L10nBulkAction' => __( 'Compress Images', 'tiny-compress-images' ),
307 'L10nBulkMarkCompressed' => __(
308 'Mark as Compressed',
309 'tiny-compress-images'
310 ),
311 'L10nCancelled' => __( 'Cancelled', 'tiny-compress-images' ),
312 'L10nCompressing' => __( 'Compressing', 'tiny-compress-images' ),
313 'L10nCompressed' => __( 'compressed', 'tiny-compress-images' ),
314 'L10nConverted' => __( 'converted', 'tiny-compress-images' ),
315 'L10nFile' => __( 'File', 'tiny-compress-images' ),
316 'L10nSizesOptimized' => __(
317 'Sizes optimized',
318 'tiny-compress-images'
319 ),
320 'L10nInitialSize' => __( 'Initial size', 'tiny-compress-images' ),
321 'L10nCurrentSize' => __( 'Current size', 'tiny-compress-images' ),
322 'L10nSavings' => __( 'Savings', 'tiny-compress-images' ),
323 'L10nStatus' => __( 'Status', 'tiny-compress-images' ),
324 'L10nShowMoreDetails' => __(
325 'Show more details',
326 'tiny-compress-images'
327 ),
328 'L10nError' => __( 'Error', 'tiny-compress-images' ),
329 'L10nLatestError' => __( 'Latest error', 'tiny-compress-images' ),
330 'L10nInternalError' => __( 'Internal error', 'tiny-compress-images' ),
331 'L10nOutOf' => __( 'out of', 'tiny-compress-images' ),
332 'L10nWaiting' => __( 'Waiting', 'tiny-compress-images' ),
333 )
334 );
335
336 wp_enqueue_script( self::NAME . '_admin' );
337
338 if ( 'media_page_tiny-bulk-optimization' == $hook ) {
339 wp_enqueue_style(
340 self::NAME . '_tiny_bulk_optimization',
341 plugins_url( '/css/bulk-optimization.css', __FILE__ ),
342 array(),
343 self::version()
344 );
345
346 wp_enqueue_style(
347 self::NAME . '_chart',
348 plugins_url( '/css/optimization-chart.css', __FILE__ ),
349 array(),
350 self::version()
351 );
352
353 wp_register_script(
354 self::NAME . '_tiny_bulk_optimization',
355 plugins_url( '/js/bulk-optimization.js', __FILE__ ),
356 array(),
357 self::version(),
358 true
359 );
360
361 wp_enqueue_script( self::NAME . '_tiny_bulk_optimization' );
362 }
363 }
364
365 public function process_attachment( $metadata, $attachment_id ) {
366 if ( $this->settings->auto_compress_enabled() ) {
367 if (
368 $this->settings->background_compress_enabled()
369 ) {
370 $this->async_compress_on_upload( $metadata, $attachment_id );
371 } else {
372 return $this->blocking_compress_on_upload( $metadata, $attachment_id );
373 }
374 }
375
376 return $metadata;
377 }
378
379 public function blocking_compress_on_upload( $metadata, $attachment_id ) {
380 if ( ! empty( $metadata ) ) {
381 $tiny_image = new Tiny_Image( $this->settings, $attachment_id, $metadata );
382
383 Tiny_Logger::debug(
384 'blocking compress on upload',
385 array(
386 'image_id' => $attachment_id,
387 )
388 );
389
390 $result = $tiny_image->compress();
391 return $tiny_image->get_wp_metadata();
392 } else {
393 return $metadata;
394 }
395 }
396
397 public function async_compress_on_upload( $metadata, $attachment_id ) {
398 $context = 'wp';
399 $action = 'tiny_async_optimize_upload_new_media';
400 $_ajax_nonce = wp_create_nonce( 'new_media-' . $attachment_id );
401 $body = compact( 'action', '_ajax_nonce', 'metadata', 'attachment_id', 'context' );
402
403 $args = array(
404 'timeout' => 0.01,
405 'blocking' => false,
406 'body' => $body,
407 'cookies' => isset( $_COOKIE ) && is_array( $_COOKIE ) ? $_COOKIE : array(),
408 'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
409 );
410
411 if ( defined( 'XMLRPC_REQUEST' ) && get_current_user_id() ) {
412 /* We generate a hash to be used for the transient we use to store the current user. */
413 $rpc_hash = md5( maybe_serialize( $body ) );
414
415 $args['body']['tiny_rpc_action'] = $args['body']['action'];
416 /* We set a different action to make sure that all RPC requests are first validated. */
417 $args['body']['action'] = 'tiny_rpc';
418 $args['body']['tiny_rpc_hash'] = $rpc_hash;
419 $args['body']['tiny_rpc_nonce'] = wp_create_nonce( 'tiny_rpc_' . $rpc_hash );
420
421 /*
422 We can't use cookies here, so we save the user id in a transient
423 so that we can retrieve it again when processing the RPC request.
424 We should be able to use a relatively short timeout, as the request
425 should be processed directly afterwards.
426 */
427 set_transient( 'tiny_rpc_' . $rpc_hash, get_current_user_id(), 10 );
428 }
429
430 Tiny_Logger::debug(
431 'remote post',
432 array(
433 'image_id' => $attachment_id,
434 )
435 );
436
437 if ( getenv( 'WORDPRESS_HOST' ) !== false ) {
438 wp_remote_post( getenv( 'WORDPRESS_HOST' ) . '/wp-admin/admin-ajax.php', $args );
439 } else {
440 wp_remote_post( admin_url( 'admin-ajax.php' ), $args );
441 }
442 }
443
444 public function process_rpc_request() {
445 if (
446 empty( $_POST['tiny_rpc_action'] ) ||
447 empty( $_POST['tiny_rpc_hash'] )
448 ) {
449 exit();
450 }
451
452 $rpc_hash = sanitize_key( wp_unslash( $_POST['tiny_rpc_hash'] ) );
453 if ( 32 !== strlen( $rpc_hash ) ) {
454 exit();
455 }
456
457 $user_id = absint( get_transient( 'tiny_rpc_' . $rpc_hash ) );
458 $user = $user_id ? get_userdata( $user_id ) : false;
459
460 /* We no longer need the transient. */
461 delete_transient( 'tiny_rpc_' . $rpc_hash );
462
463 if ( ! $user || ! $user->exists() ) {
464 exit();
465 }
466 wp_set_current_user( $user_id );
467
468 if ( ! check_ajax_referer( 'tiny_rpc_' . $rpc_hash, 'tiny_rpc_nonce', false ) ) {
469 exit();
470 }
471
472 /* Now that everything is checked, perform the actual action. */
473 $action = sanitize_key( wp_unslash( $_POST['tiny_rpc_action'] ) );
474 unset(
475 $_POST['action'],
476 $_POST['tiny_rpc_action'],
477 $_POST['tiny_rpc_id'],
478 $_POST['tiny_rpc_nonce']
479 );
480 do_action( 'wp_ajax_' . $action );
481 }
482
483 public function compress_on_upload() {
484 $nonce = isset( $_POST['_ajax_nonce'] ) ?
485 sanitize_key( wp_unslash( $_POST['_ajax_nonce'] ) ) : '';
486 $attachment_id = isset( $_POST['attachment_id'] ) ?
487 intval( wp_unslash( $_POST['attachment_id'] ) ) : 0;
488
489 if ( ! wp_verify_nonce( $nonce, 'new_media-' . $attachment_id ) ) {
490 exit;
491 }
492 if ( current_user_can( 'upload_files' ) ) {
493 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
494 $metadata = isset( $_POST['metadata'] ) ? wp_unslash( $_POST['metadata'] ) : array();
495 if ( is_array( $metadata ) ) {
496 $tiny_image = new Tiny_Image( $this->settings, $attachment_id, $metadata );
497
498 Tiny_Logger::debug(
499 'compress on upload',
500 array(
501 'image_id' => $attachment_id,
502 )
503 );
504
505 $result = $tiny_image->compress();
506 // The wp_update_attachment_metadata call is thrown because the
507 // dimensions of the original image can change. This will then
508 // trigger other plugins and can result in unexpected behaviour and
509 // further changes to the image. This may require another approach.
510 // Note that as of WP 5.3 it is advised to not hook into this filter
511 // anymore, so other plugins are less likely to be triggered.
512 wp_update_attachment_metadata( $attachment_id, $tiny_image->get_wp_metadata() );
513 }
514 }
515 exit();
516 }
517
518 /**
519 * Validates AJAX request for attachment operations.
520 *
521 * @since 3.0.0
522 *
523 * @return array Either error array ['error' => 'message']
524 * or success array ['data' => [$id, $metadata]]
525 */
526 private function validate_ajax_attachment_request() {
527 check_ajax_referer( 'tiny-compress', '_nonce' );
528
529 if ( ! current_user_can( 'upload_files' ) ) {
530 return array(
531 'error' => esc_html__(
532 "You don't have permission to upload files.",
533 'tiny-compress-images'
534 ),
535 );
536 }
537 if ( empty( $_POST['id'] ) ) {
538 return array(
539 'error' => esc_html__(
540 'Not a valid media file.',
541 'tiny-compress-images'
542 ),
543 );
544 }
545 $id = intval( $_POST['id'] );
546 $metadata = wp_get_attachment_metadata( $id );
547 if ( ! is_array( $metadata ) ) {
548 return array(
549 'error' => esc_html__(
550 'Could not find metadata of media file.',
551 'tiny-compress-images'
552 ),
553 );
554 }
555
556 return array(
557 'data' => array( $id, $metadata ),
558 );
559 }
560
561 public function compress_image_from_library() {
562 $response = $this->validate_ajax_attachment_request();
563 if ( isset( $response['error'] ) ) {
564 echo esc_html( $response['error'] );
565 exit();
566 }
567 list($id, $metadata) = $response['data'];
568
569 Tiny_Logger::debug(
570 'compress from library',
571 array(
572 'image_id' => $id,
573 )
574 );
575
576 $tiny_image = new Tiny_Image( $this->settings, $id, $metadata );
577 $result = $tiny_image->compress();
578
579 // The wp_update_attachment_metadata call is thrown because the
580 // dimensions of the original image can change. This will then
581 // trigger other plugins and can result in unexpected behaviour and
582 // further changes to the image. This may require another approach.
583 // Note that as of WP 5.3 it is advised to not hook into this filter
584 // anymore, so other plugins are less likely to be triggered.
585 wp_update_attachment_metadata( $id, $tiny_image->get_wp_metadata() );
586
587 $this->render_compress_details( $tiny_image );
588
589 exit();
590 }
591
592 public function compress_image_for_bulk() {
593 $response = $this->validate_ajax_attachment_request();
594 if ( isset( $response['error'] ) ) {
595 echo json_encode( $response );
596 exit();
597 }
598
599 list($id, $metadata) = $response['data'];
600 $tiny_image_before = new Tiny_Image( $this->settings, $id, $metadata );
601 $image_statistics_before = $tiny_image_before->get_statistics(
602 $this->settings->get_sizes(),
603 $this->settings->get_active_tinify_sizes()
604 );
605 $size_before = $image_statistics_before['compressed_total_size'];
606
607 $tiny_image = new Tiny_Image( $this->settings, $id, $metadata );
608
609 Tiny_Logger::debug(
610 'compress from bulk',
611 array(
612 'image_id' => $id,
613 )
614 );
615
616 $result = $tiny_image->compress();
617 $image_statistics = $tiny_image->get_statistics(
618 $this->settings->get_sizes(),
619 $this->settings->get_active_tinify_sizes()
620 );
621 wp_update_attachment_metadata( $id, $tiny_image->get_wp_metadata() );
622
623 // Nonce verified in validate_ajax_attachment_request().
624 // phpcs:disable WordPress.Security.NonceVerification.Missing
625 $current_library_size = isset( $_POST['current_size'] ) ?
626 intval( wp_unslash( $_POST['current_size'] ) )
627 : 0;
628 // phpcs:enable WordPress.Security.NonceVerification.Missing
629 $size_after = $image_statistics['compressed_total_size'];
630 $new_library_size = $current_library_size + $size_after - $size_before;
631
632 $result['message'] = $tiny_image->get_latest_error();
633 $result['image_sizes_compressed'] = $image_statistics['image_sizes_compressed'];
634 $result['image_sizes_converted'] = $image_statistics['image_sizes_converted'];
635 $result['image_sizes_optimized'] = $image_statistics['image_sizes_optimized'];
636
637 $result['initial_total_size'] = size_format(
638 $image_statistics['initial_total_size'],
639 1
640 );
641
642 $result['optimized_total_size'] = size_format(
643 $image_statistics['compressed_total_size'],
644 1
645 );
646
647 $result['savings'] = $tiny_image->get_savings( $image_statistics );
648 $result['status'] = $this->settings->get_status();
649 $result['thumbnail'] = wp_get_attachment_image(
650 $id,
651 array( '30', '30' ),
652 true,
653 array(
654 'class' => 'pinkynail',
655 'alt' => '',
656 )
657 );
658 $result['size_change'] = $size_after - $size_before;
659 $result['human_readable_library_size'] = size_format( $new_library_size, 2 );
660
661 echo json_encode( $result );
662
663 exit();
664 }
665
666 public function ajax_optimization_statistics() {
667 if ( check_ajax_referer( 'tiny-compress', '_nonce', false ) &&
668 current_user_can( 'upload_files' ) ) {
669 $stats = Tiny_Bulk_Optimization::get_optimization_statistics( $this->settings );
670 echo json_encode( $stats );
671 }
672 exit();
673 }
674
675 public function ajax_compression_status() {
676 $response = $this->validate_ajax_attachment_request();
677
678 if ( isset( $response['error'] ) ) {
679 echo esc_html( $response['error'] );
680 exit();
681 }
682 list($id, $metadata) = $response['data'];
683
684 $tiny_image = new Tiny_Image( $this->settings, $id, $metadata );
685
686 $this->render_compress_details( $tiny_image );
687
688 exit();
689 }
690
691 public function media_library_bulk_action() {
692 $valid_actions = array( 'tiny_bulk_action', 'tiny_bulk_mark_compressed' );
693 $action = isset( $_REQUEST['action'] ) ?
694 sanitize_key( wp_unslash( $_REQUEST['action'] ) ) : '';
695 $action2 = isset( $_REQUEST['action2'] ) ?
696 sanitize_key( wp_unslash( $_REQUEST['action2'] ) ) : '';
697
698 if (
699 ! in_array( $action, $valid_actions, true ) &&
700 ! in_array( $action2, $valid_actions, true )
701 ) {
702 return;
703 }
704 $media = isset( $_REQUEST['media'] ) ?
705 array_map( 'intval', wp_unslash( (array) $_REQUEST['media'] ) )
706 : array();
707 if ( empty( $media ) ) {
708 $_REQUEST['action'] = '';
709 return;
710 }
711 check_admin_referer( 'bulk-media' );
712 $ids = implode( '-', $media );
713 $location = 'upload.php?mode=list&ids=' . $ids;
714
715 $location = add_query_arg( 'action', $action, $location );
716 $location = add_query_arg( '_tiny_nonce', wp_create_nonce( 'tiny-bulk-ids' ), $location );
717
718 if ( ! empty( $_REQUEST['paged'] ) ) {
719 $location = add_query_arg( 'paged', absint( $_REQUEST['paged'] ), $location );
720 }
721 if ( ! empty( $_REQUEST['s'] ) ) {
722 $location = add_query_arg(
723 's',
724 sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ),
725 $location
726 );
727 }
728 if ( ! empty( $_REQUEST['m'] ) ) {
729 $location = add_query_arg(
730 'm',
731 sanitize_text_field( wp_unslash( $_REQUEST['m'] ) ),
732 $location
733 );
734 }
735
736 wp_safe_redirect( admin_url( $location ) );
737 exit();
738 }
739
740 public function add_media_columns( $columns ) {
741 $columns[ self::MEDIA_COLUMN ] = esc_html__( 'Compression', 'tiny-compress-images' );
742 return $columns;
743 }
744
745 public function render_media_column( $column, $id ) {
746 if ( self::MEDIA_COLUMN === $column ) {
747 $tiny_image = new Tiny_Image( $this->settings, $id );
748 if ( $tiny_image->file_type_allowed() ) {
749 echo '<div class="tiny-ajax-container">';
750 $this->render_compress_details( $tiny_image );
751 echo '</div>';
752 }
753 }
754 }
755
756 public function show_media_info() {
757 global $post;
758 $tiny_image = new Tiny_Image( $this->settings, $post->ID );
759 if ( $tiny_image->file_type_allowed() ) {
760 echo '<div class="misc-pub-section tiny-compress-images">';
761 echo '<h4>';
762 esc_html_e( 'JPEG, PNG, & WebP optimization', 'tiny-compress-images' );
763 echo '</h4>';
764 echo '<div class="tiny-ajax-container">';
765 $this->render_compress_details( $tiny_image );
766 echo '</div>';
767 echo '</div>';
768 }
769 }
770
771 private function render_compress_details( $tiny_image ) {
772 $images_to_compress = array();
773
774 if ( ! empty( $_GET['ids'] ) ) {
775 $nonce = isset( $_GET['_tiny_nonce'] ) ?
776 sanitize_key( wp_unslash( $_GET['_tiny_nonce'] ) ) : '';
777
778 if ( $nonce && wp_verify_nonce( $nonce, 'tiny-bulk-ids' ) ) {
779 $request_ids = sanitize_text_field( wp_unslash( $_GET['ids'] ) );
780 $images_to_compress = array_map( 'intval', explode( '-', $request_ids ) );
781 }
782 }
783
784 $in_progress = $tiny_image->filter_image_sizes( 'in_progress' );
785 if ( count( $in_progress ) > 0 ) {
786 include __DIR__ . '/views/compress-details-processing.php';
787 } else {
788 include __DIR__ . '/views/compress-details.php';
789 }
790 }
791
792 public function get_estimated_bulk_cost( $estimated_credit_use ) {
793 return Tiny_Compress::estimate_cost(
794 $estimated_credit_use,
795 $this->settings->get_compression_count()
796 );
797 }
798
799 public function render_bulk_optimization_page() {
800 $stats = Tiny_Bulk_Optimization::get_optimization_statistics( $this->settings );
801
802 $estimated_costs = $this->get_estimated_bulk_cost( $stats['estimated_credit_use'] );
803 $admin_colors = self::retrieve_admin_colors();
804
805 /* This makes sure that up to date information is retrieved from the API. */
806 $this->settings->get_compressor()->get_status();
807
808 $active_tinify_sizes = $this->settings->get_active_tinify_sizes();
809 $remaining_credits = $this->settings->get_remaining_credits();
810 $is_on_free_plan = $this->settings->is_on_free_plan();
811 $email_address = $this->settings->get_email_address();
812
813 include __DIR__ . '/views/bulk-optimization.php';
814 }
815
816 public function add_dashboard_widget() {
817 wp_enqueue_style(
818 self::NAME . '_chart',
819 plugins_url( '/css/optimization-chart.css', __FILE__ ),
820 array(),
821 self::version()
822 );
823
824 wp_enqueue_style(
825 self::NAME . '_dashboard_widget',
826 plugins_url( '/css/dashboard-widget.css', __FILE__ ),
827 array(),
828 self::version()
829 );
830
831 wp_register_script(
832 self::NAME . '_dashboard_widget',
833 plugins_url( '/js/dashboard-widget.js', __FILE__ ),
834 array(),
835 self::version(),
836 true
837 );
838
839 /*
840 This might be deduplicated with the admin script localization, but
841 the order of including scripts is sometimes different. So in that
842 case we need to make sure that the order of inclusion is correct. */
843 wp_localize_script(
844 self::NAME . '_dashboard_widget',
845 'tinyCompressDashboard',
846 array(
847 'nonce' => wp_create_nonce( 'tiny-compress' ),
848 )
849 );
850
851 wp_enqueue_script( self::NAME . '_dashboard_widget' );
852
853 wp_add_dashboard_widget(
854 $this->get_prefixed_name( 'dashboard_widget' ),
855 esc_html__( 'TinyPNG - JPEG, PNG & WebP image compression', 'tiny-compress-images' ),
856 $this->get_method( 'add_widget_view' )
857 );
858 }
859
860 public function add_widget_view() {
861 $admin_colors = self::retrieve_admin_colors();
862 include __DIR__ . '/views/dashboard-widget.php';
863 }
864
865 private static function retrieve_admin_colors() {
866 global $_wp_admin_css_colors;
867 $admin_colour_scheme = get_user_option( 'admin_color', get_current_user_id() );
868 $admin_colors = array( '#0074aa', '#1685b5', '#78ca44', '#0086ba' ); // default
869 if ( isset( $_wp_admin_css_colors[ $admin_colour_scheme ] ) ) {
870 if ( isset( $_wp_admin_css_colors[ $admin_colour_scheme ]->colors ) ) {
871 $admin_colors = $_wp_admin_css_colors[ $admin_colour_scheme ]->colors;
872 }
873 }
874 if ( '#e5e5e5' == $admin_colors[0] && '#999' == $admin_colors[1] ) {
875 $admin_colors[0] = '#bbb';
876 }
877 if ( '#5589aa' == $admin_colors[0] && '#cfdfe9' == $admin_colors[1] ) {
878 $admin_colors[1] = '#85aec5';
879 }
880 if ( '#7c7976' == $admin_colors[0] && '#c6c6c6' == $admin_colors[1] ) {
881 $admin_colors[1] = '#adaba9';
882 $admin_colors[2] = '#adaba9';
883 }
884 if ( self::wp_version() > 3.7 ) {
885 if ( 'fresh' == $admin_colour_scheme ) {
886 $admin_colors = array( '#0074aa', '#1685b5', '#78ca44', '#0086ba' ); // better
887 }
888 }
889 return $admin_colors;
890 }
891
892 public function friendly_user_name() {
893 $user = wp_get_current_user();
894 $name = ucfirst( empty( $user->first_name ) ? $user->display_name : $user->first_name );
895 return $name;
896 }
897
898 /**
899 * Will clean up converted files (if any) when the original is deleted
900 *
901 * Hooked to the `delete_attachment` action.
902 *
903 * @see https://developer.wordpress.org/reference/hooks/deleted_post/
904 *
905 * @param [int] $post_id
906 *
907 * @return void
908 */
909 public function clean_attachment( $post_id ) {
910 $tiny_image = new Tiny_Image( $this->settings, $post_id );
911 $tiny_image->delete_converted_image();
912 }
913
914 /**
915 * Creates a backup of an image size before compression.
916 *
917 * Hooked to the `tiny_image_before_compression` action. Only creates
918 * a backup for the original image size when the backup setting is enabled.
919 * The backup is stored under {upload_dir}/tinify_backup/, preserving the
920 * original path structure relative to the uploads base directory.
921 *
922 * When an unscaled original exists, that is backed up instead of the
923 * scaled version.
924 *
925 * @since 3.7.0
926 *
927 * @param int $attachment_id The ID of the attachment
928 * @return bool return true on backup created
929 */
930 public function backup_original_image( $attachment_id ) {
931 if ( ! $this->settings->get_backup_enabled() ) {
932 return false;
933 }
934
935 $tiny_image = new Tiny_Image( $this->settings, $attachment_id );
936
937 $original_image = $tiny_image->get_image_size( Tiny_Image::ORIGINAL_UNSCALED );
938 if ( null === $original_image ) {
939 $original_image = $tiny_image->get_image_size();
940 }
941
942 if ( null === $original_image ) {
943 return false;
944 }
945
946 $file_path = $original_image->filename;
947 $upload_dir = wp_upload_dir();
948 $basedir = trailingslashit( $upload_dir['basedir'] );
949 if ( Tiny_Helpers::str_starts_with( $file_path, $basedir ) ) {
950 $file_path = substr( $file_path, strlen( $basedir ) );
951 }
952
953 $backup_file = $basedir . 'tinify_backup/' . $file_path;
954
955 $wp_filesystem = Tiny_Helpers::get_wp_filesystem();
956
957 if ( $wp_filesystem->exists( $backup_file ) ) {
958 return false;
959 }
960
961 $backup_dir = dirname( $backup_file );
962
963 if ( ! wp_mkdir_p( $backup_dir ) ) {
964 return false;
965 }
966
967 return $wp_filesystem->copy( $original_image->filename, $backup_file );
968 }
969
970 public static function request_review() {
971 $review_url =
972 'https://wordpress.org/support/plugin/tiny-compress-images/reviews/#new-post';
973 $review_block = esc_html__( 'Enjoying TinyPNG?', 'tiny-compress-images' );
974 $review_block .= ' ';
975 $review_block .= sprintf(
976 '<a href="%s" target="_blank">%s</a>',
977 esc_url( $review_url ),
978 esc_html__( 'Write a review', 'tiny-compress-images' )
979 );
980 return $review_block;
981 }
982
983 /**
984 * Runs on uninstall
985 *
986 * @return void
987 */
988 public static function uninstall() {
989 Tiny_Apache_Rewrite::uninstall_rules();
990 }
991
992 public function mark_image_as_compressed() {
993 $response = $this->validate_ajax_attachment_request();
994 if ( isset( $response['error'] ) ) {
995 echo esc_html( $response['error'] );
996 exit();
997 }
998
999 list($id, $metadata) = $response['data'];
1000 $tiny_image = new Tiny_Image( $this->settings, $id, $metadata );
1001 $tiny_image->mark_as_compressed();
1002
1003 $this->render_compress_details( $tiny_image );
1004
1005 exit();
1006 }
1007 }
1008