Файловый менеджер - Редактировать - /var/www/xthruster/html/wp-content/uploads/flags/optimization.tar
Назад
classes/route-base.php 0000644 00000001574 14721622656 0011002 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes; use ImageOptimization\Classes\Route; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Route_Base extends Route { protected $auth = true; protected string $path = ''; public function get_methods(): array { return []; } public function get_endpoint(): string { return 'optimize/' . $this->get_path(); } public function get_path(): string { return $this->path; } public function get_name(): string { return ''; } public function post_permission_callback( WP_REST_Request $request ): bool { return $this->get_permission_callback( $request ); } public function get_permission_callback( WP_REST_Request $request ): bool { $valid = $this->permission_callback( $request ); return $valid && user_can( $this->current_user_id, 'manage_options' ); } } classes/optimize-image.php 0000644 00000030626 14721622656 0011654 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes; use ImageOptimization\Classes\File_System\{ Exceptions\File_System_Operation_Error, File_System, }; use ImageOptimization\Classes\File_Utils; use ImageOptimization\Classes\Image\{ Exceptions\Image_Backup_Creation_Error, Exceptions\Invalid_Image_Exception, Image, Image_Backup, Image_Conversion, Image_DB_Update, Image_Dimensions, Image_Meta, Image_Status, WP_Image_Meta }; use ImageOptimization\Classes\Logger; use ImageOptimization\Classes\Utils; use ImageOptimization\Classes\Exceptions\Quota_Exceeded_Error; use ImageOptimization\Modules\Optimization\Classes\{ Exceptions\Image_File_Already_Exists_Error, Exceptions\Image_Optimization_Error, Exceptions\Bulk_Token_Expired_Error, Exceptions\Image_Already_Optimized_Error, }; use ImageOptimization\Modules\Settings\Classes\Settings; use Throwable; use ImageOptimization\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * The class is responsible for the optimization logic itself. It gets an image file, runs * backup process if needed, sends a file to the service, stores the result and updates image meta. * * This class is used by manual, bulk and on-upload optimization flows. */ class Optimize_Image { private const IMAGE_OPTIMIZE_ENDPOINT = 'image/optimize'; protected ?Image $image; protected WP_Image_Meta $wp_meta; protected string $initiator; protected ?string $bulk_token; private string $current_image_path; private string $current_image_size; private bool $keep_backups; private array $current_size_duplicates; protected Image_Conversion $image_conversion; private bool $is_reoptimize; /** * @throws Quota_Exceeded_Error|Bulk_Token_Expired_Error|Image_File_Already_Exists_Error|Image_Optimization_Error */ public function optimize(): void { $sizes_enabled = Settings::get( Settings::CUSTOM_SIZES_OPTION_NAME ); $sizes_exist = $this->wp_meta->get_size_keys(); Logger::log( Logger::LEVEL_INFO, "Start optimization of {$this->image->get_id()}" ); foreach ( $sizes_exist as $size_exist ) { // If some image sizes optimization is disabled in settings, we check if the current one is still enabled if ( 'all' !== $sizes_enabled && Image::SIZE_FULL !== $size_exist && ! in_array( $size_exist, $sizes_enabled, true ) ) { continue; } // Elementor editor generates thumbnails we don't need to optimize. if ( str_starts_with( $size_exist, 'elementor_' ) ) { continue; } $image_meta = new Image_Meta( $this->image->get_id() ); // If the current size was already optimized -- ignore it. if ( ! $this->is_reoptimize && in_array( $size_exist, $image_meta->get_optimized_sizes(), true ) ) { Logger::log( Logger::LEVEL_INFO, "Size `$size_exist` is already optimized" ); continue; } if ( ! file_exists( $this->image->get_file_path( $size_exist ) ) ) { Logger::log( Logger::LEVEL_ERROR, "Can't access file for size `$size_exist`" ); throw new Image_Optimization_Error( esc_html__( 'File is missing. Verify the upload', 'image-optimization' ) ); } $this->current_image_size = $size_exist; $this->current_size_duplicates = $this->wp_meta->get_size_duplicates( $size_exist ); $this->current_image_path = $this->image->get_file_path( $size_exist ); $this->optimize_current_size(); $this->current_image_size = ''; $this->current_size_duplicates = []; $this->current_image_path = ''; } $this->mark_as_optimized(); if ( ! $this->keep_backups ) { Image_Backup::remove( $this->image->get_id() ); } Logger::log( Logger::LEVEL_INFO, "End optimization of {$this->image->get_id()}" ); } private function optimize_current_size(): void { try { $original_path = $this->current_image_path; $response = $this->send_file(); $optimized_size = (int) $response->optimizedSize; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase $optimized_image_binary = base64_decode( $response->image, true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode $this->replace_image_file( $optimized_image_binary ); $this->update_attachment_meta( $optimized_size ); if ( $original_path !== $this->current_image_path ) { $this->update_posts( $original_path, $this->current_image_path ); } // This should only be updated after meta $this->update_attachment_post(); } catch ( Image_Already_Optimized_Error $iao ) { // If we can't optimize it further, just update the meta $original_size = $this->wp_meta->get_file_size( $this->current_image_size ) ?? File_System::size( $this->image->get_file_path( $this->current_image_size ) ); $this->update_attachment_meta( $original_size, true ); $this->update_attachment_post( true ); } catch ( Bulk_Token_Expired_Error | Quota_Exceeded_Error | Image_File_Already_Exists_Error $e ) { throw $e; } catch ( Throwable $t ) { // In case of anything else throw new Image_Optimization_Error( $t->getMessage() ); } } private function send_file() { $connect_status = Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->get_connect_status(); $headers = [ 'access_token' => $connect_status->access_token ?? '', ]; if ( $this->bulk_token ) { $headers['x-elementor-bulk-token'] = $this->bulk_token; } $optimization_options = [ 'compression_level' => Settings::get( Settings::COMPRESSION_LEVEL_OPTION_NAME ), 'convert_to_format' => $this->image_conversion->get_current_conversion_option(), 'strip_exif' => Settings::get( Settings::STRIP_EXIF_METADATA_OPTION_NAME ), ]; if ( Settings::get( Settings::RESIZE_LARGER_IMAGES_OPTION_NAME ) ) { $optimization_options['resize'] = Settings::get( Settings::RESIZE_LARGER_IMAGES_SIZE_OPTION_NAME ); } $image_key = wp_generate_password( 15, false ); $response = Utils::get_api_client()->make_request( 'POST', self::IMAGE_OPTIMIZE_ENDPOINT, [ 'initiator' => $this->initiator, 'image_url' => $this->image->get_url( $this->current_image_size ), 'image_key' => $image_key, 'checksum' => md5_file( $this->current_image_path ), 'attachment_id' => $this->image->get_id(), 'attachment_parent_id' => Image::SIZE_FULL === $this->current_image_size ? 0 : $this->image->get_id(), 'image_optimization_settings' => base64_encode( wp_json_encode( $optimization_options ) ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode ], $headers, $this->current_image_path, 'image' ); if ( isset( $response->stats ) ) { Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->update_usage_data( $response->stats ); } if ( ! isset( $response->imageKey ) || $image_key !== $response->imageKey ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase Logger::log( Logger::LEVEL_ERROR, "Image key must be $image_key, instead got $response->imageKey" ); throw new Image_Optimization_Error( esc_html__( 'Service response is incorrect', 'image-optimization' ) ); } $received_file_hash = md5( base64_decode( $response->image, true ) ); if ( ! isset( $response->checksum ) || $received_file_hash !== $response->checksum ) { Logger::log( Logger::LEVEL_ERROR, "Image key must be $response->checksum, instead calculated $received_file_hash" ); throw new Image_Optimization_Error( esc_html__( 'Service response is incorrect', 'image-optimization' ) ); } return $response; } /** * @throws File_System_Operation_Error|Image_Backup_Creation_Error|Image_File_Already_Exists_Error */ private function replace_image_file( string $file_data ): void { $path = $this->current_image_path; // If we have backups disabled, we want to make sure we can download and save new file before we // remove an existing one. $tmp_path = File_Utils::replace_extension( $path, 'tmp' ); File_System::put_contents( $tmp_path, $file_data ); if ( $this->image_conversion->is_enabled() ) { $converted_path = File_Utils::replace_extension( $tmp_path, $this->image_conversion->get_current_file_extension() ); $original_is_already_converted = strtolower( $path ) === strtolower( $converted_path ); if ( ! $original_is_already_converted && File_System::exists( $converted_path ) ) { throw new Image_File_Already_Exists_Error(); } // We want to keep both original as a fallback and optimized webp to prevent 404s if ( ! $original_is_already_converted ) { $this->keep_backups = true; ( new Image_Meta( $this->image->get_id() ) ) ->set_image_backup_path( $this->current_image_size, $this->current_image_path ) ->save(); } if ( $original_is_already_converted ) { Image_Backup::create( $this->image->get_id(), $this->current_image_size, $this->current_image_path ); } File_System::move( $tmp_path, $converted_path, true ); $path = $converted_path; } else { Image_Backup::create( $this->image->get_id(), $this->current_image_size, $this->current_image_path ); File_System::move( $tmp_path, $path, true ); } if ( Image::SIZE_FULL === $this->current_image_size ) { // Drop WP caches update_attached_file( $this->image->get_id(), $path ); } // Updating to the correct value $this->current_image_path = $path; } /** * Updates attachment records in the `wp_posts` table. * * @return void */ private function update_attachment_post( ?bool $is_fully_optimized = false ) { $update_query = []; if ( $this->image_conversion->is_enabled() && ! $is_fully_optimized ) { $attachment_object = $this->image->get_attachment_object(); $update_query['guid'] = File_Utils::replace_extension( $attachment_object->guid, $this->image_conversion->get_current_file_extension() ); $update_query['post_mime_type'] = $this->image_conversion->get_current_mime_type(); } $update_query['post_modified'] = current_time( 'mysql' ); $update_query['post_modified_gmt'] = current_time( 'mysql', true ); $this->image->update_attachment( $update_query ); } /** * Updates attachment records in the `wp_postmeta` table. * * @param int $optimized_size * @param bool|null $is_fully_optimized * * @return void */ private function update_attachment_meta( int $optimized_size, ?bool $is_fully_optimized = false ) { $meta = new Image_Meta( $this->image->get_id() ); $dimensions = Image_Dimensions::get_by_path( $this->current_image_path ); $sizes_to_update = [ $this->current_image_size, ...$this->current_size_duplicates ]; foreach ( $sizes_to_update as $size ) { $meta ->set_compression_level( Settings::get( Settings::COMPRESSION_LEVEL_OPTION_NAME ) ) ->add_optimized_size( $size ) ->add_original_data( $size, $this->wp_meta->get_size_data( $size ) ); $this->wp_meta ->set_width( $size, $dimensions->width ) ->set_height( $size, $dimensions->height ) ->set_file_size( $size, $optimized_size ); if ( $this->image_conversion->is_enabled() && ! $is_fully_optimized ) { $this->wp_meta ->set_file_path( $size, $this->current_image_path ) ->set_mime_type( $size, $this->image_conversion->get_current_mime_type() ); } } $meta->save(); $this->wp_meta->save(); } /** * If we change an image extension, we should walk through the wp_posts table and update all the * hardcoded image links to prevent 404s. * * @param string $old_path Previous image path * @param string $new_path Current image path * * @return void */ private function update_posts( string $old_path, string $new_path ) { Image_DB_Update::update_posts_table_urls( File_Utils::get_url_from_path( $old_path ), File_Utils::get_url_from_path( $new_path ) ); } /** * Changes image status after all image sizes were optimized. * * @return void */ private function mark_as_optimized() { ( new Image_Meta( $this->image->get_id() ) ) ->set_status( Image_Status::OPTIMIZED ) ->set_error_type( null ) ->save(); } /** * @throws Invalid_Image_Exception */ public function __construct( int $image_id, string $initiator, ?string $bulk_token = null, ?bool $is_reoptimize = false ) { $this->image = new Image( $image_id ); $this->wp_meta = new WP_Image_Meta( $image_id, $this->image ); $this->initiator = $initiator; $this->bulk_token = $bulk_token; $this->is_reoptimize = $is_reoptimize; $this->image_conversion = new Image_Conversion(); $this->keep_backups = Settings::get( Settings::BACKUP_ORIGINAL_IMAGES_OPTION_NAME ); } } classes/optimization-status.php 0000644 00000003470 14721622656 0013000 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes; use ImageOptimization\Classes\Image\{ Image_Meta, Image_Optimization_Error_Type, Image_Status }; use ImageOptimization\Modules\Oauth\Classes\Data; use ImageOptimization\Modules\Stats\Classes\Optimization_Stats; use ImageOptimization\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Optimization_Status { /** * * * @param int[] $image_ids * @return array */ public static function get_images_optimization_statuses( array $image_ids ): array { $output = []; $images_left = Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->images_left(); foreach ( $image_ids as $image_id ) { $meta = new Image_Meta( $image_id ); $data = []; $status = $meta->get_status(); $data['status'] = $status; if ( Image_Status::OPTIMIZED === $status ) { $data['stats'] = Optimization_Stats::get_image_stats( $image_id ); } if ( Image_Status::RESTORING_FAILED === $status ) { $data['message'] = esc_html__( 'Image restoring failed', 'image-optimization' ); } if ( Image_Status::OPTIMIZATION_FAILED === $status ) { $error_type = $meta->get_error_type() ?? Image_Optimization_Error_Type::GENERIC; $error_message = Optimization_Error_Message::get_optimization_error_message( $error_type ); $data['message'] = $error_message; $data['images_left'] = $images_left; } if ( Image_Status::REOPTIMIZING_FAILED === $status ) { $error_type = $meta->get_error_type() ?? Image_Optimization_Error_Type::GENERIC; $error_message = Optimization_Error_Message::get_reoptimization_error_message( $error_type ); $data['message'] = $error_message; $data['images_left'] = $images_left; } $output[ $image_id ] = $data; } return $output; } } classes/exceptions/image-already-optimized-error.php 0000644 00000000571 14721622656 0016743 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * The service responses with the error when the optimized image size >= than the original size. */ class Image_Already_Optimized_Error extends Exception { protected $message = 'Image already optimized'; } classes/exceptions/bulk-token-obtaining-error.php 0000644 00000000420 14721622656 0016254 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Bulk_Token_Obtaining_Error extends Exception { protected $message = 'Bulk token obtaining error'; } classes/exceptions/image-optimization-error.php 0000644 00000000414 14721622656 0016042 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Optimization_Error extends Exception { protected $message = 'Image optimization error'; } classes/exceptions/image-validation-error.php 0000644 00000000410 14721622656 0015442 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Validation_Error extends Exception { protected $message = 'Image validation error'; } classes/exceptions/image-optimization-already-in-progress-error.php 0000644 00000000464 14721622656 0021734 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Optimization_Already_In_Progress_Error extends Exception { protected $message = 'Image optimization already in progress error'; } classes/exceptions/bulk-token-expired-error.php 0000644 00000000414 14721622656 0015745 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Bulk_Token_Expired_Error extends Exception { protected $message = 'Bulk token expired error'; } classes/exceptions/image-file-already-exists-error.php 0000644 00000000443 14721622656 0017171 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_File_Already_Exists_Error extends Exception { protected $message = 'Image file with this name already exists'; } classes/exceptions/image-optimization-retryable-error.php 0000644 00000000427 14721622656 0020035 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Optimization_Retryable_Error extends Exception { protected $message = 'Image optimization error'; } classes/bulk-optimization-controller.php 0000644 00000031337 14721622656 0014576 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes; 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, WP_Image_Meta }; use ImageOptimization\Classes\File_System\Exceptions\File_System_Operation_Error; use ImageOptimization\Classes\File_System\File_System; use ImageOptimization\Classes\Logger; use ImageOptimization\Classes\Utils; use ImageOptimization\Modules\Oauth\Classes\Data; use ImageOptimization\Classes\Exceptions\Quota_Exceeded_Error; use ImageOptimization\Modules\Optimization\Classes\Exceptions\Bulk_Token_Obtaining_Error; use ImageOptimization\Modules\Optimization\Components\Exceptions\Bulk_Optimization_Token_Not_Found_Error; use ImageOptimization\Modules\Stats\Classes\Optimization_Stats; use ImageOptimization\Plugin; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Bulk_Optimization_Controller { private const OBTAIN_TOKEN_ENDPOINT = 'image/bulk-token'; public static function reschedule_bulk_optimization() { self::delete_bulk_optimization(); self::find_images_and_schedule_optimization(); } public static function reschedule_bulk_reoptimization() { self::delete_bulk_optimization(); self::find_optimized_images_and_schedule_reoptimization(); } /** * Cancels pending bulk optimization operations. * * @return void * @throws Async_Operation_Exception */ public static function delete_bulk_optimization(): void { $query = ( new Image_Optimization_Operation_Query() ) ->set_hook( Async_Operation_Hook::OPTIMIZE_BULK ) // It's risky to cancel in-progress operations at that point, so we cancel only the pending ones. ->set_status( Async_Operation::OPERATION_STATUS_PENDING ) ->set_limit( -1 ); $operations = Async_Operation::get( $query ); foreach ( $operations as $operation ) { $image_id = $operation->get_args()['attachment_id']; Async_Operation::remove( [ $operation->get_id() ] ); ( new Image_Meta( $image_id ) )->delete(); } } /** * Cancels pending bulk re-optimization operations. * * @return void * @throws Async_Operation_Exception */ public static function cancel_bulk_reoptimization(): void { $query = ( new Image_Optimization_Operation_Query() ) ->set_hook( Async_Operation_Hook::REOPTIMIZE_BULK ) // It's risky to cancel in-progress operations at that point, so we cancel only the pending ones. ->set_status( Async_Operation::OPERATION_STATUS_PENDING ) ->set_limit( -1 ); $operations = Async_Operation::get( $query ); foreach ( $operations as $operation ) { $image_id = $operation->get_args()['attachment_id']; Async_Operation::cancel( $operation->get_id() ); ( new Image_Meta( $image_id ) )->delete(); } } /** * Looks for all non-optimized images and creates a bulk operation for each of them. * Also, obtains bulk token and passes it to a newly created operation. * * @return void * * @throws Quota_Exceeded_Error|Invalid_Image_Exception */ public static function find_images_and_schedule_optimization(): void { $images = self::find_images( ( new Image_Query_Builder() ) ->return_not_optimized_images(), true ); if ( ! $images['total_images_count'] ) { return; } $operation_id = wp_generate_password( 10, false ); try { $bulk_token = self::obtain_bulk_token( $images['total_images_count'] ); self::set_bulk_operation_token( $operation_id, $bulk_token ); } catch ( Bulk_Token_Obtaining_Error $e ) { $bulk_token = null; } foreach ( $images['attachments_in_quota'] as $attachment_id ) { $meta = new Image_Meta( $attachment_id ); if ( null === $bulk_token ) { $meta ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->save(); continue; } try { Async_Operation::create( Async_Operation_Hook::OPTIMIZE_BULK, [ 'attachment_id' => $attachment_id, 'operation_id' => $operation_id, ], Async_Operation_Queue::OPTIMIZE ); $meta ->set_status( Image_Status::OPTIMIZATION_IN_PROGRESS ) ->save(); } catch ( Async_Operation_Exception $aoe ) { $meta ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->save(); continue; } } } /** * Looks for already optimized images with backups and creates a bulk operation for each of them. * Also, obtains bulk token and passes it to a newly created operation. * * @return void * * @throws Quota_Exceeded_Error|Invalid_Image_Exception */ public static function find_optimized_images_and_schedule_reoptimization(): void { $images = self::find_images( ( new Image_Query_Builder() ) ->return_optimized_images() ); if ( ! $images['total_images_count'] ) { return; } $operation_id = wp_generate_password( 10, false ); try { $bulk_token = self::obtain_bulk_token( $images['total_images_count'] ); self::set_bulk_operation_token( $operation_id, $bulk_token ); } catch ( Bulk_Token_Obtaining_Error $e ) { $bulk_token = null; } foreach ( $images['attachments_in_quota'] as $attachment_id ) { $meta = new Image_Meta( $attachment_id ); if ( null === $bulk_token ) { $meta ->set_status( Image_Status::REOPTIMIZING_FAILED ) ->save(); continue; } try { Async_Operation::create( Async_Operation_Hook::REOPTIMIZE_BULK, [ 'attachment_id' => $attachment_id, 'operation_id' => $operation_id, ], Async_Operation_Queue::OPTIMIZE ); $meta ->set_status( Image_Status::REOPTIMIZING_IN_PROGRESS ) ->save(); } catch ( Async_Operation_Exception $aoe ) { $meta ->set_status( Image_Status::REOPTIMIZING_FAILED ) ->save(); continue; } } foreach ( $images['attachments_out_of_quota'] as $attachment_id ) { ( new Image_Meta( $attachment_id ) ) ->set_status( Image_Status::REOPTIMIZING_FAILED ) ->set_error_type( Image_Optimization_Error_Type::QUOTA_EXCEEDED ) ->save(); } } /** * Looks for images for bulk optimization operations based on a query passed and the quota left. * * @param Image_Query_Builder $query Image query to execute. * @param bool $limit_to_quota If true, it limits image query to the quota left. * @return array{total_images_count: int, attachments_in_quota: array, attachments_out_of_quota: array} * * @throws Invalid_Image_Exception * @throws Quota_Exceeded_Error */ private static function find_images( Image_Query_Builder $query, bool $limit_to_quota = false ): array { $output = [ 'total_images_count' => 0, 'attachments_in_quota' => [], 'attachments_out_of_quota' => [], ]; $images_left = Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->images_left(); if ( ! $images_left ) { throw new Quota_Exceeded_Error( __( 'Images quota exceeded', 'image-optimization' ) ); } if ( $limit_to_quota ) { $query->set_paging_size( $images_left ); } $wp_query = $query->execute(); if ( ! $wp_query->post_count ) { return $output; } foreach ( $wp_query->posts as $attachment_id ) { try { Validate_Image::is_valid( $attachment_id ); $wp_meta = new WP_Image_Meta( $attachment_id ); } catch ( Invalid_Image_Exception | Exceptions\Image_Validation_Error $ie ) { continue; } $sizes_count = count( $wp_meta->get_size_keys() ); if ( $output['total_images_count'] + $sizes_count <= $images_left ) { $output['total_images_count'] += $sizes_count; $output['attachments_in_quota'][] = $attachment_id; } else { break; } } $output['attachments_out_of_quota'] = array_diff( $wp_query->posts, $output['attachments_in_quota'] ); return $output; } /** * Looks for the bulk token in transients. * * @param string $operation_id Bulk optimization operation id * * @return string|null Bulk token. * * @throws Bulk_Optimization_Token_Not_Found_Error */ public static function get_bulk_operation_token( string $operation_id ): ?string { $bulk_token = get_transient( "image_optimizer_bulk_token_$operation_id" ); if ( ! $bulk_token ) { throw new Bulk_Optimization_Token_Not_Found_Error( "There is no token found for the operation $operation_id" ); } return $bulk_token; } /** * Saves bulk optimization token to transients for a day. * * @param string $operation_id Bulk optimization operation id * @param string $bulk_token Bulk optimization token * @return void */ public static function set_bulk_operation_token( string $operation_id, string $bulk_token ): void { set_transient( "image_optimizer_bulk_token_$operation_id", $bulk_token, HOUR_IN_SECONDS ); } /** * Sends a request to the BE to obtain bulk optimization token. * It prevents obtaining a token for each and every optimization operation. * * @return string * * @throws Bulk_Token_Obtaining_Error */ private static function obtain_bulk_token( int $images_count ): ?string { try { $response = Utils::get_api_client()->make_request( 'POST', self::OBTAIN_TOKEN_ENDPOINT, [ 'images_count' => $images_count, ] ); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Error while sending bulk token request: ' . $t->getMessage() ); throw new Bulk_Token_Obtaining_Error( $t->getMessage() ); } return $response->token ?? null; } /** * 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 * @throws Async_Operation_Exception */ public static function is_optimization_in_progress(): bool { $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 ) ->return_ids(); return ! empty( Async_Operation::get( $query ) ); } /** * 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 * @throws Async_Operation_Exception */ public static function is_reoptimization_in_progress(): bool { $query = ( new Image_Optimization_Operation_Query() ) ->set_hook( Async_Operation_Hook::REOPTIMIZE_BULK ) ->set_status( [ Async_Operation::OPERATION_STATUS_PENDING, Async_Operation::OPERATION_STATUS_RUNNING ] ) ->set_limit( 1 ) ->return_ids(); return ! empty( Async_Operation::get( $query ) ); } /** * Retrieves the bulk optimization process status. * * @return array{status: string, stats: array} * @throws Async_Operation_Exception */ public static function get_status(): array { $stats = Optimization_Stats::get_image_stats(); $output = [ 'status' => 'not-started', 'percentage' => round( $stats['optimized_image_count'] / $stats['total_image_count'] * 100 ), ]; $active_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 ); if ( empty( Async_Operation::get( $active_query ) ) ) { return $output; } $output['status'] = 'in-progress'; return $output; } /** * Returns latest operations for the bulk optimization screen. * * @param string|null $operation_id * * @return array * @throws Async_Operation_Exception */ public static function get_processed_images( string $operation_id ): array { $output = []; $query = ( new Image_Optimization_Operation_Query() ) ->set_hook( Async_Operation_Hook::OPTIMIZE_BULK ) ->set_bulk_operation_id( $operation_id ) ->set_limit( 50 ); $operations = Async_Operation::get( $query ); foreach ( $operations as $operation ) { $image_id = $operation->get_args()['attachment_id']; $image = new Image( $image_id ); try { $stats = Optimization_Stats::get_image_stats( $image_id ); } catch ( Invalid_Image_Exception $iie ) { continue; } catch ( Throwable $t ) { $original_file_size = 0; $current_file_size = 0; } $output[] = [ 'id' => $operation->get_id(), 'status' => $operation->get_status() === Async_Operation::OPERATION_STATUS_COMPLETE ? ( new Image_Meta( $image_id ) )->get_status() : $operation->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; } } classes/single-optimization.php 0000644 00000004556 14721622656 0012744 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes; 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\{ Image_Meta, Image_Status, }; use ImageOptimization\Modules\Optimization\Classes\Exceptions\Image_Optimization_Already_In_Progress_Error; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Single_Optimization { /** * @throws Throwable|Async_Operation_Exception */ public static function optimize_many( array $image_ids, bool $is_reoptimize = false ): void { foreach ( $image_ids as $image_id ) { try { self::schedule_single_optimization( $image_id, $is_reoptimize ); } catch ( Image_Optimization_Already_In_Progress_Error $ioe ) { continue; } catch ( Throwable $t ) { throw $t; } } } /** * @throws Throwable|Async_Operation_Exception|Image_Optimization_Already_In_Progress_Error */ public static function schedule_single_optimization( int $image_id, bool $is_reoptimize = false ): void { if ( self::is_optimization_in_progress( $image_id ) ) { throw new Image_Optimization_Already_In_Progress_Error( esc_html__( 'Optimization is already in progress', 'image-optimization' ) ); } $meta = new Image_Meta( $image_id ); try { $meta ->set_status( $is_reoptimize ? Image_Status::REOPTIMIZING_IN_PROGRESS : Image_Status::OPTIMIZATION_IN_PROGRESS ) ->save(); Async_Operation::create( $is_reoptimize ? Async_Operation_Hook::REOPTIMIZE_SINGLE : Async_Operation_Hook::OPTIMIZE_SINGLE, [ 'attachment_id' => $image_id ], Async_Operation_Queue::OPTIMIZE ); } catch ( Throwable $t ) { $meta ->set_status( $is_reoptimize ? Image_Status::REOPTIMIZING_FAILED : Image_Status::OPTIMIZATION_FAILED ) ->save(); throw $t; } } /** * @throws Async_Operation_Exception */ private static function is_optimization_in_progress( int $image_id ): bool { $query = ( new Image_Optimization_Operation_Query() ) ->set_status( [ Async_Operation::OPERATION_STATUS_PENDING, Async_Operation::OPERATION_STATUS_RUNNING ] ) ->set_image_id( $image_id ) ->return_ids(); $operations = Async_Operation::get( $query ); return count( $operations ) > 0; } } classes/validate-image.php 0000644 00000007761 14721622656 0011611 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes; use ImageOptimization\Classes\Image\{ Exceptions\Invalid_Image_Exception, Image, Image_Meta, WP_Image_Meta, }; use ImageOptimization\Classes\File_System\Exceptions\File_System_Operation_Error; use ImageOptimization\Classes\File_System\File_System; use ImageOptimization\Classes\File_Utils; use ImageOptimization\Modules\Optimization\Classes\Exceptions\Image_Validation_Error; use ImageOptimization\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Validate_Image { public const MAX_FILE_SIZE = 10 * 1024 * 1024; public const MAX_BIG_FILE_SIZE = 25 * 1024 * 1024; /** * Returns true if $image_id provided associated with an image that can be optimized. * * @param int $image_id Attachment id. * * @return true * @throws Image_Validation_Error * @throws Invalid_Image_Exception */ public static function is_valid( int $image_id ): bool { $attachment_object = get_post( $image_id ); if ( ! $attachment_object ) { throw new Image_Validation_Error( __( 'Can\'t optimize this file. If the issue persists, Contact Support', 'image-optimization' ) ); } if ( ! wp_attachment_is_image( $attachment_object ) || ! in_array( $attachment_object->post_mime_type, Image::get_supported_mime_types(), true ) || ( in_array( $attachment_object->post_mime_type, Image::get_mime_types_cannot_be_optimized(), true ) && ! get_post_meta( $image_id, Image_Meta::IMAGE_OPTIMIZER_METADATA_KEY, true ) ) ) { throw new Image_Validation_Error( self::prepare_supported_formats_list_error() ); } if ( ! file_exists( get_attached_file( $image_id ) ) ) { throw new Image_Validation_Error( esc_html__( 'File is missing. Verify the upload', 'image-optimization' ) ); } $wp_meta = new WP_Image_Meta( $image_id ); try { $image_size = $wp_meta->get_file_size( Image::SIZE_FULL ) ?? File_System::size( ( new Image( $image_id ) )->get_file_path( Image::SIZE_FULL ) ); } catch ( File_System_Operation_Error $e ) { throw new Image_Validation_Error( esc_html__( 'File is missing. Verify the upload', 'image-optimization' ) ); } if ( $image_size > self::MAX_FILE_SIZE ) { if ( ! self::are_big_files_supported() ) { throw new Image_Validation_Error( sprintf( __( 'File is too large. Max size is %s', 'image-optimization' ), File_Utils::format_file_size( self::MAX_FILE_SIZE, 0 ), ) ); } if ( $image_size > self::MAX_BIG_FILE_SIZE ) { throw new Image_Validation_Error( sprintf( __( 'File is too large. Max size is %s', 'image-optimization' ), File_Utils::format_file_size( self::MAX_BIG_FILE_SIZE, 0 ), ) ); } } return true; } /** * Prepares the error message for the unsupported file formats. * * @return string The error message. */ private static function prepare_supported_formats_list_error(): string { $formats = Image::get_supported_formats(); $formats = array_filter( $formats, fn ( $format ) => ! in_array( $format, Image::get_formats_cannot_be_optimized(), true ) ); $last_item = strtoupper( array_pop( $formats ) ); $formats_list = join( ', ', array_map( 'strtoupper', $formats ) ); return sprintf( __( 'Unsupported file format. Only %1$s, or %2$s are supported', 'image-optimization' ), $formats_list, $last_item ); } public static function are_big_files_supported(): bool { static $is_allowed = null; $connect_manager = Plugin::instance()->modules_manager->get_modules( 'connect-manager' ); if ( null === $is_allowed ) { if ( ! $connect_manager->connect_instance->is_connected() ) { $is_allowed = false; return $is_allowed; } $plan_data = $connect_manager->connect_instance->get_connect_status(); if ( ! isset( $plan_data->subscription_plan->features->large_upload_allowed ) ) { $is_allowed = false; } else { $is_allowed = (bool) $plan_data->subscription_plan->features->large_upload_allowed; } } return $is_allowed; } } classes/optimization-error-message.php 0000644 00000002503 14721622656 0014224 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Classes; use ImageOptimization\Classes\Image\Image_Optimization_Error_Type; use TypeError; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Optimization_Error_Message { public static function get_reoptimization_error_message( string $error_type ) { if ( Image_Optimization_Error_Type::GENERIC === $error_type ) { return esc_html__( 'Image reoptimizing failed', 'image-optimization' ); } return self::get_optimization_error_message( $error_type ); } public static function get_optimization_error_message( string $error_type ) { if ( ! in_array( $error_type, Image_Optimization_Error_Type::get_values(), true ) ) { throw new TypeError( "Error type $error_type is not a part of Image_Optimization_Error_Type values" ); } $messages = [ Image_Optimization_Error_Type::FILE_ALREADY_EXISTS => esc_html__( 'File with this name already exists', 'image-optimization' ), Image_Optimization_Error_Type::QUOTA_EXCEEDED => esc_html__( 'Plan quota reached', 'image-optimization' ), Image_Optimization_Error_Type::GENERIC => esc_html__( 'Optimization error', 'image-optimization' ), ]; if ( isset( $messages[ $error_type ] ) ) { return $messages[ $error_type ]; } return $messages[ Image_Optimization_Error_Type::GENERIC ]; } } rest/optimize-single-image.php 0000644 00000003414 14721622656 0012446 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Rest; use ImageOptimization\Classes\Image\Image; use ImageOptimization\Modules\Oauth\Components\Connect; use ImageOptimization\Modules\Optimization\Classes\{ Route_Base, Single_Optimization, Validate_Image, }; use Throwable; use WP_REST_Request; use ImageOptimization\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Optimize_Single_Image extends Route_Base { const NONCE_NAME = 'image-optimization-optimize-image'; const IMAGE_ID_PARAM = 'imageId'; protected string $path = 'image'; public function get_name(): string { return 'optimize-single-image'; } public function get_methods(): array { return [ 'POST' ]; } public function POST( WP_REST_Request $request ) { $this->verify_nonce_and_capability( $request->get_param( self::NONCE_NAME ), self::NONCE_NAME ); if ( ! Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->is_activated() ) { return $this->respond_error_json([ 'message' => esc_html__( 'Invalid activation', 'image-optimization' ), 'code' => 'unauthorized', ]); } $is_reoptimize = (bool) $request->get_param( 'reoptimize' ); $image_id = (int) $request->get_param( self::IMAGE_ID_PARAM ); if ( empty( $image_id ) ) { return $this->respond_error_json([ 'message' => esc_html__( 'Invalid image id', 'image-optimization' ), 'code' => 'bad_request', ]); } try { Validate_Image::is_valid( $image_id ); Single_Optimization::schedule_single_optimization( $image_id, $is_reoptimize ); return $this->respond_success_json(); } catch ( Throwable $t ) { return $this->respond_error_json([ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ]); } } } rest/optimization-status.php 0000644 00000002271 14721622656 0012316 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Rest; use ImageOptimization\Modules\Optimization\Classes\Optimization_Status as Optimization_Status_Controller; use ImageOptimization\Modules\Optimization\Classes\Route_Base; use Throwable; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Optimization_Status extends Route_Base { protected string $path = 'status'; public function get_name(): string { return 'optimization-status'; } public function get_methods(): array { // There might be a huge amount of ids in some cases, so decided to keep it as POST return [ 'POST' ]; } public function POST( WP_REST_Request $request ) { try { $body = json_decode( $request->get_body() ); $image_ids = $body->image_ids ?? null; if ( empty( $image_ids ) ) { return $this->respond_success_json([ 'status' => [], ]); } return $this->respond_success_json([ 'status' => Optimization_Status_Controller::get_images_optimization_statuses( $image_ids ), ]); } catch ( Throwable $t ) { return $this->respond_error_json([ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ]); } } } rest/cancel-bulk-optimization.php 0000644 00000003164 14721622656 0013155 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Rest; use ImageOptimization\Modules\Optimization\Classes\{ Bulk_Optimization_Controller, Route_Base, }; use ImageOptimization\Modules\Oauth\Components\Connect; use Throwable; use WP_REST_Request; use ImageOptimization\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Cancel_Bulk_Optimization extends Route_Base { const NONCE_NAME = 'image-optimization-cancel-bulk-optimization'; protected string $path = 'bulk/cancel'; public function get_name(): string { return 'cancel-bulk-optimization'; } public function get_methods(): array { return [ 'POST' ]; } public function POST( WP_REST_Request $request ) { $this->verify_nonce_and_capability( $request->get_param( self::NONCE_NAME ), self::NONCE_NAME ); if ( ! Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->is_activated() ) { return $this->respond_error_json([ 'message' => esc_html__( 'Invalid activation', 'image-optimization' ), 'code' => 'unauthorized', ]); } try { $is_in_progress = Bulk_Optimization_Controller::is_optimization_in_progress(); if ( ! $is_in_progress ) { return $this->respond_error_json([ 'message' => esc_html__( 'Bulk optimization is not in progress', 'image-optimization' ), 'code' => 'forbidden', ]); } Bulk_Optimization_Controller::cancel_bulk_optimization(); return $this->respond_success_json(); } catch ( Throwable $t ) { return $this->respond_error_json([ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ]); } } } rest/optimize-bulk.php 0000644 00000005321 14721622656 0011041 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Rest; use ImageOptimization\Classes\Async_Operation\Exceptions\Async_Operation_Exception; use ImageOptimization\Modules\Optimization\Classes\{ Bulk_Optimization_Controller, Route_Base, }; use ImageOptimization\Classes\Image\Exceptions\Invalid_Image_Exception; use ImageOptimization\Classes\Exceptions\Quota_Exceeded_Error; use ImageOptimization\Modules\Oauth\Components\Connect; use Throwable; use WP_REST_Request; use ImageOptimization\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Optimize_Bulk extends Route_Base { const NONCE_NAME = 'image-optimization-optimize-bulk'; protected string $path = 'bulk'; public function get_name(): string { return 'optimize-bulk'; } public function get_methods(): array { return [ 'POST' ]; } public function POST( WP_REST_Request $request ) { $this->verify_nonce_and_capability( $request->get_param( self::NONCE_NAME ), self::NONCE_NAME ); if ( ! Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->is_activated() ) { return $this->respond_error_json([ 'message' => esc_html__( 'Invalid activation', 'image-optimization' ), 'code' => 'unauthorized', ]); } $is_reoptimize = (bool) $request->get_param( 'reoptimize' ); try { if ( $is_reoptimize ) { return $this->handle_bulk_reoptimization(); } else { return $this->handle_bulk_optimization(); } } catch ( Throwable $t ) { return $this->respond_error_json([ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ]); } } /** * @return \WP_Error|\WP_REST_Response * @throws Async_Operation_Exception|Invalid_Image_Exception|Quota_Exceeded_Error */ private function handle_bulk_optimization() { $is_in_progress = Bulk_Optimization_Controller::is_optimization_in_progress(); if ( $is_in_progress ) { return $this->respond_error_json( [ 'message' => esc_html__( 'Bulk optimization is already in progress', 'image-optimization' ), 'code' => 'forbidden', ] ); } Bulk_Optimization_Controller::find_images_and_schedule_optimization(); return $this->respond_success_json(); } /** * @throws Async_Operation_Exception|Quota_Exceeded_Error */ private function handle_bulk_reoptimization() { $is_in_progress = Bulk_Optimization_Controller::is_reoptimization_in_progress(); if ( $is_in_progress ) { return $this->respond_error_json( [ 'message' => esc_html__( 'Bulk re-optimization is already in progress', 'image-optimization' ), 'code' => 'forbidden', ] ); } Bulk_Optimization_Controller::find_optimized_images_and_schedule_reoptimization(); return $this->respond_success_json(); } } rest/get-bulk-optimization-images.php 0000644 00000002163 14721622656 0013750 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Rest; use ImageOptimization\Modules\Optimization\Classes\{ Bulk_Optimization_Controller, Route_Base, }; use Throwable; use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Get_Bulk_Optimization_Images extends Route_Base { protected string $path = 'bulk/images'; public function get_name(): string { return 'bulk-optimization-images'; } public function get_methods(): array { return [ 'GET' ]; } public function GET( WP_REST_Request $request ) { try { $operation_id = $request->get_param( 'operation_id' ); if ( empty( $operation_id ) ) { return $this->respond_error_json([ 'message' => esc_html__( 'Missed operation id', 'image-optimization' ), 'code' => 'bad_request', ]); } $images = Bulk_Optimization_Controller::get_processed_images( $operation_id ); return $this->respond_success_json([ 'images' => $images, ]); } catch ( Throwable $t ) { return $this->respond_error_json([ 'message' => $t->getMessage(), 'code' => 'internal_server_error', ]); } } } module.php 0000644 00000004563 14721622656 0006565 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization; use ImageOptimization\Classes\Module_Base; use ImageOptimization\Modules\Backups\Rest\Restore_Single; use ImageOptimization\Modules\Optimization\Rest\Optimize_Single_Image; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Module extends Module_Base { public function get_name(): string { return 'optimization'; } public static function routes_list() : array { return [ 'Optimize_Single_Image', 'Optimize_Bulk', 'Get_Bulk_Optimization_Images', 'Optimization_Status', 'Cancel_Bulk_Optimization', ]; } public static function component_list() : array { return [ 'Avif_Compatibility', 'Media_Control', 'Single_Optimization', 'Upload_Optimization', 'Bulk_Optimization', 'List_View_Pointer', 'Admin_Bulk_Actions', 'Admin_Filter', ]; } /** * Enqueue styles and scripts */ public function enqueue_scripts() { $asset_file = include IMAGE_OPTIMIZATION_ASSETS_PATH . 'build/control.asset.php'; foreach ( $asset_file['dependencies'] as $style ) { wp_enqueue_style( $style ); } wp_enqueue_style( 'image-optimization-control', $this->get_css_assets_url( 'control' ), [], IMAGE_OPTIMIZATION_VERSION, ); wp_enqueue_script( 'image-optimization-control', $this->get_js_assets_url( 'control' ), $asset_file['dependencies'], IMAGE_OPTIMIZATION_VERSION, true ); wp_set_script_translations( 'image-optimization-control', 'image-optimization' ); wp_localize_script( 'image-optimization-control', 'imageOptimizerControlSettings', [ 'optimizeSingleImageNonce' => wp_create_nonce( Optimize_Single_Image::NONCE_NAME ), 'restoreSingleImageNonce' => wp_create_nonce( Restore_Single::NONCE_NAME ), ] ); } public static function load_template( $path, $name, $args = [] ): bool { $templates_path = sprintf( '%s/templates/%s/%s.php', dirname( __FILE__ ), $path, $name ); try { load_template( $templates_path, false, $args ); } catch ( Throwable $t ) { return false; } return true; } /** * Module constructor. */ public function __construct() { $this->register_components(); $this->register_routes(); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); add_action( 'elementor/editor/after_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); } } components/avif-compatibility.php 0000644 00000004106 14721622656 0013252 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Components; use ImageOptimization\Classes\Image\Image_Dimensions; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Avif_Compatibility { public function is_avif_supported(): bool { return in_array( 'image/avif', get_allowed_mime_types(), true ); } public function add_to_supported_types( $mime_types ) { $mime_types['avif'] = 'image/avif'; return $mime_types; } public function add_to_supported_mime_types( $mime_types ) { $mime_types['image/avif'] = 'avif'; return $mime_types; } public function mark_as_displayable( $result, $path ) { if ( str_ends_with( $path, '.avif' ) ) { return true; } return $result; } public function fix_avif_images( $metadata, $attachment_id ) { if ( empty( $metadata ) ) { return $metadata; } $attachment = get_post( $attachment_id ); if ( ! $attachment || is_wp_error( $attachment ) || 'image/avif' !== $attachment->post_mime_type ) { return $metadata; } if ( ( ! empty( $metadata['width'] ) && ( 0 !== $metadata['width'] ) ) && ( ! empty( $metadata['height'] ) && 0 !== $metadata['height'] ) ) { return $metadata; } $file = get_attached_file( $attachment_id ); if ( ! $file ) { return $metadata; } $dimensions = Image_Dimensions::get_by_path( $file ); $metadata['width'] = $dimensions->width; $metadata['height'] = $dimensions->height; if ( empty( $metadata['file'] ) ) { $metadata['file'] = _wp_relative_upload_path( $file ); } if ( empty( $metadata['sizes'] ) ) { $metadata['sizes'] = []; } return $metadata; } public function __construct() { if ( $this->is_avif_supported() ) { return; } add_filter( 'upload_mimes', [ $this, 'add_to_supported_types' ] ); add_filter( 'mime_types', [ $this, 'add_to_supported_types' ] ); add_filter( 'getimagesize_mimes_to_exts', [ $this, 'add_to_supported_mime_types' ] ); add_filter( 'file_is_displayable_image', [ $this, 'mark_as_displayable' ], 10, 2 ); add_filter( 'wp_generate_attachment_metadata', [ $this, 'fix_avif_images' ], 1, 3 ); } } components/upload-optimization.php 0000644 00000006134 14721622656 0013471 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Components; use ImageOptimization\Classes\Async_Operation\{ Async_Operation, Async_Operation_Hook, Async_Operation_Queue, }; use ImageOptimization\Classes\Image\{ Exceptions\Invalid_Image_Exception, Image_Meta, Image_Optimization_Error_Type, Image_Status }; use ImageOptimization\Classes\Logger; use ImageOptimization\Classes\Exceptions\Quota_Exceeded_Error; use ImageOptimization\Modules\Optimization\Classes\Exceptions\Image_File_Already_Exists_Error; use ImageOptimization\Modules\Optimization\Classes\Exceptions\Image_Validation_Error; use ImageOptimization\Modules\Optimization\Classes\Optimize_Image; use ImageOptimization\Modules\Optimization\Classes\Validate_Image; use ImageOptimization\Modules\Settings\Classes\Settings; use Throwable; use ImageOptimization\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Upload_Optimization { public function handle_upload( array $metadata, int $attachment_id, string $context ) { if ( 'create' !== $context ) { return $metadata; } if ( ! Settings::get( Settings::OPTIMIZE_ON_UPLOAD_OPTION_NAME ) ) { return $metadata; } // @var ImageOptimizer/Modules/ConnectManager/Module $module = Plugin::instance()->modules_manager->get_modules( 'connect-manager' ); if ( ! $module->connect_instance->is_connected() || ! $module->connect_instance->is_activated() ) { return $metadata; } try { Validate_Image::is_valid( $attachment_id ); } catch ( Invalid_Image_Exception | Image_Validation_Error $iie ) { return $metadata; } $meta = new Image_Meta( $attachment_id ); try { $meta ->set_status( Image_Status::OPTIMIZATION_IN_PROGRESS ) ->save(); Async_Operation::create( Async_Operation_Hook::OPTIMIZE_ON_UPLOAD, [ 'attachment_id' => $attachment_id ], Async_Operation_Queue::OPTIMIZE ); } catch ( Throwable $t ) { $meta ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->save(); } return $metadata; } /** @async */ public function optimize_image_on_upload( int $image_id ) { try { $oi = new Optimize_Image( $image_id, 'upload', ); $oi->optimize(); } catch ( Quota_Exceeded_Error $qe ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->set_error_type( Image_Optimization_Error_Type::QUOTA_EXCEEDED ) ->save(); } catch ( Image_File_Already_Exists_Error $fe ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->set_error_type( Image_Optimization_Error_Type::FILE_ALREADY_EXISTS ) ->save(); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Optimization error. Reason: ' . $t->getMessage() ); ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->set_error_type( Image_Optimization_Error_Type::GENERIC ) ->save(); } } public function __construct() { add_action( 'wp_generate_attachment_metadata', [ $this, 'handle_upload' ], 10, 3 ); add_action( Async_Operation_Hook::OPTIMIZE_ON_UPLOAD, [ $this, 'optimize_image_on_upload' ] ); } } components/admin-filter.php 0000644 00000005414 14721622656 0012034 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Components; use ImageOptimization\Classes\Image\Image_Meta; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Admin_Filter { public function add_filter( string $post_type ) { if ( 'attachment' !== $post_type ) { return; } $options = [ '' => __( 'All Media Files', 'image-optimization' ), 'optimized' => __( 'Optimized', 'image-optimization' ), 'not-optimized' => __( 'Unoptimized', 'image-optimization' ), 'in-progress' => __( 'In progress', 'image-optimization' ), 'failed' => __( 'Errors', 'image-optimization' ), ]; $current_value = $this->get_current_filter(); ?> <label class="screen-reader-text" for="image-optimization-filter"> <?php esc_html_e( 'Filter by optimization status', 'image-optimization' ); ?> </label> <select class="image-optimization-filter" id="image-optimization-filter" name="image-optimization-filter"> <?php foreach ( $options as $value => $title ) { printf( '<option value="%s" %s>%s</option>', esc_attr( $value ), selected( $value, $current_value, false ), esc_html( $title ) ); } ?> </select> <?php } public function handle_filter( $query ) { global $pagenow; if ( 'upload.php' !== $pagenow ) { return; } $current_value = $this->get_current_filter(); if ( empty( $current_value ) ) { return; } $meta_query = empty( $query->get( 'meta_query' ) ) ? [] : $query->get( 'meta_query' ); $meta_query[] = [ [ 'key' => '_wp_attachment_metadata', // Images without this field considered invalid 'compare' => 'EXISTS', ], ]; switch ( $current_value ) { case 'not-optimized': $meta_query[] = [ 'key' => Image_Meta::IMAGE_OPTIMIZER_METADATA_KEY, 'compare' => 'NOT EXISTS', ]; break; case 'optimized': $meta_query[] = [ 'compare' => 'LIKE', 'value' => '"status";s:9:"optimized"', 'key' => Image_Meta::IMAGE_OPTIMIZER_METADATA_KEY, ]; break; case 'in-progress': $meta_query[] = [ 'compare' => 'LIKE', 'value' => '-in-progress"', // Covers both optimization and restoring 'key' => Image_Meta::IMAGE_OPTIMIZER_METADATA_KEY, ]; break; case 'failed': $meta_query[] = [ 'compare' => 'LIKE', 'value' => '-failed"', // Covers both optimization and restoring 'key' => Image_Meta::IMAGE_OPTIMIZER_METADATA_KEY, ]; break; } $query->set( 'meta_query', $meta_query ); } private function get_current_filter(): string { return sanitize_text_field( wp_unslash( $_GET['image-optimization-filter'] ?? '' ) ); } public function __construct() { add_filter( 'restrict_manage_posts', [ $this, 'add_filter' ] ); add_filter( 'parse_query', [ $this, 'handle_filter' ] ); } } components/list-view-pointer.php 0000644 00000004075 14721622656 0013064 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Components; use ImageOptimization\Modules\Core\Components\Pointers; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class List_View_Pointer { const CURRENT_POINTER_SLUG = 'image-optimizer-list-view'; public function admin_print_script() { if ( $this->is_dismissed() ) { return; } wp_enqueue_script( 'wp-pointer' ); wp_enqueue_style( 'wp-pointer' ); $pointer_content = '<h3>' . esc_html__( 'Switch to list view', 'image-optimization' ) . '</h3>'; $pointer_content .= '<p>' . esc_html__( 'Get the most out of your optimizing options. Use the List view to quickly optimize your uploaded images with Image Optimizer.', 'image-optimization' ) . '</p>'; $allowed_tags = [ 'h3' => [], 'p' => [], ]; ?> <script> jQuery( document ).ready( function( $ ) { setTimeout(() => { $( '#wp-media-grid div.media-toolbar.wp-filter' ).first().pointer( { content: '<?php echo wp_kses( $pointer_content, $allowed_tags ); ?>', pointerClass: 'image-optimization-list-view-pointer', position: { edge: 'top', align: '<?php echo is_rtl() ? 'right' : 'left'; ?>', }, close() { return jQuery.ajax( { url: ajaxurl, method: 'POST', data: { action: 'image_optimizer_pointer_dismissed', data: { pointer: '<?php echo esc_attr( static::CURRENT_POINTER_SLUG ); ?>' }, nonce: '<?php echo esc_attr( wp_create_nonce( 'image-optimization-pointer-dismissed' ) ); ?>' } } ); } } ).pointer( 'open' ); }, 0) } ); </script> <style> .image-optimization-list-view-pointer .wp-pointer-arrow { inset-inline-start: 15px; } </style> <?php } private function is_dismissed(): bool { $meta = (array) get_user_meta( get_current_user_id(), Pointers::DISMISSED_POINTERS_META_KEY, true ); return key_exists( static::CURRENT_POINTER_SLUG, $meta ); } public function __construct() { add_action( 'in_admin_header', [ $this, 'admin_print_script' ] ); } } components/exceptions/bulk-optimization-token-not-found-error.php 0000644 00000000447 14721622656 0021500 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Components\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Bulk_Optimization_Token_Not_Found_Error extends Exception { protected $message = 'Bulk optimization token not found'; } components/single-optimization.php 0000644 00000005405 14721622656 0013466 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Components; use ImageOptimization\Classes\Async_Operation\Async_Operation_Hook; use ImageOptimization\Classes\Image\{ Image, Image_Meta, Image_Optimization_Error_Type, Image_Restore, Image_Status }; use ImageOptimization\Classes\Logger; use ImageOptimization\Classes\Exceptions\Quota_Exceeded_Error; use ImageOptimization\Modules\Optimization\Classes\Exceptions\Image_File_Already_Exists_Error; use ImageOptimization\Modules\Optimization\Classes\Optimize_Image; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Single_Optimization { /** @async */ public function optimize_single_image( int $image_id ) { try { $oi = new Optimize_Image( $image_id, 'manual', ); $oi->optimize(); } catch ( Quota_Exceeded_Error $qe ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->set_error_type( Image_Optimization_Error_Type::QUOTA_EXCEEDED ) ->save(); } catch ( Image_File_Already_Exists_Error $fe ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->set_error_type( Image_Optimization_Error_Type::FILE_ALREADY_EXISTS ) ->save(); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Optimization error. Reason: ' . $t->getMessage() ); ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->set_error_type( Image_Optimization_Error_Type::GENERIC ) ->save(); } } /** @async */ public function reoptimize_single_image( int $image_id ) { try { $image = new Image( $image_id ); if ( $image->can_be_restored() ) { Image_Restore::restore( $image_id, true ); } $oi = new Optimize_Image( $image_id, 'manual', null, true ); $oi->optimize(); } catch ( Quota_Exceeded_Error $qe ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::REOPTIMIZING_FAILED ) ->set_error_type( Image_Optimization_Error_Type::QUOTA_EXCEEDED ) ->save(); } catch ( Image_File_Already_Exists_Error $fe ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::REOPTIMIZING_FAILED ) ->set_error_type( Image_Optimization_Error_Type::FILE_ALREADY_EXISTS ) ->save(); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Reoptimizing error. Reason: ' . $t->getMessage() ); ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::REOPTIMIZING_FAILED ) ->set_error_type( Image_Optimization_Error_Type::GENERIC ) ->save(); } } public function __construct() { add_action( Async_Operation_Hook::OPTIMIZE_SINGLE, [ $this, 'optimize_single_image' ] ); add_action( Async_Operation_Hook::REOPTIMIZE_SINGLE, [ $this, 'reoptimize_single_image' ] ); } } components/bulk-optimization.php 0000644 00000013234 14721622656 0013141 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Components; use ImageOptimization\Classes\Async_Operation\Async_Operation_Hook; use ImageOptimization\Classes\Image\{ Image, Image_Meta, Image_Optimization_Error_Type, Image_Restore, Image_Status }; use ImageOptimization\Classes\Async_Operation\Exceptions\Async_Operation_Exception; use ImageOptimization\Classes\Logger; use ImageOptimization\Classes\Utils; use ImageOptimization\Classes\Exceptions\Quota_Exceeded_Error; use ImageOptimization\Modules\Optimization\{ Classes\Exceptions\Bulk_Token_Expired_Error, Classes\Exceptions\Image_File_Already_Exists_Error, Classes\Optimize_Image, Classes\Bulk_Optimization_Controller, Components\Exceptions\Bulk_Optimization_Token_Not_Found_Error, }; use ImageOptimization\Modules\Stats\Classes\Optimization_Stats; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Bulk_Optimization { const BULK_OPTIMIZATION_BASE_SLUG = 'image-optimization-bulk-optimization'; const BULK_OPTIMIZATION_CAPABILITY = 'manage_options'; public function render_app() { ?> <!-- The hack required to wrap WP notifications --> <div class="wrap"> <h1 style="display: none;" role="presentation"></h1> </div> <div id="image-optimization-app"></div> <?php } public function register_page() { add_media_page( __( 'Bulk Optimization', 'image-optimization' ), __( 'Bulk Optimization', 'image-optimization' ), self::BULK_OPTIMIZATION_CAPABILITY, self::BULK_OPTIMIZATION_BASE_SLUG, [ $this, 'render_app' ], 7 ); } /** @async */ public function optimize_bulk( int $image_id, string $operation_id ) { try { $bulk_token = Bulk_Optimization_Controller::get_bulk_operation_token( $operation_id ); $oi = new Optimize_Image( $image_id, 'bulk', $bulk_token ); $oi->optimize(); } catch ( Quota_Exceeded_Error $qe ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->set_error_type( Image_Optimization_Error_Type::QUOTA_EXCEEDED ) ->save(); } catch ( Image_File_Already_Exists_Error $fe ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->set_error_type( Image_Optimization_Error_Type::FILE_ALREADY_EXISTS ) ->save(); } catch ( Bulk_Token_Expired_Error | Bulk_Optimization_Token_Not_Found_Error $bte ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::NOT_OPTIMIZED ) ->save(); Bulk_Optimization_Controller::reschedule_bulk_optimization(); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Optimization error. Reason: ' . $t->getMessage() ); ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::OPTIMIZATION_FAILED ) ->set_error_type( Image_Optimization_Error_Type::GENERIC ) ->save(); } finally { Optimization_Stats::get_image_stats( null, true ); } } /** @async */ public function reoptimize_bulk( int $image_id, string $operation_id ) { try { $image = new Image( $image_id ); if ( $image->can_be_restored() ) { Image_Restore::restore( $image_id, true ); } $bulk_token = Bulk_Optimization_Controller::get_bulk_operation_token( $operation_id ); $oi = new Optimize_Image( $image_id, 'bulk', $bulk_token, true ); $oi->optimize(); } catch ( Quota_Exceeded_Error $qe ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::REOPTIMIZING_FAILED ) ->set_error_type( Image_Optimization_Error_Type::QUOTA_EXCEEDED ) ->save(); } catch ( Image_File_Already_Exists_Error $fe ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::REOPTIMIZING_FAILED ) ->set_error_type( Image_Optimization_Error_Type::FILE_ALREADY_EXISTS ) ->save(); } catch ( Bulk_Token_Expired_Error | Bulk_Optimization_Token_Not_Found_Error $bte ) { ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::NOT_OPTIMIZED ) ->save(); Bulk_Optimization_Controller::reschedule_bulk_reoptimization(); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Reoptimization error. Reason: ' . $t->getMessage() ); ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::REOPTIMIZING_FAILED ) ->set_error_type( Image_Optimization_Error_Type::GENERIC ) ->save(); } finally { Optimization_Stats::get_image_stats( null, true ); } } /** * Renders the bulk optimization notice * * @return void */ public function render_bulk_optimization_notice() { try { $is_in_progress = Bulk_Optimization_Controller::is_optimization_in_progress(); } catch ( Async_Operation_Exception $aoe ) { $is_in_progress = false; } ?> <div class="notice notice-info notice image-optimizer__notice image-optimizer__notice--info image-optimizer__notice--bulk-tip" style="display: <?php echo $is_in_progress ? 'block' : 'none'; ?>"> <p> <b> <?php esc_html_e( 'Heads up!', 'image-optimization' ); ?> </b> <span> <?php esc_html_e( 'Bulk optimizing may take a lot of processing and server time, depending on the number of images. Your site will still work smoothly until the processing is all done, without any downtime.', 'image-optimization' ); ?> </span> </p> </div> <?php } public function __construct() { add_action( 'admin_menu', [ $this, 'register_page' ] ); add_action( Async_Operation_Hook::OPTIMIZE_BULK, [ $this, 'optimize_bulk' ], 10, 2 ); add_action( Async_Operation_Hook::REOPTIMIZE_BULK, [ $this, 'reoptimize_bulk' ], 10, 2 ); add_action('current_screen', function () { if ( Utils::is_bulk_optimization_page() ) { add_filter( 'admin_footer_text', [ $this, 'render_bulk_optimization_notice' ] ); } }); } } components/media-control.php 0000644 00000021044 14721622656 0012213 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Components; use ImageOptimization\Classes\Image\{ Exceptions\Invalid_Image_Exception, Image, Image_Conversion, Image_Conversion_Option, Image_Meta, Image_Optimization_Error_Type, Image_Status }; use ImageOptimization\Modules\Oauth\Components\{ Exceptions\Auth_Error, }; use ImageOptimization\Modules\Optimization\{ Classes\Exceptions\Image_Validation_Error, Classes\Optimization_Error_Message, Classes\Validate_Image, Module, }; use ImageOptimization\Classes\File_Utils; use ImageOptimization\Modules\Settings\Classes\Settings; use ImageOptimization\Modules\Stats\Classes\Optimization_Stats; use Throwable; use ImageOptimization\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * The class is responsible for rendering optimization control in all views. */ class Media_Control { const COLUMN_ID = 'image_optimization'; const META_BOX_ID = 'image_optimization_meta_box'; const DETAILS_MODAL_FIELD_ID = 'image_optimization_modal'; /** * Adds a new optimization column to the library list view. * * @param array $columns * @return array */ public function add_optimization_column( array $columns ): array { if ( empty( $columns ) ) { return $columns; } $columns[ self::COLUMN_ID ] = esc_html__( 'Image optimization', 'image-optimization' ); return $columns; } /** * Renders optimization controls in the library list view. * * @param string $column_name * @param int $attachment_id * @return void */ public function render_optimization_column( string $column_name, int $attachment_id ) { if ( self::COLUMN_ID !== $column_name ) { return; } $this->render_optimization_control( 'list-view', $attachment_id ); } /** * Adds optimization control as media meta box. * * @return void */ public function add_optimization_meta_box() { add_meta_box( self::META_BOX_ID, __( 'Image optimization', 'image-optimization' ), function( $post ) { $this->render_optimization_control( 'meta-box', $post->ID ); }, 'attachment', 'side', ); } /** * Adds optimization control to media modals. * * @param array $form_fields * @param \WP_Post $post * * @return array */ public function add_media_modal_details( array $form_fields, \WP_Post $post ): array { $form_fields[ self::DETAILS_MODAL_FIELD_ID ] = [ 'label' => '', 'input' => 'hidden', 'value' => $this->get_optimization_control_html( 'details-view', $post->ID ), 'required' => false, ]; return $form_fields; } /** * Returns optimization control as an HTML string. * * @param string $context Control placement context. One of ['list-view', 'meta-box', 'details-view']. * @param int $image_id Attachment id. * @return false|string */ public function get_optimization_control_html( string $context, int $image_id ) { ob_start(); $this->render_optimization_control( $context, $image_id ); $html = ob_get_contents(); ob_end_clean(); return $html; } /** * Renders the optimization control for the current image in the current context. * * @param string $context Control placement context. One of ['list-view', 'meta-box', 'details-view']. * @param int $image_id Attachment id. * @return void Renders the control. */ public function render_optimization_control( string $context, int $image_id ) { $global_context = [ 'image_id' => $image_id, 'can_be_restored' => false, ]; // @var ImageOptimizer/Modules/ConnectManager/Module $module = Plugin::instance()->modules_manager->get_modules( 'connect-manager' ); try { if ( ! $module->connect_instance->is_connected() || ! $module->connect_instance->is_activated() ) { throw new Auth_Error( 'You have to activate your license to use Image Optimizer' ); } Validate_Image::is_valid( $image_id ); $image = new Image( $image_id ); $meta = new Image_Meta( $image->get_id() ); if ( Image_Status::OPTIMIZED === $meta->get_status() ) { $global_context['can_be_restored'] = $image->can_be_restored(); } else { $ic = new Image_Conversion(); $path = $image->get_file_path( Image::SIZE_FULL ); $backups_enabled = Settings::get( Settings::BACKUP_ORIGINAL_IMAGES_OPTION_NAME ); $conversion_enabled = $ic->is_enabled(); $needs_conversion = $conversion_enabled && strtolower( File_Utils::get_extension( $path ) ) !== $ic->get_current_file_extension(); $global_context['can_be_restored'] = ( $needs_conversion && $conversion_enabled ) || $backups_enabled; } switch ( $meta->get_status() ) { case Image_Status::OPTIMIZATION_IN_PROGRESS: Module::load_template( $context, 'loading', array_merge( $global_context, [ 'action' => 'optimize', ] ) ); break; case Image_Status::REOPTIMIZING_IN_PROGRESS: Module::load_template( $context, 'loading', array_merge( $global_context, [ 'action' => 'reoptimize', ] ) ); break; case Image_Status::RESTORING_IN_PROGRESS: Module::load_template( $context, 'loading', array_merge( $global_context, [ 'action' => 'restore', ] ) ); break; case Image_Status::OPTIMIZED: $stats = Optimization_Stats::get_image_stats( $image_id ); $saved = [ 'relative' => max( 100 - round( $stats['current_image_size'] / $stats['initial_image_size'] * 100 ), 0 ), 'absolute' => $stats['initial_image_size'] - $stats['current_image_size'], ]; $is_losseless_and_webp = Image_Conversion_Option::WEBP === Settings::get( Settings::CONVERT_TO_FORMAT_OPTION_NAME ) && 'lossless' === Settings::get( Settings::COMPRESSION_LEVEL_OPTION_NAME ); Module::load_template( $context, 'optimized', array_merge( $global_context, [ 'sizes_optimized_count' => $stats['optimized_image_count'], 'saved' => $saved, 'is_losseless_and_webp' => $is_losseless_and_webp, ] ) ); break; case Image_Status::OPTIMIZATION_FAILED: $error_type = $meta->get_error_type() ?? Image_Optimization_Error_Type::GENERIC; $error_message = Optimization_Error_Message::get_optimization_error_message( $error_type ); $images_left = $module->connect_instance->images_left(); Module::load_template( $context, 'error', array_merge( $global_context, [ 'message' => $error_message, 'optimization_error_type' => $error_type, 'images_left' => $images_left, 'allow_retry' => true, 'action' => 'optimize', ] ) ); break; case Image_Status::REOPTIMIZING_FAILED: $error_type = $meta->get_error_type() ?? Image_Optimization_Error_Type::GENERIC; $error_message = Optimization_Error_Message::get_reoptimization_error_message( $error_type ); $images_left = $module->connect_instance->images_left(); Module::load_template( $context, 'error', array_merge( $global_context, [ 'message' => $error_message, 'optimization_error_type' => $error_type, 'images_left' => $images_left, 'allow_retry' => true, 'action' => 'reoptimize', ] ) ); break; case Image_Status::RESTORING_FAILED: Module::load_template( $context, 'error', array_merge( $global_context, [ 'message' => esc_html__( 'Image restoring error', 'image-optimization' ), 'allow_retry' => true, 'action' => 'restore', ] ) ); break; default: Module::load_template( $context, 'not-optimized', $global_context ); } } catch ( Invalid_Image_Exception | Image_Validation_Error $iie ) { Module::load_template( $context, 'error', array_merge( $global_context, [ 'action' => 'error', 'message' => $iie->getMessage(), 'allow_retry' => false, ] ) ); } catch ( Auth_Error $ae ) { Module::load_template( $context, 'error', array_merge( $global_context, [ 'action' => 'error', 'message' => esc_html__( 'N/A', 'image-optimization' ), 'allow_retry' => false, ] ) ); } catch ( Throwable $t ) { Module::load_template( $context, 'error', array_merge( $global_context, [ 'action' => 'error', 'message' => esc_html__( 'Internal server error', 'image-optimization' ), 'allow_retry' => false, ] ) ); } } public function __construct() { add_filter( 'manage_upload_columns', [ $this, 'add_optimization_column' ] ); add_action( 'manage_media_custom_column', [ $this, 'render_optimization_column' ], 10, 2 ); add_action( 'add_meta_boxes_attachment', [ $this, 'add_optimization_meta_box' ] ); add_filter( 'attachment_fields_to_edit', [ $this, 'add_media_modal_details' ], 10, 2 ); } } components/admin-bulk-actions.php 0000644 00000002343 14721622656 0013140 0 ustar 00 <?php namespace ImageOptimization\Modules\Optimization\Components; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } use ImageOptimization\Modules\Backups\Classes\Restore_Images; use ImageOptimization\Modules\Optimization\Classes\Single_Optimization as Single_Optimization_Controller; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Admin_Bulk_Actions { public function add_bulk_actions( array $bulk_actions ): array { $bulk_actions['image-optimization-optimize'] = esc_html__( 'Optimize', 'image-optimization' ); $bulk_actions['image-optimization-restore'] = esc_html__( 'Restore original', 'image-optimization' ); return $bulk_actions; } public function handle_bulk_actions( $redirect_url, $action, $post_ids ): string { if ( 'image-optimization-optimize' === $action ) { Single_Optimization_Controller::optimize_many( $post_ids ); } if ( 'image-optimization-restore' === $action ) { Restore_Images::find_and_schedule_restoring( $post_ids ); } return $redirect_url; } public function __construct() { add_filter( 'bulk_actions-upload', [ $this, 'add_bulk_actions' ] ); add_filter( 'handle_bulk_actions-upload', [ $this, 'handle_bulk_actions' ], 10, 3 ); } } templates/details-view/optimized.php 0000644 00000006307 14721622656 0013675 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } use ImageOptimization\Classes\File_Utils; ?> <div class="image-optimization-control image-optimization-control--details-view image-optimization-control--optimized" data-image-optimization-context="details-view" data-image-optimization-status="optimized" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> <?php esc_html_e( 'Status', 'image-optimization' ); ?>: </span> <span class="image-optimization-control__property-value"> <?php esc_html_e( 'Optimized', 'image-optimization' ); ?> </span> </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> <?php esc_html_e( 'Image sizes optimized', 'image-optimization' ); ?>: </span> <span class="image-optimization-control__property-value"> <?php echo esc_html( $args['sizes_optimized_count'] ); ?> </span> </span> <span class="setting image-optimization-setting"> <?php if ( 0 === $args['saved']['absolute'] ) { ?> <span class="name image-optimization-control__property"></span> <span class="name image-optimization-control__property-value" style="max-width: 50%;"> <?php if ( $args['is_losseless_and_webp'] ) { echo esc_html_e( 'Requested WebP format is larger than the original file. Switch to Lossy in settings to convert this image in an optimized manner.', 'image-optimization' ); } else { echo esc_html_e( 'Image is fully optimized', 'image-optimization' ); } ?> </span> <?php } else { ?> <span class="name image-optimization-control__property"> <?php esc_html_e( 'Overall saving', 'image-optimization' ); ?>: </span> <span class="image-optimization-control__property-value"> <?php printf( esc_html__( '%1$s%% (%2$s)', 'image-optimization' ), esc_html( $args['saved']['relative'] ), esc_html( File_Utils::format_file_size( $args['saved']['absolute'], 1 ) ) ); ?> </span> <?php } ?> </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value image-optimization-control__property-value--button"> <button class="button button-link image-optimization-control__button image-optimization-control__button--reoptimize" type="button"> <?php esc_html_e( 'Reoptimize', 'image-optimization' ); ?> </button> </span> </span> <?php if ( $args['can_be_restored'] ) { ?> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value image-optimization-control__property-value--button"> <button class="button button-link image-optimization-control__button image-optimization-control__button--restore-original" type="button"> <?php esc_html_e( 'Restore original', 'image-optimization' ); ?> </button> </span> </span> <?php } ?> </div> templates/details-view/error.php 0000644 00000004230 14721622656 0013013 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <div class="image-optimization-control image-optimization-control--details-view image-optimization-control--error" data-image-optimization-context="details-view" data-image-optimization-status="error" data-image-allow-retry="<?php echo esc_attr( $args['allow_retry'] ); ?>" data-image-optimization-action="<?php echo esc_attr( $args['action'] ); ?>" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> <?php esc_html_e( 'Status', 'image-optimization' ); ?>: </span> <span class="image-optimization-control__property-value"> <?php esc_html_e( 'Error', 'image-optimization' ); ?> </span> </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> <?php esc_html_e( 'Reason', 'image-optimization' ); ?>: </span> <span class="image-optimization-control__property-value"> <?php echo esc_html( $args['message'] ); ?> </span> </span> <?php if ( $args['allow_retry'] ) { ?> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value image-optimization-control__property-value--button"> <?php if ( isset( $args['images_left'] ) && 0 === $args['images_left'] ) { ?> <a class="button button-secondary button-large image-optimization-control__button" href="https://go.elementor.com/io-panel-upgrade/" target="_blank" rel="noopener noreferrer"> <?php esc_html_e( 'Upgrade', 'image-optimization' ); ?> </a> <?php } else { ?> <button class="button button-secondary button-large button-link-delete image-optimization-control__button image-optimization-control__button--try-again" type="button"> <?php esc_html_e( 'Try again', 'image-optimization' ); ?> </button> <?php } ?> </span> </span> <?php } ?> </div> templates/details-view/not-optimized.php 0000644 00000002402 14721622656 0014463 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <div class="image-optimization-control image-optimization-control--details-view image-optimization-control--not-optimized" data-image-optimization-context="details-view" data-image-optimization-status="not-optimized" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> <?php esc_html_e( 'Status', 'image-optimization' ); ?>: </span> <span class="image-optimization-control__property-value"> <?php esc_html_e( 'Not optimized', 'image-optimization' ); ?> </span> </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value image-optimization-control__property-value--button"> <button type="button" class="button button-primary image-optimization-control__button image-optimization-control__button--optimize"> <?php esc_html_e( 'Optimize now', 'image-optimization' ); ?> </button> </span> </span> </div> templates/details-view/loading.php 0000644 00000002220 14721622656 0013274 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <div class="image-optimization-control image-optimization-control--details-view image-optimization-control--loading" data-image-optimization-context="details-view" data-image-optimization-status="loading" data-image-optimization-action="<?php echo esc_attr( $args['action'] ); ?>" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> <?php esc_html_e( 'Status', 'image-optimization' ); ?>: </span> <span class="image-optimization-control__property-value"> <?php esc_html_e( 'In Progress', 'image-optimization' ); ?> </span> </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value image-optimization-control__property-value--spinner"> <span class="spinner is-active"></span> </span> </span> </div> templates/list-view/optimized.php 0000644 00000004072 14721622656 0013220 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } use ImageOptimization\Classes\File_Utils; ?> <div class="image-optimization-control image-optimization-control--list-view image-optimization-control--optimized" data-image-optimization-context="list-view" data-image-optimization-status="optimized" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <p class="image-optimization-control__property"> <?php esc_html_e( 'Image sizes optimized', 'image-optimization' ); ?>: <span><?php echo esc_html( $args['sizes_optimized_count'] ); ?></span> </p> <p class="image-optimization-control__property"> <?php if ( 0 === $args['saved']['absolute'] ) { ?> <span> <?php if ( $args['is_losseless_and_webp'] ) { echo esc_html_e( 'Requested WebP format is larger than the original file. Switch to Lossy in settings to convert this image in an optimized manner.', 'image-optimization' ); } else { echo esc_html_e( 'Image is fully optimized', 'image-optimization' ); } ?> </span> <?php } else { ?> <?php esc_html_e( 'Overall saving', 'image-optimization' ); ?>: <span> <?php printf( esc_html__( '%1$s%% (%2$s)', 'image-optimization' ), esc_html( $args['saved']['relative'] ), esc_html( File_Utils::format_file_size( $args['saved']['absolute'], 1 ) ) ); ?> </span> <?php } ?> </p> <div class="image-optimization-control__buttons-wrapper"> <?php if ( $args['can_be_restored'] ) { ?> <button type="button" class="button button-secondary image-optimization-control__button image-optimization-control__button--restore-original"> <?php esc_html_e( 'Restore original', 'image-optimization' ); ?> </button> <?php } ?> <button type="button" class="button button-secondary image-optimization-control__button image-optimization-control__button--reoptimize"> <?php esc_html_e( 'Reoptimize', 'image-optimization' ); ?> </button> </div> </div> templates/list-view/error.php 0000644 00000003362 14721622656 0012346 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <div class="image-optimization-control image-optimization-control--list-view image-optimization-control--error" data-image-optimization-context="list-view" data-image-optimization-status="error" data-image-allow-retry="<?php echo esc_attr( $args['allow_retry'] ); ?>" data-image-optimization-action="<?php echo esc_attr( $args['action'] ); ?>" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <?php $message = esc_html( $args['message'] ); if ( false === $args['allow_retry'] ) { $message_chunks = explode( '. ', $message, 2 ); $message = "<span class='image-optimization-control__error-title'>{$message_chunks[0]}</span>"; if ( isset( $message_chunks[1] ) ) { $message .= "<span class='image-optimization-control__error-subtitle'>{$message_chunks[1]}</span>"; } } ?> <span class='image-optimization-control__error-message'><?php echo $message; ?></span> <?php if ( $args['allow_retry'] ) { ?> <?php if ( isset( $args['images_left'] ) && 0 === $args['images_left'] ) { ?> <a class="button button-secondary button-large image-optimization-control__button" href="https://go.elementor.com/io-panel-upgrade/" target="_blank" rel="noopener noreferrer"> <?php esc_html_e( 'Upgrade', 'image-optimization' ); ?> </a> <?php } else { ?> <button class="button button-secondary button-large button-link-delete image-optimization-control__button image-optimization-control__button--try-again" type="button"> <?php esc_html_e( 'Try again', 'image-optimization' ); ?> </button> <?php } ?> <?php } ?> </div> templates/list-view/not-optimized.php 0000644 00000001266 14721622656 0014020 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <div class="image-optimization-control image-optimization-control--list-view image-optimization-control--not-optimized" data-image-optimization-context="list-view" data-image-optimization-status="not-optimized" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <button type="button" class="button button-primary image-optimization-control__button image-optimization-control__button--optimize"> <?php esc_html_e( 'Optimize now', 'image-optimization' ); ?> </button> </div> templates/list-view/loading.php 0000644 00000002143 14721622656 0012626 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <div class="image-optimization-control image-optimization-control--list-view image-optimization-control--loading" data-image-optimization-context="list-view" data-image-optimization-status="loading" data-image-optimization-action="<?php echo esc_attr( $args['action'] ); ?>" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <button class="button button-secondary image-optimization-control__button image-optimization-control__button--optimize" disabled=""> <span class="spinner is-active"></span> <?php switch ( $args['action'] ) { case 'restore': esc_html_e( 'Restoring…', 'image-optimization' ); break; case 'optimize': esc_html_e( 'Optimizing…', 'image-optimization' ); break; case 'reoptimize': esc_html_e( 'Reoptimizing…', 'image-optimization' ); break; default: esc_html_e( 'Loading…', 'image-optimization' ); } ?> </button> </div> templates/meta-box/optimized.php 0000644 00000004713 14721622656 0013013 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } use ImageOptimization\Classes\File_Utils; ?> <div class="image-optimization-control image-optimization-control--meta-box image-optimization-control--optimized" data-image-optimization-context="meta-box" data-image-optimization-status="optimized" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <p class="image-optimization-control__property"> <?php esc_html_e( 'Status', 'image-optimization' ); ?>: <span class="image-optimization-control__property-value"> <?php esc_html_e( 'Optimized', 'image-optimization' ); ?> </span> </p> <p class="image-optimization-control__property"> <?php esc_html_e( 'Image sizes optimized', 'image-optimization' ); ?>: <span class="image-optimization-control__property-value"> <?php echo esc_html( $args['sizes_optimized_count'] ); ?> </span> </p> <p class="image-optimization-control__property"> <?php if ( 0 === $args['saved']['absolute'] ) { ?> <span class="image-optimization-control__property-value"> <?php if ( $args['is_losseless_and_webp'] ) { echo esc_html_e( 'Requested WebP format is larger than the original file. Switch to Lossy in settings to convert this image in an optimized manner.', 'image-optimization' ); } else { echo esc_html_e( 'Image is fully optimized', 'image-optimization' ); } ?> </span> <?php } else { ?> <?php esc_html_e( 'Overall saving', 'image-optimization' ); ?>: <span class="image-optimization-control__property-value"> <?php printf( esc_html__( '%1$s%% (%2$s)', 'image-optimization' ), esc_html( $args['saved']['relative'] ), esc_html( File_Utils::format_file_size( $args['saved']['absolute'], 1 ) ) ); ?> </span> <?php } ?> </p> <div class="image-optimization-control__action-button-wrapper"> <?php if ( $args['can_be_restored'] ) { ?> <button class="button button-link image-optimization-control__button image-optimization-control__button--restore-original" type="button"> <?php esc_html_e( 'Restore original', 'image-optimization' ); ?> </button> <?php } ?> <button class="button button-link image-optimization-control__button image-optimization-control__button--reoptimize" type="button"> <?php esc_html_e( 'Reoptimize', 'image-optimization' ); ?> </button> </div> </div> templates/meta-box/error.php 0000644 00000003476 14721622656 0012145 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <div class="image-optimization-control image-optimization-control--meta-box image-optimization-control--error" data-image-optimization-context="meta-box" data-image-optimization-status="error" data-image-allow-retry="<?php echo esc_attr( $args['allow_retry'] ); ?>" data-image-optimization-action="<?php echo esc_attr( $args['action'] ); ?>" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <p class="image-optimization-control__property"> <?php esc_html_e( 'Status', 'image-optimization' ); ?>: <span class="image-optimization-control__property-value"> <?php esc_html_e( 'Error', 'image-optimization' ); ?> </span> </p> <p class="image-optimization-control__property"> <?php esc_html_e( 'Reason', 'image-optimization' ); ?>: <span class="image-optimization-control__property-value"> <?php echo esc_html( $args['message'] ); ?> </span> </p> <?php if ( $args['allow_retry'] ) { ?> <div class="image-optimization-control__action-button-wrapper"> <?php if ( isset( $args['images_left'] ) && 0 === $args['images_left'] ) { ?> <a class="button button-secondary button-large image-optimization-control__button" href="https://go.elementor.com/io-panel-upgrade/" target="_blank" rel="noopener noreferrer"> <?php esc_html_e( 'Upgrade', 'image-optimization' ); ?> </a> <?php } else { ?> <button class="button button-secondary button-large button-link-delete image-optimization-control__button image-optimization-control__button--try-again" type="button"> <?php esc_html_e( 'Try again', 'image-optimization' ); ?> </button> <?php } ?> </div> <?php } ?> </div> templates/meta-box/not-optimized.php 0000644 00000001775 14721622656 0013616 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <div class="image-optimization-control image-optimization-control--meta-box image-optimization-control--not-optimized" data-image-optimization-context="meta-box" data-image-optimization-status="not-optimized" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <p class="image-optimization-control__property"> <?php esc_html_e( 'Status', 'image-optimization' ); ?>: <span class="image-optimization-control__property-value"> <?php esc_html_e( 'Not optimized', 'image-optimization' ); ?> </span> </p> <div class="image-optimization-control__action-button-wrapper"> <button type="button" class="button button-primary image-optimization-control__button image-optimization-control__button--optimize"> <?php esc_html_e( 'Optimize now', 'image-optimization' ); ?> </button> </div> </div> templates/meta-box/loading.php 0000644 00000001616 14721622656 0012423 0 ustar 00 <?php if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } ?> <div class="image-optimization-control image-optimization-control--meta-box image-optimization-control--loading" data-image-optimization-context="meta-box" data-image-optimization-status="loading" data-image-optimization-action="<?php echo esc_attr( $args['action'] ); ?>" data-image-optimization-image-id="<?php echo esc_attr( $args['image_id'] ); ?>" data-image-optimization-can-be-restored="<?php echo esc_attr( $args['can_be_restored'] ); ?>"> <p class="image-optimization-control__property"> <?php esc_html_e( 'Status', 'image-optimization' ); ?>: <span class="image-optimization-control__property-value"> <?php esc_html_e( 'In Progress', 'image-optimization' ); ?> </span> </p> <div class="image-optimization-control__action-spinner-wrapper"> <span class="spinner is-active"></span> </div> </div> assets/css/control.css 0000644 00000006244 14721622656 0011051 0 ustar 00 .image-optimization-control--list-view { display: flex; justify-content: flex-start; align-items: center; min-height: 105px; } .image-optimization-control--list-view.image-optimization-control--error { justify-content: center; align-items: flex-start; } .image-optimization-control__buttons-wrapper { margin-block-start: 8px; } .image-optimization-control__buttons-wrapper :not(:last-of-type) { margin-inline-end: 8px; } .image-optimization-control--error { flex-direction: column; } .image-optimization-control--optimized { flex-direction: column; align-items: flex-start; } .image-optimization-control__error-message { display: flex; flex-direction: column; font-family: Roboto, sans-serif; font-size: 14px; font-weight: 600; line-height: 24px; margin-block-end: 15px; max-width: 200px; } .image-optimization-control__error-title { margin-block-end: 10px; } .image-optimization-control__error-subtitle { color: #DC2626; } .image-optimization-control__button { font-family: Roboto, sans-serif; font-weight: 500; border-radius: 4px; } .image-optimization-control__button--optimize > .spinner { float: left; padding: 0; margin-block-start: 4px; margin-left: 0; margin-block-end: 0; } .image-optimization-control--error .image-optimization-control__button--try-again, .image-optimization-control--error .image-optimization-control__button--try-again:hover { color: #DC2626; border-color: #DC2626; background: transparent; } .image-optimization-control__property { margin: 9px 0; } .image-optimization-control__property:last-of-type { margin-block-end: 0; } .image-optimization-control .image-optimization-setting { margin-block-end: 0; } .image-optimization-control__property-value, .image-optimization-control--details-view .setting .image-optimization-control__property-value { font-weight: 600; font-size: 13px; line-height: 24px; } .image-optimization-control--details-view .setting .image-optimization-control__property-value { margin: 6px 3px 3px; text-align: start; } .image-optimization-control--details-view .setting .image-optimization-control__property-value--button { margin: 0 0 0 3px; font-weight: initial; } .image-optimization-control--details-view .setting .image-optimization-control__property-value--spinner { display: flex; justify-content: center; margin: 25px 0 0 3px; } .image-optimization-control__action-button-wrapper { margin-block-start: 37px; display: flex; width: 100%; justify-content: flex-end; } .image-optimization-control__action-button-wrapper .button-link:not(:last-of-type) { margin-inline-end: 24px; } .image-optimization-control__action-button-wrapper .button-link, .image-optimization-control--details-view .image-optimization-control__property-value--button .button-link { text-decoration: none; } .image-optimization-control__action-button-wrapper .button-link:hover, .image-optimization-control--details-view .image-optimization-control__property-value--button .button-link:hover { background-color: transparent; } .image-optimization-control__action-spinner-wrapper { height: 75px; display: flex; justify-content: center; align-items: center; } div .components-tooltip { max-width: 165px; text-align: start; } assets/js/classes/api.js 0000644 00000002375 14721622656 0011250 0 ustar 00 import apiFetch from '@wordpress/api-fetch'; import APIError from './exceptions/APIError'; const v1Prefix = '/image-optimizer/v1'; class API { static async request( { path, data, method = 'POST' } ) { try { const response = await apiFetch( { path, method, data, } ); if ( ! response.success ) { throw new APIError( response.data.message ); } return response.data; } catch ( e ) { if ( e instanceof APIError ) { throw e; } else { throw new APIError( e.message ); } } } static async optimizeSingleImage( { imageId, reoptimize = false } ) { return API.request( { path: `${ v1Prefix }/optimize/image`, data: { imageId, reoptimize, 'image-optimization-optimize-image': window?.imageOptimizerControlSettings?.optimizeSingleImageNonce, }, } ); } static async restoreSingleImage( imageId ) { return API.request( { path: `${ v1Prefix }/backups/restore/${ imageId }`, data: { 'image-optimization-restore-single': window?.imageOptimizerControlSettings?.restoreSingleImageNonce, }, } ); } static async getOptimizationStatus( imageIds ) { return API.request( { path: `${ v1Prefix }/optimize/status`, data: { image_ids: imageIds, }, } ); } } export default API; assets/js/classes/extend-views.js 0000644 00000004231 14721622656 0013112 0 ustar 00 class ExtendViews { constructor() { this.init(); } init() { this.extendAttachmentDetails(); this.extendAttachmentDetailsTwoColumn(); } // #tmpl-attachment-details extendAttachmentDetails() { if ( ! wp?.media?.view?.Attachment?.Details ) { return; } wp.media.view.Attachment.Details = wp.media.view.Attachment.Details.extend( { template( view ) { const html = wp.media.template( 'attachment-details' )( view ); if ( this.model.attributes.type !== 'image' ) { return html; } const content = document.createElement( 'div' ); content.innerHTML = html; const optimizationControl = this.getOptimizationControlHTML( view.compat.item ); if ( ! optimizationControl ) { return content.innerHTML; } content.innerHTML += optimizationControl; return content.innerHTML; }, getOptimizationControlHTML( compatData ) { const tempElement = document.createElement( 'div' ); tempElement.innerHTML = compatData; return tempElement.querySelector( 'input[name*="[image_optimization_modal]"]' )?.value; }, } ); } // #tmpl-attachment-details-two-column extendAttachmentDetailsTwoColumn() { if ( ! wp?.media?.view?.Attachment?.Details?.TwoColumn ) { return; } wp.media.view.Attachment.Details.TwoColumn = wp.media.view.Attachment.Details.TwoColumn.extend( { template( view ) { const html = wp.media.template( 'attachment-details-two-column' )( view ); if ( this.model.attributes.type !== 'image' ) { return html; } const content = document.createElement( 'div' ); content.innerHTML = html; const optimizationControl = this.getOptimizationControlHTML( view.compat.item ); if ( ! optimizationControl ) { return content.innerHTML; } const settings = content.querySelector( '.settings' ); settings.innerHTML += optimizationControl; return content.innerHTML; }, getOptimizationControlHTML( compatData ) { const tempElement = document.createElement( 'div' ); tempElement.innerHTML = compatData; return tempElement.querySelector( 'input[name*="[image_optimization_modal]"]' )?.value; }, } ); } } export default ExtendViews; assets/js/classes/exceptions/APIError.js 0000644 00000000207 14721622656 0014273 0 ustar 00 class APIError extends Error { constructor( message ) { super( message ); this.name = 'APIError'; } } export default APIError; assets/js/classes/control/control-sync.js 0000644 00000004760 14721622656 0014611 0 ustar 00 import API from '../api'; import { SELECTORS } from '../../constants'; import ControlMeta from './control-meta'; import ControlStates from './control-states'; class ControlSync { async run() { const controls = document.querySelectorAll( SELECTORS.controlWrapperSelector ); if ( ! controls.length ) { return; } const imageIds = this.mapImageIds( controls ); const { status } = await API.getOptimizationStatus( imageIds ); controls.forEach( ( controlWrapper ) => { const imageId = new ControlMeta( controlWrapper ).getImageId(); const currentStatus = new ControlMeta( controlWrapper ).getStatus(); const imageData = status[ imageId ]; const controlStates = new ControlStates( controlWrapper ); if ( currentStatus === imageData.status ) { return; } if ( controlWrapper.dataset.isFrozen === 'true' ) { controlWrapper.dataset.isFrozen = false; return; } if ( currentStatus === 'error' && ! new ControlMeta( controlWrapper ).allowRetry() ) { return; } switch ( imageData.status ) { case 'optimization-in-progress': controlStates.renderLoading( 'optimize' ); break; case 'reoptimizing-in-progress': controlStates.renderLoading( 'reoptimize' ); break; case 'restoring-in-progress': controlStates.renderLoading( 'restore' ); break; case 'not-optimized': controlStates.renderNotOptimized(); break; case 'optimized': const statsData = { sizesOptimized: imageData.stats.optimized_image_count, saved: { absolute: imageData.stats.initial_image_size - imageData.stats.current_image_size, relative: Math.max( 100 - Math.round( imageData.stats.current_image_size / imageData.stats.initial_image_size * 100 ), 0 ), }, }; controlStates.renderOptimized( statsData ); break; case 'optimization-failed': controlStates.renderError( { message: imageData.message, imagesLeft: imageData.images_left, action: 'optimize', } ); break; case 'reoptimizing-failed': controlStates.renderError( { message: imageData.message, imagesLeft: imageData.images_left, action: 'reoptimize', } ); break; case 'restoring-failed': controlStates.renderError( { message: imageData.message, action: 'restore', } ); } } ); } mapImageIds( nodes ) { return Array.prototype.map.call( nodes, ( node ) => new ControlMeta( node ).getImageId(), ); } } export default ControlSync; assets/js/classes/control/control-states.js 0000644 00000004767 14721622656 0015147 0 ustar 00 import listViewTemplates from '../../templates/list-view'; import metaBoxTemplates from '../../templates/meta-box'; import detailsViewTemplates from '../../templates/details-view'; import { SELECTORS } from '../../constants'; import ControlMeta from './control-meta'; class ControlStates { constructor( controlWrapper ) { this.controlWrapper = controlWrapper; this.context = new ControlMeta( controlWrapper ).getContext(); this.action = new ControlMeta( controlWrapper ).getAction(); this.canBeRestored = new ControlMeta( controlWrapper ).canBeRestored(); this.templates = { 'list-view': listViewTemplates, 'meta-box': metaBoxTemplates, 'details-view': detailsViewTemplates, }; } renderNotOptimized( data ) { this.controlWrapper.className = this.mixControlContextClass( SELECTORS.controlNotOptimizedClassName ); this.controlWrapper.innerHTML = this.getTemplates().notOptimizedTemplate( data ); this.controlWrapper.dataset.imageOptimizationStatus = 'not-optimized'; } renderOptimized( data ) { const canBeRestored = this.canBeRestored && data?.saved?.absolute !== 0; this.controlWrapper.className = this.mixControlContextClass( SELECTORS.controlOptimizedClassName ); this.controlWrapper.innerHTML = this.getTemplates().optimizedTemplate( { ...data, canBeRestored } ); this.controlWrapper.dataset.imageOptimizationStatus = 'optimized'; } renderError( { message, imagesLeft, action } ) { this.controlWrapper.className = this.mixControlContextClass( SELECTORS.controlErrorClassName ); this.controlWrapper.innerHTML = this.getTemplates().errorTemplate( message, imagesLeft ); this.controlWrapper.dataset.imageOptimizationAction = action; this.controlWrapper.dataset.imageOptimizationStatus = 'error'; } renderLoading( action ) { this.controlWrapper.className = this.mixControlContextClass( SELECTORS.controlLoadingClassName ); this.controlWrapper.innerHTML = this.getTemplates().loadingTemplate( action ); this.controlWrapper.dataset.imageOptimizationStatus = 'loading'; } getTemplates() { const template = this.templates[ this.context ]; if ( ! template ) { throw new Error( `No templates found for the context ${ this.context }` ); } return template; } mixControlContextClass( className ) { const contextClassName = SELECTORS.controlWrapper[ this.context ]; if ( ! contextClassName ) { throw new Error( `No context className found for the context ${ this.context }` ); } return `${ className } ${ contextClassName }`; } } export default ControlStates; assets/js/classes/control/control-meta.js 0000644 00000001415 14721622656 0014555 0 ustar 00 class ControlMeta { constructor( controlNode ) { this.controlNode = controlNode; } getImageId() { return this.controlNode.dataset?.imageOptimizationImageId ? parseInt( this.controlNode.dataset?.imageOptimizationImageId, 10 ) : null; } getAction() { return this.controlNode.dataset?.imageOptimizationAction || null; } getContext() { return this.controlNode.dataset?.imageOptimizationContext || null; } getStatus() { return this.controlNode.dataset?.imageOptimizationStatus || null; } canBeRestored() { const value = this.controlNode.dataset?.imageOptimizationCanBeRestored; if ( ! value ) { return null; } return '1' === value; } allowRetry() { return this.controlNode.dataset?.allowRetry || null; } } export default ControlMeta; assets/js/constants/index.js 0000644 00000002640 14721622656 0012160 0 ustar 00 export const UPGRADE_LINK = 'https://go.elementor.com/io-panel-upgrade/'; export const SELECTORS = Object.freeze( { optimizeButtonSelector: '.image-optimization-control__button--optimize', reoptimizeButtonSelector: '.image-optimization-control__button--reoptimize', tryAgainOptimizeButtonSelector: '[data-image-optimization-action="optimize"] .image-optimization-control__button--try-again', tryAgainReoptimizeButtonSelector: '[data-image-optimization-action="reoptimize"] .image-optimization-control__button--try-again', tryAgainRestoreButtonSelector: '[data-image-optimization-action="restore"] .image-optimization-control__button--try-again', controlWrapperSelector: '.image-optimization-control', controlNotOptimizedClassName: 'image-optimization-control image-optimization-control--not-optimized', controlLoadingClassName: 'image-optimization-control image-optimization-control--loading', controlOptimizedClassName: 'image-optimization-control image-optimization-control--optimized', controlErrorClassName: 'image-optimization-control image-optimization-control--error', controlWrapper: { 'list-view': 'image-optimization-control--list-view', 'meta-box': 'image-optimization-control--meta-box', 'details-view': 'image-optimization-control--details-view', }, restoreButtonSelector: '.image-optimization-control__button--restore-original', loadingControlsSelector: '[data-image-optimization-status="loading"]', } ); assets/js/module.js 0000644 00000000467 14721622656 0010327 0 ustar 00 import '../css/control.css'; import ExtendViews from './classes/extend-views'; import OptimizationControl from './control'; class Module { constructor() { this.init(); } init() { new ExtendViews(); new OptimizationControl(); } } document.addEventListener( 'DOMContentLoaded', () => new Module() ); assets/js/utils/index.js 0000644 00000001632 14721622656 0011304 0 ustar 00 import { __, sprintf } from '@wordpress/i18n'; export const formatFileSize = ( fileSizeInBytes, decimals = 2 ) => { const sizes = [ // translators: %s: file size in bytes __( '%s Bytes', 'image-optimization' ), // translators: %s: file size in kilobytes __( '%s Kb', 'image-optimization' ), // translators: %s: file size in megabytes __( '%s Mb', 'image-optimization' ), // translators: %s: file size in gigabytes __( '%s Gb', 'image-optimization' ), ]; if ( ! fileSizeInBytes ) { // translators: %s: file size in bytes return sprintf( __( '%s Bytes', 'image-optimization' ), 0 ); } const currentScale = Math.floor( Math.log( fileSizeInBytes ) / Math.log( 1024 ) ); const formattedValue = parseFloat( ( fileSizeInBytes / Math.pow( 1024, currentScale ) ).toFixed( decimals ) ); // eslint-disable-next-line @wordpress/valid-sprintf return sprintf( sizes[ currentScale ], formattedValue ); }; assets/js/templates/details-view/index.js 0000644 00000012620 14721622656 0014536 0 ustar 00 import { __ } from '@wordpress/i18n'; import { escapeHTML } from '@wordpress/escape-html'; import { formatFileSize } from '../../utils'; import { UPGRADE_LINK } from '../../constants'; const notOptimizedTemplate = () => { return ` <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> ${ __( 'Status', 'image-optimization' ) }: </span> <span class="image-optimization-control__property-value"> ${ __( 'Not optimized', 'image-optimization' ) } </span> </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value image-optimization-control__property-value--button"> <button type="button" class="button button-primary image-optimization-control__button image-optimization-control__button--optimize"> ${ __( 'Optimize now', 'image-optimization' ) } </button> </span> </span> `; }; const loadingTemplate = () => { return ` <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> ${ __( 'Status', 'image-optimization' ) }: </span> <span class="image-optimization-control__property-value"> ${ __( 'In Progress', 'image-optimization' ) } </span> </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value image-optimization-control__property-value--spinner"> <span class="spinner is-active"></span> </span> </span> `; }; const errorTemplate = ( message, imagesLeft ) => { return ` <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> ${ __( 'Status', 'image-optimization' ) }: </span> <span class="image-optimization-control__property-value"> ${ __( 'Error', 'image-optimization' ) } </span> </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> ${ __( 'Reason', 'image-optimization' ) }: </span> <span class="image-optimization-control__property-value"> ${ escapeHTML( message ) } </span> </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value image-optimization-control__property-value--button"> ${ imagesLeft === 0 ? `<a class="button button-secondary button-large image-optimization-control__button" href="${ UPGRADE_LINK }" target="_blank" rel="noopener noreferrer"> ${ __( 'Upgrade', 'image-optimization' ) } </a> ` : ` <button class="button button-secondary button-large button-link-delete image-optimization-control__button image-optimization-control__button--try-again" type="button"> ${ __( 'Try again', 'image-optimization' ) } </button>` } </span> </span> `; }; const optimizedTemplate = ( data ) => { const absoluteValue = formatFileSize( data?.saved?.absolute, 1 ); return ` <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> ${ __( 'Status', 'image-optimization' ) }: </span> <span class="image-optimization-control__property-value"> ${ __( 'Optimized', 'image-optimization' ) } </span> </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"> ${ __( 'Image sizes optimized', 'image-optimization' ) }: </span> <span class="image-optimization-control__property-value"> ${ data?.sizesOptimized } </span> </span> <span class="setting image-optimization-setting"> ${ data?.saved?.absolute !== 0 ? `<span class="name image-optimization-control__property"> ${ __( 'Overall saving', 'image-optimization' ) }: </span> <span class="image-optimization-control__property-value"> ${ data?.saved?.relative }% (${ absoluteValue }) </span>` : `<span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value"> ${ __( 'Image is fully optimized', 'image-optimization' ) } </span>` } </span> <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value image-optimization-control__property-value--button"> <button class="button button-link image-optimization-control__button image-optimization-control__button--reoptimize" type="button"> ${ __( 'Reoptimize', 'image-optimization' ) } </button> </span> </span> ${ data?.canBeRestored ? ` <span class="setting image-optimization-setting"> <span class="name image-optimization-control__property"></span> <span class="image-optimization-control__property-value image-optimization-control__property-value--button"> <button class="button button-link image-optimization-control__button image-optimization-control__button--restore-original" type="button"> ${ __( 'Restore original', 'image-optimization' ) } </button> </span> </span> ` : '' } `; }; const detailsViewTemplates = Object.freeze( { notOptimizedTemplate, loadingTemplate, errorTemplate, optimizedTemplate, } ); export default detailsViewTemplates; assets/js/templates/list-view/index.js 0000644 00000005736 14721622656 0014076 0 ustar 00 import { __ } from '@wordpress/i18n'; import { escapeHTML } from '@wordpress/escape-html'; import { formatFileSize } from '../../utils'; import { UPGRADE_LINK } from '../../constants'; const notOptimizedTemplate = () => { return ` <button type="button" class="button button-primary image-optimization-control__button image-optimization-control__button--optimize"> ${ __( 'Optimize now', 'image-optimization' ) } </button> `; }; const loadingTemplate = ( action ) => { let buttonText; switch ( action ) { case 'restore': buttonText = __( 'Restoring…', 'image-optimization' ); break; case 'optimize': buttonText = __( 'Optimizing…', 'image-optimization' ); break; case 'reoptimize': buttonText = __( 'Reoptimizing…', 'image-optimization' ); break; default: buttonText = __( 'Loading…', 'image-optimization' ); } return ` <button class="button button-secondary image-optimization-control__button image-optimization-control__button--optimize" disabled=""> <span class="spinner is-active"></span> ${ buttonText } </button> `; }; const errorTemplate = ( message, imagesLeft ) => { return ` <span class="image-optimization-control__error-message">${ escapeHTML( message ) }</span> ${ imagesLeft === 0 ? `<a class="button button-secondary button-large image-optimization-control__button" href="${ UPGRADE_LINK }" target="_blank" rel="noopener noreferrer"> ${ __( 'Upgrade', 'image-optimization' ) } </a> ` : ` <button class="button button-secondary button-large button-link-delete image-optimization-control__button image-optimization-control__button--try-again" type="button"> ${ __( 'Try again', 'image-optimization' ) } </button>` } `; }; const optimizedTemplate = ( data ) => { const absoluteValue = formatFileSize( data?.saved?.absolute, 1 ); return ` <p class="image-optimization-control__property"> ${ __( 'Image sizes optimized', 'image-optimization' ) }: <span>${ data?.sizesOptimized }</span> </p> <p class="image-optimization-control__property"> ${ data?.saved?.absolute !== 0 ? `${ __( 'Overall saving', 'image-optimization' ) }: <span>${ data?.saved?.relative }% (${ absoluteValue })</span>` : `<span>${ __( 'Image is fully optimized', 'image-optimization' ) }</span>` } </p> <div class="image-optimization-control__buttons-wrapper"> ${ data?.canBeRestored ? ` <button type="button" class="button button-secondary image-optimization-control__button image-optimization-control__button--restore-original"> ${ __( 'Restore original', 'image-optimization' ) } </button> ` : '' } <button type="button" class="button button-secondary image-optimization-control__button image-optimization-control__button--reoptimize"> ${ __( 'Reoptimize', 'image-optimization' ) } </button> </div> `; }; const listViewTemplates = Object.freeze( { notOptimizedTemplate, loadingTemplate, errorTemplate, optimizedTemplate, } ); export default listViewTemplates; assets/js/templates/meta-box/index.js 0000644 00000007540 14721622656 0013662 0 ustar 00 import { __ } from '@wordpress/i18n'; import { escapeHTML } from '@wordpress/escape-html'; import { formatFileSize } from '../../utils'; import { UPGRADE_LINK } from '../../constants'; const notOptimizedTemplate = () => { return ` <p class="image-optimization-control__property"> ${ __( 'Status', 'image-optimization' ) }: <span class="image-optimization-control__property-value"> ${ __( 'Not optimized', 'image-optimization' ) } </span> </p> <div class="image-optimization-control__action-button-wrapper"> <button type="button" class="button button-primary image-optimization-control__button image-optimization-control__button--optimize"> ${ __( 'Optimize now', 'image-optimization' ) } </button> </div> `; }; const loadingTemplate = () => { return ` <p class="image-optimization-control__property"> ${ __( 'Status', 'image-optimization' ) }: <span class="image-optimization-control__property-value"> ${ __( 'In Progress', 'image-optimization' ) } </span> </p> <div class="image-optimization-control__action-spinner-wrapper"> <span class="spinner is-active"></span> </div> `; }; const errorTemplate = ( message, imagesLeft ) => { return ` <p class="image-optimization-control__property"> ${ __( 'Status', 'image-optimization' ) }: <span class="image-optimization-control__property-value"> ${ __( 'Error', 'image-optimization' ) } </span> </p> <p class="image-optimization-control__property"> ${ __( 'Reason', 'image-optimization' ) }: <span class="image-optimization-control__property-value"> ${ escapeHTML( message ) } </span> </p> <div class="image-optimization-control__action-button-wrapper"> ${ imagesLeft === 0 ? `<a class="button button-secondary button-large image-optimization-control__button" href="${ UPGRADE_LINK }" target="_blank" rel="noopener noreferrer"> ${ __( 'Upgrade', 'image-optimization' ) } </a> ` : ` <button class="button button-secondary button-large button-link-delete image-optimization-control__button image-optimization-control__button--try-again" type="button"> ${ __( 'Try again', 'image-optimization' ) } </button>` } </div> `; }; const optimizedTemplate = ( data ) => { const absoluteValue = formatFileSize( data?.saved?.absolute, 1 ); return ` <p class="image-optimization-control__property"> ${ __( 'Status', 'image-optimization' ) }: <span class="image-optimization-control__property-value"> ${ __( 'Optimized', 'image-optimization' ) } </span> </p> <p class="image-optimization-control__property"> ${ __( 'Image sizes optimized', 'image-optimization' ) }: <span class="image-optimization-control__property-value"> ${ data?.sizesOptimized } </span> </p> <p class="image-optimization-control__property"> ${ data?.saved?.absolute !== 0 ? ` <span class="image-optimization-control__property-value"> ${ __( 'Overall saving', 'image-optimization' ) }: ${ data?.saved?.relative }% (${ absoluteValue }) </span> ` : ` <span class="image-optimization-control__property-value"> ${ __( 'Image is fully optimized', 'image-optimization' ) } </span> ` } </p> <div class="image-optimization-control__action-button-wrapper"> ${ data?.canBeRestored ? ` <button class="button button-link image-optimization-control__button image-optimization-control__button--restore-original" type="button"> ${ __( 'Restore original', 'image-optimization' ) } </button> ` : '' } <button class="button button-link image-optimization-control__button image-optimization-control__button--reoptimize" type="button"> ${ __( 'Reoptimize', 'image-optimization' ) } </button> </div> `; }; const metaBoxTemplates = Object.freeze( { notOptimizedTemplate, loadingTemplate, errorTemplate, optimizedTemplate, } ); export default metaBoxTemplates; assets/js/control.js 0000644 00000006050 14721622656 0010514 0 ustar 00 import { __ } from '@wordpress/i18n'; import { speak } from '@wordpress/a11y'; import { SELECTORS } from './constants'; import API from './classes/api'; import ControlSync from './classes/control/control-sync'; import ControlStates from './classes/control/control-states'; import ControlMeta from './classes/control/control-meta'; class OptimizationControl { constructor() { this.controlSyncRequestInProgress = false; this.init(); this.controlSync = new ControlSync(); } init() { this.initEventListeners(); setInterval( () => this.runStatusCheckLoop(), 5000 ); } async runStatusCheckLoop() { if ( this.controlSyncRequestInProgress ) { return; } this.controlSyncRequestInProgress = true; await this.controlSync.run(); this.controlSyncRequestInProgress = false; } initEventListeners() { document.addEventListener( 'click', ( e ) => this.handleOptimizeButtonClick( e ) ); document.addEventListener( 'click', ( e ) => this.handleReoptimizeButtonClick( e ) ); document.addEventListener( 'click', ( e ) => this.handleRestoreButtonClick( e ) ); } async handleOptimizeButtonClick( e ) { if ( ! e.target.closest( `${ SELECTORS.optimizeButtonSelector }, ${ SELECTORS.tryAgainOptimizeButtonSelector }` ) ) { return; } speak( __( 'Optimization is in progress', 'image-optimization' ), 'assertive' ); const controlWrapper = e.target.closest( SELECTORS.controlWrapperSelector ); const states = new ControlStates( controlWrapper ); states.renderLoading( 'optimize' ); try { controlWrapper.dataset.isFrozen = true; await API.optimizeSingleImage( { imageId: new ControlMeta( controlWrapper ).getImageId(), reoptimize: false, } ); } catch ( error ) { states.renderError( error ); } } async handleReoptimizeButtonClick( e ) { if ( ! e.target.closest( `${ SELECTORS.reoptimizeButtonSelector }, ${ SELECTORS.tryAgainReoptimizeButtonSelector }` ) ) { return; } speak( __( 'Reoptimizing is in progress', 'image-optimization' ), 'assertive' ); const controlWrapper = e.target.closest( SELECTORS.controlWrapperSelector ); const states = new ControlStates( controlWrapper ); states.renderLoading( 'reoptimize' ); try { controlWrapper.dataset.isFrozen = true; await API.optimizeSingleImage( { imageId: new ControlMeta( controlWrapper ).getImageId(), reoptimize: true, } ); } catch ( error ) { states.renderError( error ); } } async handleRestoreButtonClick( e ) { if ( ! e.target.closest( `${ SELECTORS.restoreButtonSelector }, ${ SELECTORS.tryAgainRestoreButtonSelector }` ) ) { return; } speak( __( 'Image restoring is in progress', 'image-optimization' ), 'assertive' ); const controlWrapper = e.target.closest( SELECTORS.controlWrapperSelector ); const states = new ControlStates( controlWrapper ); states.renderLoading( 'restore' ); try { controlWrapper.dataset.isFrozen = true; await API.restoreSingleImage( new ControlMeta( controlWrapper ).getImageId() ); } catch ( error ) { states.renderError( error ); } } } export default OptimizationControl;
| ver. 1.4 |
Github
|
.
| PHP 7.4.3-4ubuntu2.24 | Генерация страницы: 0.01 |
proxy
|
phpinfo
|
Настройка