Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
e7cdf57
update dashboard widget
tijmenbruggeman Sep 23, 2026
07a033d
refactor ui for dashboard widget
tijmenbruggeman Sep 23, 2026
90b3175
fix tests
tijmenbruggeman Sep 23, 2026
a612785
Use plugin version
tijmenbruggeman Sep 23, 2026
5a64197
fix empty label
tijmenbruggeman Sep 23, 2026
db7470b
simplify pay state
tijmenbruggeman Sep 23, 2026
4bf80a7
resolve e2e tests
tijmenbruggeman Sep 23, 2026
eb3edbc
format
tijmenbruggeman Sep 23, 2026
41d6570
feat: update texts
tijmenbruggeman Sep 29, 2026
2210523
set guard to only allow dashboard widget for users that can upload
tijmenbruggeman Oct 6, 2026
33fdc86
convert to private funcs
tijmenbruggeman Oct 6, 2026
5d3035a
use settings get pay state
tijmenbruggeman Oct 6, 2026
3a4846a
remove unused action and method
tijmenbruggeman Oct 6, 2026
c63df81
remove default valeus
tijmenbruggeman Oct 6, 2026
da29255
format and tests
tijmenbruggeman Oct 6, 2026
1b50e80
read credits and api key from settings in widget data
tijmenbruggeman Oct 6, 2026
da613b8
formatting
tijmenbruggeman Oct 6, 2026
a58f8a4
add doc
tijmenbruggeman Oct 6, 2026
f14c406
Merge branch 'master' of github.com:wcreateweb/wordpress-plugin into …
tijmenbruggeman Oct 6, 2026
1b2ec35
Merge branch 'master' of github.com:wcreateweb/wordpress-plugin into …
tijmenbruggeman Oct 19, 2026
90595ef
feat: refresh pending api key status in settings
tijmenbruggeman Oct 19, 2026
f500f6f
feat: store when a bulk run started
tijmenbruggeman Oct 19, 2026
e52ab1a
feat: redesign dashboard widget
tijmenbruggeman Oct 19, 2026
d2efee3
style: lighten the widget badge
tijmenbruggeman Oct 19, 2026
2b7091d
refactor: remove dashboard widget polling
tijmenbruggeman Oct 19, 2026
99c77e7
test: allow 15s for the background run
tijmenbruggeman Oct 19, 2026
bcaa6a1
style: slide between the widget views
tijmenbruggeman Oct 19, 2026
38258fb
simplify timeleft calculation
tijmenbruggeman Oct 20, 2026
7a34a89
css tuning
tijmenbruggeman Oct 20, 2026
8cec312
show auto compress when option is enabled
tijmenbruggeman Oct 20, 2026
9b52a85
PR fixes
tijmenbruggeman Oct 20, 2026
5cf9781
translation
tijmenbruggeman Oct 20, 2026
745e330
time_left to seconds_left
tijmenbruggeman Oct 20, 2026
c847cc1
fix: require a nonce to start the bulk run from the widget
tijmenbruggeman Oct 20, 2026
0a02b15
load widget async
tijmenbruggeman Oct 20, 2026
72014b6
show buttons conditionally
tijmenbruggeman Oct 20, 2026
77d56b1
remove unused state
tijmenbruggeman Oct 20, 2026
c211b55
cleanup
tijmenbruggeman Oct 20, 2026
718c377
hide notice when autostart
tijmenbruggeman Oct 20, 2026
f0695b7
hide trigger when autostart
tijmenbruggeman Oct 20, 2026
bb309d7
speed up tests
tijmenbruggeman Oct 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions src/class-tiny-dashboard.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
<?php
/*
* Tiny Compress Images - WordPress plugin.
* Copyright (C) 2015-2023 Tinify B.V.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
class Tiny_Dashboard extends Tiny_WP_Base {

/**
* @var Tiny_Settings settings
*/
private $settings;

/**
* @var Tiny_Background_Optimize
*/
private $background_optimizer;

/**
* @param Tiny_Settings $settings
* @param Tiny_Background_Optimize $background_optimizer
*/
public function __construct( $settings, $background_optimizer ) {
parent::__construct();
$this->settings = $settings;
$this->background_optimizer = $background_optimizer;
}

public function ajax_init() {
add_action(
'wp_ajax_tiny_dashboard_widget',
array( $this, 'ajax_dashboard_widget' )
);
}

public function admin_init() {
if ( current_user_can( 'upload_files' ) ) {
add_action(
'wp_dashboard_setup',
array( $this, 'add_dashboard_widget' )
);
}
}

