• File: bulk-optimization-controller.php
  • Full Path: /home/builxejc/public_html/wp-content/plugins/image-optimization/modules/optimization/classes/bulk-optimization/bulk-optimization-controller.php
  • Date Modified: 02/01/2026 6:56 PM
  • File size: 7.67 KB
  • MIME-type: text/x-php
  • Charset: utf-8
<?php

namespace ImageOptimization\Modules\Optimization\Classes\Bulk_Optimization;

use ImageOptimization\Classes\Async_Operation\{
	Async_Operation,
	Async_Operation_Hook,
	Async_Operation_Queue,
	Exceptions\Async_Operation_Exception,
	Queries\Image_Optimization_Operation_Query
};
use ImageOptimization\Classes\Image\{
	Exceptions\Invalid_Image_Exception,
	Image,
	Image_Meta,
	Image_Optimization_Error_Type,
	Image_Query_Builder,
	Image_Status
};
use ImageOptimization\Classes\Exceptions\Quota_Exceeded_Error;
use ImageOptimization\Classes\Logger;
use ImageOptimization\Modules\Stats\Classes\Optimization_Stats;
use Throwable;

// @codeCoverageIgnoreStart
if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}
// @codeCoverageIgnoreEnd

final class Bulk_Optimization_Controller {
	/**
	 * Cancels pending bulk optimization operations.
	 *
	 * @return void
	 * @throws Async_Operation_Exception
	 */
	public static function delete_bulk_optimization(): void {
		$queue = new Bulk_Optimization_Queue( Bulk_Optimization_Queue_Type::OPTIMIZATION );

		if ( $queue->exists() ) {
			foreach ( $queue->get_image_ids() as $image_id ) {
				$meta = new Image_Meta( $image_id );
				$status = $meta->get_status();

				if ( Image_Status::OPTIMIZATION_IN_PROGRESS === $status ) {
					$meta->delete();
				}
			}

			$queue->delete();
		}

		$query = ( new Image_Optimization_Operation_Query() )
			->set_hook( Async_Operation_Hook::OPTIMIZE_BULK )
			->set_status( [ Async_Operation::OPERATION_STATUS_PENDING, Async_Operation::OPERATION_STATUS_RUNNING ] )
			->set_limit( 1 );

		$operation = Async_Operation::get( $query );

		if ( ! empty( $operation ) ) {
			Async_Operation::remove( [ $operation[0]->get_id() ] );
		}
	}

	/**
	 * Creates a queue for bulk optimization and schedules the processor.
	 *
	 * @return void
	 * @throws Quota_Exceeded_Error|Invalid_Image_Exception|Async_Operation_Exception
	 */
	public static function create_optimization_queue(): void {
		$images = Bulk_Optimization_Image_Query::find_images(
			( new Image_Query_Builder() )
				->return_not_optimized_images(),
			true
		);

		Logger::debug( 'Non-optimized images found: ' . $images['total_images_count'] );

		if ( ! $images['total_images_count'] ) {
			$not_fully_optimized_images = Bulk_Optimization_Image_Query::find_images(
				Bulk_Optimization_Image_Query::query_not_fully_optimized_images(),
				true
			);

			Logger::debug( 'Non fully optimized images found: ' . $images['total_images_count'] );

			if ( ! $not_fully_optimized_images['total_images_count'] ) {
				Logger::debug( 'Bulk optimization not started' );

				return;
			}

			$images = $not_fully_optimized_images;
		}

		$token_data = Bulk_Optimization_Token_Manager::obtain_token( $images['total_images_count'] );
		$queue = new Bulk_Optimization_Queue( Bulk_Optimization_Queue_Type::OPTIMIZATION );

		Logger::debug( "Bulk token obtained for {$token_data['batch_size']} images" );

		$queue
			->set_bulk_token(
				$token_data['token'],
				time() + HOUR_IN_SECONDS,
				$token_data['batch_size']
			)
			->set_status( Bulk_Optimization_Queue_Status::PROCESSING )
			->add_images( $images['attachments_in_quota'] )
			->save();

		Logger::info( "New queue {$queue->get_operation_id()} created" );

		Async_Operation::create(
			Async_Operation_Hook::OPTIMIZE_BULK,
			[ 'operation_id' => $queue->get_operation_id() ],
			Async_Operation_Queue::OPTIMIZE
		);

		Logger::debug( 'Async operation created' );

		foreach ( $images['attachments_in_quota'] as $image_id ) {
			( new Image_Meta( $image_id ) )
				->set_status( Image_Status::OPTIMIZATION_IN_PROGRESS )
				->set_retry_count( 0 )
				->set_error_type( null )
				->save();
		}
	}

