Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
e303d3a
add lib wp-background-processing
tijmenbruggeman Sep 29, 2026
925cf8e
Override get_batches and implement background processing
tijmenbruggeman Sep 29, 2026
4a75120
Merge branch 'master' of github.com:wcreateweb/wordpress-plugin into …
tijmenbruggeman Oct 7, 2026
9040a0a
run 5 workers on the queue
tijmenbruggeman Oct 7, 2026
6b79e02
remove phpcs ignores
tijmenbruggeman Oct 7, 2026
ede4813
format
tijmenbruggeman Oct 7, 2026
ea85650
no need for returning the running images
tijmenbruggeman Oct 7, 2026
c82d6b8
exit() is not required, wp_send_json_success does this
tijmenbruggeman Oct 7, 2026
8e59b1c
stop compressing when out of credits
tijmenbruggeman Oct 7, 2026
464af39
prevent syntax out from WP 4.0 and up
tijmenbruggeman Oct 7, 2026
36844aa
Move to loopback workers with postmeta queue
tijmenbruggeman Oct 7, 2026
00c0365
select column
tijmenbruggeman Oct 7, 2026
823a86a
obsolete true
tijmenbruggeman Oct 7, 2026
cd18b77
reuse next_queued and get_processing
tijmenbruggeman Oct 7, 2026
312a7f2
rename classes
tijmenbruggeman Oct 7, 2026
2b6d64b
rename
tijmenbruggeman Oct 7, 2026
fc1bdf2
reduce stalled
tijmenbruggeman Oct 7, 2026
970b265
format
tijmenbruggeman Oct 7, 2026
ba5414e
run workers as the logged in user with a nonce
tijmenbruggeman Oct 7, 2026
95f8e56
Self heal each worker
tijmenbruggeman Oct 9, 2026
a20de51
No need for a restart
tijmenbruggeman Oct 9, 2026
8f476ee
delete_transient after task and set transient on start_worker
tijmenbruggeman Oct 9, 2026
2d97915
consistent is_active
tijmenbruggeman Oct 9, 2026
dc551c1
race condition: set transient and then start the missing workers
tijmenbruggeman Oct 9, 2026
8e88d05
start new worker in work, then check inactive workers
tijmenbruggeman Oct 9, 2026
bdae1e0
fix phpcs and add debug/error logs
tijmenbruggeman Oct 9, 2026
dfd8d0e
do not show all done when items are cancelled
tijmenbruggeman Oct 9, 2026
09b2ef1
Catch errors when cancel fails
tijmenbruggeman Oct 9, 2026
d26de1d
make a distinction between stopped and cancelled
tijmenbruggeman Oct 9, 2026
3d1da4a
Show cancel once everything is queued
tijmenbruggeman Oct 9, 2026
fe2765e
remove comment
tijmenbruggeman Oct 9, 2026
8dec83f
falls back to front-end requests if loopback is not supported
tijmenbruggeman Oct 12, 2026
73643e7
consistent names
tijmenbruggeman Oct 12, 2026
5a90b09
do not follow redirection on the ping
tijmenbruggeman Oct 12, 2026
129f626
return status_header
tijmenbruggeman Oct 12, 2026
f7bff49
add e2e tests + util that improves speed
tijmenbruggeman Oct 12, 2026
e8e8011
add assertions
tijmenbruggeman Oct 12, 2026
7be30a3
resolve e2e
tijmenbruggeman Oct 12, 2026
134c692
add clear func and cleanup on uninstall
tijmenbruggeman Oct 14, 2026
f415456
clarify loopback post
tijmenbruggeman Oct 14, 2026
d7669e2
add mock for unslash
tijmenbruggeman Oct 14, 2026
2c65f7f
use db query instead of seperate meta data inserts
tijmenbruggeman Oct 14, 2026
f309057
allow direct response on fastcgi and litespeed
tijmenbruggeman Oct 14, 2026
b242915
docs for 0.01s timeout and blocking
tijmenbruggeman Oct 14, 2026
4248994
docs for https
tijmenbruggeman Oct 14, 2026
f0c5046
remove debug log
tijmenbruggeman Oct 14, 2026
7a3e7a4
format
tijmenbruggeman Oct 14, 2026
ba05409
add posts to test helper
tijmenbruggeman Oct 15, 2026
05ef1e5
adjust tests for wp query
tijmenbruggeman Oct 15, 2026
2e597c3
fix loopbacks
tijmenbruggeman Oct 15, 2026
130535b
Added debug logs
tijmenbruggeman Oct 16, 2026
b754ff4
prevent double compress
tijmenbruggeman Oct 16, 2026
2b4e1d5
remove redundant log
tijmenbruggeman Oct 16, 2026
d034b71
remove redundant log
tijmenbruggeman Oct 16, 2026
6cfdc69
config compression duration and loopback support in mocks
tijmenbruggeman Oct 16, 2026
d8d61b1
format
tijmenbruggeman Oct 16, 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
520 changes: 520 additions & 0 deletions src/class-tiny-background-optimize.php

Large diffs are not rendered by default.