public function add_dashboard_widget() {
wp_enqueue_style(
self::NAME . '_dashboard_widget',
plugins_url( '/css/dashboard-widget.css', __FILE__ ),
array(),
Tiny_Plugin::version()
);

wp_enqueue_script(
self::NAME . '_dashboard_widget',
plugins_url( '/js/dashboard-widget.js', __FILE__ ),
array(),
Tiny_Plugin::version(),
true
);

wp_add_dashboard_widget(
$this->get_prefixed_name( 'dashboard_widget' ),
esc_html__( 'TinyPNG - JPEG, PNG & WebP image compression', 'tiny-compress-images' ),
array( $this, 'add_widget_view' )
);
}

/**
* The statistics scan the whole library, so the dashboard shows a spinner
* and the widget JS fetches the real content from ajax_dashboard_widget().
*/
public function add_widget_view() {
include __DIR__ . '/views/dashboard-widget-loading.php';
}

public function ajax_dashboard_widget() {
if ( ! check_ajax_referer( 'tiny-dashboard-widget', '_nonce', false ) ||
! current_user_can( 'upload_files' ) ) {
status_header( 403 );
wp_die( -1, 403 );
}

$stats = Tiny_Bulk_Optimization::get_optimization_statistics( $this->settings );
$widget = $this->get_widget_data( $stats );
include __DIR__ . '/views/dashboard-widget.php';
wp_die();
}

/**
* Logic for the view
*
* @param array $optimization_stats See Tiny_Bulk_Optimization::get_optimization_statistics().
* @return array
*/
public function get_widget_data( $optimization_stats ) {
$images_total = max( 0, intval( $optimization_stats['uploaded-images'] ) );
$images_remaining = min(
$images_total,
count( $optimization_stats['available-for-optimization'] )
);
$images_optimized = $images_total - $images_remaining;
$bytes_saved = $optimization_stats['unoptimized-library-size']
- $optimization_stats['optimized-library-size'];

$percentage = $images_total > 0
? intval( floor( $images_optimized / $images_total * 100 ) )
: 0;

$estimated_credit_use = intval( $optimization_stats['estimated_credit_use'] );

// Only free accounts have a limited number of credits, including
// free accounts that bought a credit bundle.
$remaining_credits = $this->settings->get_remaining_credits();
$has_limited_credits = $this->settings->is_on_free_plan() &&
is_numeric( $remaining_credits );
$remaining_credits = $has_limited_credits ? intval( $remaining_credits ) : null;

// How many of the remaining images the credits still cover.
$images_affordable = 0;
if ( $has_limited_credits && $images_remaining > 0 && $estimated_credit_use > 0 ) {
$images_affordable = intval(
floor( $remaining_credits / ( $estimated_credit_use / $images_remaining ) )
);
}

$seconds_left = null;
if ( ! $this->settings->has_api_key() ) {
$state = 'not_connected';
} elseif ( $this->settings->refresh_api_key_pending() ) {
$state = 'pending';
} elseif ( 0 === $images_total ) {
$state = 'empty';
} elseif ( $this->background_optimizer->is_running() ) {
$state = 'compressing';

$seconds_left = intval(
round( $estimated_credit_use / Tiny_Background_Optimize::WORKERS )
);
} elseif ( 0 === $images_remaining ) {
$state = 'done';
} elseif ( 0 === $remaining_credits ) {
$state = 'out_of_credits';
} elseif ( $has_limited_credits && $estimated_credit_use > $remaining_credits ) {
$state = 'credits_short';
} else {
$state = 'ready';
}

return array(
'state' => $state,
'images_optimized' => $images_optimized,
'images_total' => $images_total,
'images_remaining' => $images_remaining,
'images_affordable' => $images_affordable,
'bytes_saved' => max( 0, intval( $bytes_saved ) ),
'percentage' => $percentage,
'estimated_credit_use' => $estimated_credit_use,
'remaining_credits' => $remaining_credits,
'seconds_left' => $seconds_left,
'email_address' => $this->settings->get_email_address(),
'auto_compress' => $this->settings->auto_compress_enabled(),
);
}
}
78 changes: 6 additions & 72 deletions src/class-tiny-plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public function __construct() {
$this->settings = new Tiny_Settings();
new Tiny_Conversion( $this->settings );
$this->background_optimizer = new Tiny_Background_Optimize( $this->settings );
new Tiny_Dashboard( $this->settings, $this->background_optimizer );
}