	/**
	 * Creates a queue for bulk reoptimization and schedules the processor.
	 *
	 * @return void
	 * @throws Quota_Exceeded_Error|Invalid_Image_Exception|Async_Operation_Exception
	 */
	public static function create_reoptimization_queue(): void {
		$images = Bulk_Optimization_Image_Query::find_images(
			( new Image_Query_Builder() )
				->return_optimized_images()
		);

		if ( ! $images['total_images_count'] ) {
			return;
		}

		$token_data = Bulk_Optimization_Token_Manager::obtain_token( $images['total_images_count'] );
		$queue = new Bulk_Optimization_Queue( Bulk_Optimization_Queue_Type::REOPTIMIZATION );

		$queue
			->set_bulk_token(
				$token_data['token'],
				time() + HOUR_IN_SECONDS,
				$token_data['batch_size']
			)
			->set_status( Bulk_Optimization_Queue_Status::PROCESSING )
			->add_images( $images['attachments_in_quota'] )
			->save();

		Async_Operation::create(
			Async_Operation_Hook::REOPTIMIZE_BULK,
			[ 'operation_id' => $queue->get_operation_id() ],
			Async_Operation_Queue::OPTIMIZE
		);

		foreach ( $images['attachments_in_quota'] as $image_id ) {
			( new Image_Meta( $image_id ) )
				->set_status( Image_Status::REOPTIMIZING_IN_PROGRESS )
				->set_retry_count( 0 )
				->set_error_type( null )
				->save();
		}

		// Handle images out of quota
		foreach ( $images['attachments_out_of_quota'] as $image_id ) {
			( new Image_Meta( $image_id ) )
				->set_status( Image_Status::REOPTIMIZING_FAILED )
				->set_error_type( Image_Optimization_Error_Type::QUOTA_EXCEEDED )
				->save();
		}
	}

	/**
	 * Checks if there is a bulk optimization operation in progress.
	 * If there is at least a single active bulk optimization operation it returns true, otherwise false.
	 *
	 * @return bool
	 */
	public static function is_optimization_in_progress(): bool {
		$queue = new Bulk_Optimization_Queue( Bulk_Optimization_Queue_Type::OPTIMIZATION );

		return $queue->exists() && Bulk_Optimization_Queue_Status::PROCESSING === $queue->get_status();
	}

	/**
	 * Checks if there is a bulk re-optimization operation in progress.
	 * If there is at least a single active bulk re-optimization operation it returns true, otherwise false.
	 *
	 * @return bool
	 */
	public static function is_reoptimization_in_progress(): bool {
		$queue = new Bulk_Optimization_Queue( Bulk_Optimization_Queue_Type::REOPTIMIZATION );

		return $queue->exists() && Bulk_Optimization_Queue_Status::PROCESSING === $queue->get_status();
	}

	/**
	 * Retrieves the bulk optimization process status.
	 *
	 * @return array{status: string, stats: array}
	 */
	public static function get_status(): array {
		$stats = Optimization_Stats::get_image_stats();

		$percentage = 0;
		if ( $stats['total_image_count'] > 0 ) {
			$percentage = round( $stats['optimized_image_count'] / $stats['total_image_count'] * 100 );
		}

		$output = [
			'status' => 'not-started',
			'percentage' => $percentage,
		];

		$queue = new Bulk_Optimization_Queue( Bulk_Optimization_Queue_Type::OPTIMIZATION );

		if ( ! $queue->exists() ) {
			return $output;
		}

		$output['status'] = 'in-progress';

		return $output;
	}

	/**
	 * Returns latest operations for the bulk optimization screen.
	 *
	 * @return array
	 */
	public static function get_processed_images(): array {
		$output = [];
		$queue = new Bulk_Optimization_Queue( Bulk_Optimization_Queue_Type::OPTIMIZATION );

		if ( ! $queue->exists() ) {
			return $output;
		}

		$images = $queue->get_images();
		$images = array_slice( $images, 0, 50 );

		foreach ( $images as $queue_image ) {
			$image_id = $queue_image['id'];

			try {
				$image = new Image( $image_id );
				$stats = Optimization_Stats::get_image_stats( $image_id );
			} catch ( Throwable $t ) {
				continue;
			}

			$meta = new Image_Meta( $image_id );

			$output[] = [
				'id' => $image_id,
				'status' => $meta->get_status(),
				'image_name' => $image->get_attachment_object()->post_title,
				'image_id' => $image_id,
				'thumbnail_url' => $image->get_url( 'thumbnail' ),
				'original_file_size' => $stats['initial_image_size'],
				'current_file_size' => $stats['current_image_size'],
			];
		}

		return $output;
	}
}