244 changes: 191 additions & 53 deletions src/class-tiny-plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@ class Tiny_Plugin extends Tiny_WP_Base {
const MEDIA_COLUMN = self::NAME;
const DATETIME_FORMAT = 'Y-m-d G:i:s';

private static $version;

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

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

public static function jpeg_quality() {
return 85;
Expand All @@ -42,6 +47,7 @@ public function __construct() {
parent::__construct();
$this->settings = new Tiny_Settings();
new Tiny_Conversion( $this->settings );
$this->background_optimizer = new Tiny_Background_Optimize( $this->settings );
}

public function set_compressor( $compressor ) {
Expand Down Expand Up @@ -100,8 +106,23 @@ public function ajax_init() {
);

add_action(
'wp_ajax_tiny_compress_image_for_bulk',
$this->get_method( 'compress_image_for_bulk' )
'wp_ajax_tiny_bulk_queue_start',
$this->get_method( 'ajax_bulk_queue_start' )
);

add_action(
'wp_ajax_tiny_bulk_queue_status',
$this->get_method( 'ajax_bulk_queue_status' )
);

add_action(
'wp_ajax_tiny_bulk_queue_cancel',
$this->get_method( 'ajax_bulk_queue_cancel' )
);

add_action(
'wp_ajax_tiny_bulk_queue_optimize_next',
$this->get_method( 'ajax_bulk_queue_optimize_next' )
);

add_action(
Expand Down Expand Up @@ -304,16 +325,13 @@ public function enqueue_scripts( $hook ) {
'No action taken',
'tiny-compress-images'
),
'L10nDuplicate' => __(
'Image was already processed',
'tiny-compress-images'
),
'L10nBulkAction' => __( 'Compress Images', 'tiny-compress-images' ),
'L10nBulkMarkCompressed' => __(
'Mark as Compressed',
'tiny-compress-images'
),
'L10nCancelled' => __( 'Cancelled', 'tiny-compress-images' ),
'L10nStopped' => __( 'Stopped', 'tiny-compress-images' ),
'L10nCompressing' => __( 'Compressing', 'tiny-compress-images' ),
'L10nCompressed' => __( 'compressed', 'tiny-compress-images' ),
'L10nConverted' => __( 'converted', 'tiny-compress-images' ),
Expand All @@ -335,6 +353,20 @@ public function enqueue_scripts( $hook ) {
'L10nInternalError' => __( 'Internal error', 'tiny-compress-images' ),
'L10nOutOf' => __( 'out of', 'tiny-compress-images' ),
'L10nWaiting' => __( 'Waiting', 'tiny-compress-images' ),
'L10nBackgroundNotice' => __(
// phpcs:ignore Generic.Files.LineLength
'Optimization continues in the background. You can close this page and come back later.',
'tiny-compress-images'
),
'L10nStoppedNotice' => __(
'Optimization stopped early.',
'tiny-compress-images'
),
'L10nInPageNotice' => __(
// phpcs:ignore Generic.Files.LineLength
'For the plugin to do the work, you need to keep this page open. But no worries: when stopped, you can continue where you left off!',
'tiny-compress-images'
),
)
);

Expand Down Expand Up @@ -594,47 +626,21 @@ public function compress_image_from_library() {
exit();
}

public function compress_image_for_bulk() {
$response = $this->validate_ajax_attachment_request();
if ( isset( $response['error'] ) ) {
echo json_encode( $response );
exit();
}

list($id, $metadata) = $response['data'];
$tiny_image_before = new Tiny_Image( $this->settings, $id, $metadata );
$image_statistics_before = $tiny_image_before->get_statistics(
$this->settings->get_sizes(),
$this->settings->get_active_tinify_sizes()
);
$size_before = $image_statistics_before['compressed_total_size'];

$tiny_image = new Tiny_Image( $this->settings, $id, $metadata );

Tiny_Logger::debug(
'compress from bulk',
array(
'image_id' => $id,
)
);

$result = $tiny_image->compress();
/**
* What the bulk optimization page shows about an image once it is done.
*
* @param Tiny_Image $tiny_image The image as it is after compressing.
* @param array $result What compressing it came to: at least
* 'success', 'failed', 'message' and
* 'size_change'.
* @return array
*/
private function bulk_image_result( $tiny_image, array $result ) {
$image_statistics = $tiny_image->get_statistics(
$this->settings->get_sizes(),
$this->settings->get_active_tinify_sizes()
);
wp_update_attachment_metadata( $id, $tiny_image->get_wp_metadata() );

// Nonce verified in validate_ajax_attachment_request().
// phpcs:disable WordPress.Security.NonceVerification.Missing
$current_library_size = isset( $_POST['current_size'] ) ?
intval( wp_unslash( $_POST['current_size'] ) )
: 0;
// phpcs:enable WordPress.Security.NonceVerification.Missing
$size_after = $image_statistics['compressed_total_size'];
$new_library_size = $current_library_size + $size_after - $size_before;

$result['message'] = $tiny_image->get_latest_error();
$result['image_sizes_compressed'] = $image_statistics['image_sizes_compressed'];
$result['image_sizes_converted'] = $image_statistics['image_sizes_converted'];
$result['image_sizes_optimized'] = $image_statistics['image_sizes_optimized'];
Expand All @@ -649,23 +655,153 @@ public function compress_image_for_bulk() {
1
);

$result['savings'] = $tiny_image->get_savings( $image_statistics );
$result['status'] = $this->settings->get_status();
$result['thumbnail'] = wp_get_attachment_image(
$id,
$result['savings'] = $tiny_image->get_savings( $image_statistics );
$result['status'] = $this->settings->get_status();
$result['thumbnail'] = wp_get_attachment_image(
$tiny_image->get_id(),
array( '30', '30' ),
true,
array(
'class' => 'pinkynail',
'alt' => '',
)
);
$result['size_change'] = $size_after - $size_before;
$result['human_readable_library_size'] = size_format( $new_library_size, 2 );

echo json_encode( $result );
return $result;
}

exit();
/**
* Retrieves images available for optimization and
* queues them for optimization
*/
public function ajax_bulk_queue_start() {
if ( ! check_ajax_referer( 'tiny-compress', '_nonce', false ) ||
! current_user_can( 'upload_files' ) ) {
status_header( 403 );
wp_die( -1, 403 );
}

/* A run that is still going is followed, not restarted. */
if ( $this->background_optimizer->is_running() ) {
wp_send_json_success( array( 'background' => true ) );
}

$stats = Tiny_Bulk_Optimization::get_optimization_statistics( $this->settings );
$ids = wp_list_pluck( $stats['available-for-optimization'], 'ID' );
$has_loopback = $this->background_optimizer->loopback();

Tiny_Logger::debug(
'bulk optimize starting',
array(
'in_background' => $has_loopback,
'image_count' => count( $ids ),
)
);

if ( $has_loopback ) {
// if loopback is supported, will optimize in background
$this->background_optimizer->start( $ids );
} else {
// if not, waits for request from front-end
$this->background_optimizer->queue( $ids );
}

wp_send_json_success( array( 'background' => $has_loopback ) );
}

/**
* Optimize the next queued image
*
* Used by front-end queue handler
* hooked in wp_ajax_tiny_bulk_queue_optimize_next
*
* @see src/js/bulk-optimization.js - optimizeInPage()
*/
public function ajax_bulk_queue_optimize_next() {
if ( ! check_ajax_referer( 'tiny-compress', '_nonce', false ) ||
! current_user_can( 'upload_files' ) ) {
status_header( 403 );
wp_die( -1, 403 );
}

$next_image = $this->background_optimizer->optimize_next();
Tiny_Logger::debug(
'bulk optimize_next',
array(
'optimized next image' => $next_image,
)
);

wp_send_json_success(
array( 'optimized' => $next_image )
);
}

/**
* Where the images the bulk optimization page is waiting for stand.
*
* Finished images come with the same details the page gets when it
* compresses an image itself, so it can show them the same way.
*/
public function ajax_bulk_queue_status() {
if ( ! check_ajax_referer( 'tiny-compress', '_nonce', false ) ||
! current_user_can( 'upload_files' ) ) {
status_header( 403 );
wp_die( -1, 403 );
}

$ids = isset( $_POST['ids'] ) ?
explode( ',', sanitize_text_field( wp_unslash( $_POST['ids'] ) ) )
: array();
$library_size = isset( $_POST['current_size'] ) ?
intval( wp_unslash( $_POST['current_size'] ) )
: 0;

$running = $this->background_optimizer->is_running();

$items = array();
foreach ( $this->background_optimizer->get_results( $ids ) as $id => $item ) {
if ( is_array( $item['result'] ) ) {
$item['result'] = $this->bulk_image_result(
new Tiny_Image( $this->settings, $id ),
array_merge(
array(
'success' => 0,
'failed' => 0,
'message' => null,
'size_change' => 0,
),
$item['result']
)
);

/* The page applies the results in order, each on top of the last. */
$library_size += $item['result']['size_change'];

$item['result']['human_readable_library_size'] = size_format( $library_size, 2 );
}

$items[ $id ] = $item;
}

wp_send_json_success(
array(
'running' => $running,
'items' => $items,
)
);
}

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

$this->background_optimizer->cancel();

wp_send_json_success();
}

public function ajax_optimization_statistics() {
Expand Down Expand Up @@ -815,6 +951,7 @@ public function render_bulk_optimization_page() {
$remaining_credits = $this->settings->get_remaining_credits();
$is_on_free_plan = $this->settings->is_on_free_plan();
$email_address = $this->settings->get_email_address();
$bulk_running = $this->background_optimizer->is_running();

include __DIR__ . '/views/bulk-optimization.php';
}
Expand Down Expand Up @@ -969,6 +1106,7 @@ public static function request_review() {
*/
public static function uninstall() {
Tiny_Apache_Rewrite::uninstall_rules();
Tiny_Background_Optimize::clear();
}

/**
Expand Down
Loading
Loading