public function set_compressor( $compressor ) {
Expand Down Expand Up @@ -125,11 +126,6 @@ public function ajax_init() {
$this->get_method( 'ajax_bulk_queue_optimize_next' )
);

add_action(
'wp_ajax_tiny_get_optimization_statistics',
$this->get_method( 'ajax_optimization_statistics' )
);

add_action(
'wp_ajax_tiny_get_compression_status',
$this->get_method( 'ajax_compression_status' )
Expand Down Expand Up @@ -178,11 +174,6 @@ public function ajax_init() {
}

public function admin_init() {
add_action(
'wp_dashboard_setup',
$this->get_method( 'add_dashboard_widget' )
);

add_action(
'admin_enqueue_scripts',
$this->get_method( 'enqueue_scripts' )
Expand Down Expand Up @@ -803,15 +794,6 @@ public function ajax_bulk_queue_cancel() {
wp_send_json_success();
}

public function ajax_optimization_statistics() {
if ( check_ajax_referer( 'tiny-compress', '_nonce', false ) &&
current_user_can( 'upload_files' ) ) {
$stats = Tiny_Bulk_Optimization::get_optimization_statistics( $this->settings );
echo json_encode( $stats );
}
exit();
}

public function ajax_compression_status() {
$response = $this->validate_ajax_attachment_request();

Expand Down Expand Up @@ -952,61 +934,13 @@ public function render_bulk_optimization_page() {
$email_address = $this->settings->get_email_address();
$bulk_running = $this->background_optimizer->is_running();

include __DIR__ . '/views/bulk-optimization.php';
}

public function add_dashboard_widget() {
wp_enqueue_style(
self::NAME . '_chart',
plugins_url( '/css/optimization-chart.css', __FILE__ ),
array(),
self::version()
);

wp_enqueue_style(
self::NAME . '_dashboard_widget',
plugins_url( '/css/dashboard-widget.css', __FILE__ ),
array(),
self::version()
);

wp_register_script(
self::NAME . '_dashboard_widget',
plugins_url( '/js/dashboard-widget.js', __FILE__ ),
array(),
self::version(),
true
/* The dashboard widget links here with a nonce to start right away. */
$start_on_load = isset( $_GET['start'], $_GET['_wpnonce'] ) && wp_verify_nonce(
sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ),
'tiny-bulk-start'
);

/*
This might be deduplicated with the admin script localization, but
the order of including scripts is sometimes different. So in that
case we need to make sure that the order of inclusion is correct. */
wp_localize_script(
self::NAME . '_dashboard_widget',
'tinyCompressDashboard',
array(
'nonce' => wp_create_nonce( 'tiny-compress' ),
)
);

wp_enqueue_script( self::NAME . '_dashboard_widget' );

$widget_title = esc_html__(
'TinyPNG Image Optimization – Image Compression JPEG & PNG | Convert to WebP & AVIF',
'tiny-compress-images'
);

wp_add_dashboard_widget(
$this->get_prefixed_name( 'dashboard_widget' ),
$widget_title,
$this->get_method( 'add_widget_view' )
);
}

public function add_widget_view() {
$admin_colors = self::retrieve_admin_colors();
include __DIR__ . '/views/dashboard-widget.php';
include __DIR__ . '/views/bulk-optimization.php';
}

private static function retrieve_admin_colors() {
Expand Down
46 changes: 34 additions & 12 deletions src/class-tiny-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,10 @@ protected function get_api_key() {
}
}

public function has_api_key() {
return (bool) $this->get_api_key();
}

protected function get_api_key_pending() {
if ( defined( 'TINY_API_KEY' ) ) {
return false;
Expand All @@ -236,6 +240,24 @@ protected function clear_api_key_pending() {
delete_option( self::get_prefixed_name( 'api_key_pending' ) );
}

/**
* Whether the API key still waits for its activation link to be opened.
* Asks the API only while the key is pending, and clears the flag once
* the key works.
*
* @return bool
*/
public function refresh_api_key_pending() {
if ( ! $this->get_api_key_pending() ) {
return false;
}
if ( $this->compressor->get_status()->ok ) {
$this->clear_api_key_pending();
return false;
}
return true;
}

protected static function get_intermediate_size( $size ) {
/*
Inspired by
Expand Down Expand Up @@ -846,18 +868,18 @@ public function render_account_status() {
include __DIR__ . '/views/account-status-create-simple.php';
}
} else {
$status = $this->compressor->get_status();
$status->pending = false;
if ( $status->ok ) {
if ( $this->get_api_key_pending() ) {
$this->clear_api_key_pending();
}
} elseif ( $this->get_api_key_pending() ) {
$status->ok = true;
$status->pending = true;
$status->message = (
'An email has been sent to activate your account'
);
if ( $this->refresh_api_key_pending() ) {
$status = (object) array(
'ok' => true,
'pending' => true,
'message' => __(
'An email has been sent to activate your account',
'tiny-compress-images'
),
);
} else {
$status = $this->compressor->get_status();
$status->pending = false;
}
include __DIR__ . '/views/account-status-connected.php';
}
Expand Down
Loading
Loading