From 82ee1fb67a8e800faded797067d86fb5c1500c19 Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Tue, 31 Mar 2026 10:40:28 +0800 Subject: [PATCH 1/7] feat: add smart optimization to image instant optimization --- src/admin/js/media-manager/drop-zone.js | 2 +- src/admin/js/media-manager/select-files.js | 2 +- src/shared/converters/converter-abstract.js | 9 ++++++++ src/shared/converters/image-converter.js | 25 ++++++++++++++------- src/shared/converters/index.js | 12 ++++++---- 5 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/admin/js/media-manager/drop-zone.js b/src/admin/js/media-manager/drop-zone.js index 4d28f89..da99389 100644 --- a/src/admin/js/media-manager/drop-zone.js +++ b/src/admin/js/media-manager/drop-zone.js @@ -109,7 +109,7 @@ function addDropZoneListenerToMediaManager( targetDocument ) { const optimizedResults = await Promise.all( fileConverters.map( async converter => { try { - const result = await converter.convert() + const result = await converter.optimize() if ( result.error ) { // eslint-disable-next-line no-console console.warn( result.error ) diff --git a/src/admin/js/media-manager/select-files.js b/src/admin/js/media-manager/select-files.js index e941600..5860907 100644 --- a/src/admin/js/media-manager/select-files.js +++ b/src/admin/js/media-manager/select-files.js @@ -89,7 +89,7 @@ function addSelectFilesListenerToFileUploads( targetDocument ) { const optimizedResults = await Promise.all( fileConverters.map( async converter => { try { - const result = await converter.convert() + const result = await converter.optimize() if ( result.error ) { // eslint-disable-next-line no-console console.warn( result.error ) diff --git a/src/shared/converters/converter-abstract.js b/src/shared/converters/converter-abstract.js index 30cd7ca..88f2d88 100644 --- a/src/shared/converters/converter-abstract.js +++ b/src/shared/converters/converter-abstract.js @@ -96,6 +96,15 @@ class Converter { throw new Error( 'convert() must be implemented by subclass' ) } + /** + * Perform smart optimization. + * If a subclass has not implemented this method, perform regular conversion. + * @return {Promise<{file: File|Blob, metadata?: Object}>} Promise resolving with the converted file and optional metadata. + */ + async optimize() { + return await this.convert() + } + /** * Cancel the current conversion. * Subclasses should override this to implement actual cancellation logic. diff --git a/src/shared/converters/image-converter.js b/src/shared/converters/image-converter.js index 13a838f..c89bf3a 100644 --- a/src/shared/converters/image-converter.js +++ b/src/shared/converters/image-converter.js @@ -241,13 +241,13 @@ class ImageConverter extends Converter { // Skip if already the desired format const formatInfo = supportedFormats.find( f => f.value === formatTo ) - if ( ! force && formatInfo && file.type === formatInfo.mimeType ) { - return { - file, - metadata: null, - reason: 'same-format', - } - } + // if ( ! force && formatInfo && file.type === formatInfo.mimeType ) { + // return { + // file, + // metadata: null, + // reason: 'same-format', + // } + // } // Check if the browser supports the desired output format const testCanvas = document.createElement( 'canvas' ) @@ -359,7 +359,16 @@ class ImageConverter extends Converter { } async optimize() { - return await applyFiltersAsync( 'cimo.imageConverter.optimize', this ) + let result = await applyFiltersAsync( 'cimo.imageConverter.optimize', { + file: this.file, + metadata: null, + reason: 'no-optimizer', + }, this ) + + if ( result.reason === 'no-optimizer' || ! result.metadata ) { + result = await this.convert() + } + return result } } diff --git a/src/shared/converters/index.js b/src/shared/converters/index.js index 7a58c50..76bfef5 100644 --- a/src/shared/converters/index.js +++ b/src/shared/converters/index.js @@ -38,15 +38,19 @@ export const getFileConverter = _file => { } if ( file.type.startsWith( 'image/' ) ) { - // If the browser doesn't support webp, then we can't convert it. - if ( ! isFormatSupported( 'webp' ) ) { - return new NullConverter( file ) + let format = 'webp' + + // If webp is not supported, use the same format of the file. + if ( ! isFormatSupported( format ) ) { + format = file.type } + if ( ImageConverter.supportsMimeType( file.type ) ) { return new ImageConverter( file, { - format: 'webp', + format, quality: window.cimoSettings?.webpQuality || 0.8, maxDimension: window.cimoSettings?.maxImageDimension || 0, + initialQuality: 1, // Initial quality for smart optimization. } ) } } From de8cfe00e0255029d85c2d580b0ea7f11c3b746c Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Mon, 6 Apr 2026 13:37:19 +0800 Subject: [PATCH 2/7] fix: remove force arg since we now allow same format conversion for safari --- src/shared/converters/image-converter.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/shared/converters/image-converter.js b/src/shared/converters/image-converter.js index c89bf3a..68fddc0 100644 --- a/src/shared/converters/image-converter.js +++ b/src/shared/converters/image-converter.js @@ -215,11 +215,10 @@ class ImageConverter extends Converter { /** * Convert an image file to the desired format and options. - * @param {boolean} [force=false] - Force conversion even if the file is already in the desired format. - * @param {Object} [options] - Options for the conversion. + * @param {Object} [options] - Options for the conversion. * @return {Promise<{file: File|Blob, metadata?: Object}>} Promise resolving to the converted file and optional metadata. */ - async convert( force = false, options = {} ) { + async convert( options = {} ) { const file = this.file const { quality = this.options?.quality || 0.8, @@ -239,15 +238,7 @@ class ImageConverter extends Converter { } } - // Skip if already the desired format const formatInfo = supportedFormats.find( f => f.value === formatTo ) - // if ( ! force && formatInfo && file.type === formatInfo.mimeType ) { - // return { - // file, - // metadata: null, - // reason: 'same-format', - // } - // } // Check if the browser supports the desired output format const testCanvas = document.createElement( 'canvas' ) From c9f9905588fa6496084bd0ffe302208100506245 Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Tue, 7 Apr 2026 10:31:31 +0800 Subject: [PATCH 3/7] fix: speedup smart optimization, add toggle in admin --- src/admin/class-admin.php | 8 +++++ src/admin/class-script-loader.php | 1 + src/admin/js/page/admin-settings.js | 45 +++++++++++++++++------- src/shared/converters/image-converter.js | 17 +++++---- src/shared/converters/index.js | 1 - 5 files changed, 53 insertions(+), 19 deletions(-) diff --git a/src/admin/class-admin.php b/src/admin/class-admin.php index 1ed71e3..fdebea5 100644 --- a/src/admin/class-admin.php +++ b/src/admin/class-admin.php @@ -97,6 +97,9 @@ public function register_settings() { ], // Image Optimization settings + 'smart_optimization' => [ + 'type' => 'integer', + ], 'webp_quality' => [ 'type' => 'integer', ], @@ -282,6 +285,11 @@ public function sanitize_options( $options ) { $sanitized['thumbnail_sizes'] = array_map( 'sanitize_text_field', $options['thumbnail_sizes'] ); } + // Sanitize smart optimization + if ( isset( $options['smart_optimization'] ) ) { + $sanitized['smart_optimization'] = $options['smart_optimization'] ? 1 : 0; + } + // Sanitize webp quality if ( isset( $options['webp_quality'] ) ) { $quality = absint( $options['webp_quality'] ); diff --git a/src/admin/class-script-loader.php b/src/admin/class-script-loader.php index c18d8ff..8d928cf 100644 --- a/src/admin/class-script-loader.php +++ b/src/admin/class-script-loader.php @@ -88,6 +88,7 @@ public static function enqueue_cimo_assets() { 'isLoggedIn' => is_user_logged_in(), 'optimizeAllMedia' => isset( $settings['optimize_all_media'] ) ? (int) $settings['optimize_all_media'] : 0, 'isPremium' => CIMO_BUILD === 'premium', + 'smartOptimization' => isset( $settings['smart_optimization'] ) ? (int) $settings['smart_optimization'] : 1, 'webpQuality' => ! empty( $settings['webp_quality'] ) ? (int) $settings['webp_quality'] : 80, 'maxImageDimension' => ! empty( $settings['max_image_dimension'] ) ? (int) $settings['max_image_dimension'] : 0, 'videoOptimizationEnabled' => isset( $settings['video_optimization_enabled'] ) ? (int) $settings['video_optimization_enabled'] : 1, diff --git a/src/admin/js/page/admin-settings.js b/src/admin/js/page/admin-settings.js index 8b1d506..e9979bf 100644 --- a/src/admin/js/page/admin-settings.js +++ b/src/admin/js/page/admin-settings.js @@ -23,6 +23,7 @@ const AdminSettings = () => { thumbnailSizes: [], // Stores DISABLED thumbnail sizes // Image optimization settings + smartOptimization: 1, webpQuality: 80, maxImageDimension: '', @@ -89,6 +90,7 @@ const AdminSettings = () => { thumbnailSizes: cimoOptions.thumbnail_sizes || [], // Image Optimization settings + smartOptimization: cimoOptions.smart_optimization !== undefined ? cimoOptions.smart_optimization : 1, webpQuality: cimoOptions.webp_quality !== undefined ? cimoOptions.webp_quality : 80, maxImageDimension: cimoOptions.max_image_dimension || '', @@ -178,6 +180,7 @@ const AdminSettings = () => { setSettings( settings => { return { ...settings, + smartOptimization: 1, webpQuality: 80, maxImageDimension: 1920, } @@ -188,6 +191,7 @@ const AdminSettings = () => { setSettings( settings => { return { ...settings, + smartOptimization: 1, webpQuality: '', maxImageDimension: '', } @@ -294,6 +298,7 @@ const AdminSettings = () => { thumbnail_sizes: settings.thumbnailSizes, // Image Optimization settings + smart_optimization: settings.smartOptimization, webp_quality: parseInt( settings.webpQuality ) || 0, max_image_dimension: parseInt( settings.maxImageDimension ) || 0, @@ -569,21 +574,37 @@ const AdminSettings = () => { + { /* Smart Optimization */ }
- handleInputChange( 'webpQuality', value || '' ) } - min="1" - max="100" - step="1" - __next40pxDefaultSize - allowReset - initialPosition={ 80 } - help={ __( 'Set the quality / compression level for generated .webp images. Default is 80%. Higher values mean better quality and larger file size; lower values reduce file size with more compression but lower quality.', 'cimo-image-optimizer' ) } + handleInputChange( 'smartOptimization', checked ? 1 : 0 ) } + help={ __( 'Automatically optimizes images to achieve the best possible quality while keeping file sizes as small as possible for faster performance.', 'cimo-image-optimizer' ) } />
+ + { /* WebP Image Quality */ } + { ( buildType === 'free' || settings.smartOptimization === 0 ) && ( +
+ handleInputChange( 'webpQuality', value || '' ) } + min="1" + max="100" + step="1" + __next40pxDefaultSize + allowReset + initialPosition={ 80 } + help={ __( 'Set the quality / compression level for generated .webp images. Default is 80%. Higher values mean better quality and larger file size; lower values reduce file size with more compression but lower quality.', 'cimo-image-optimizer' ) } + /> +
+ ) } + { /* Maximum Image Dimension */ }
{ format, quality: window.cimoSettings?.webpQuality || 0.8, maxDimension: window.cimoSettings?.maxImageDimension || 0, - initialQuality: 1, // Initial quality for smart optimization. } ) } } From eef9cfb110282feabef8163c1d6a0f82d435a21a Mon Sep 17 00:00:00 2001 From: bfintal Date: Tue, 7 Apr 2026 12:44:08 +0800 Subject: [PATCH 4/7] fix: updated admin setting --- src/admin/css/admin-page.css | 20 ++++++++++++++++++++ src/admin/js/page/admin-settings.js | 19 ++++++++++++++----- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/admin/css/admin-page.css b/src/admin/css/admin-page.css index 85bfdf5..c6a663b 100644 --- a/src/admin/css/admin-page.css +++ b/src/admin/css/admin-page.css @@ -647,6 +647,26 @@ order: 1; } +.cimo-smart-optimization-toggle { + order: 2; + + ~ .cimo-setting-field { + order: 3; + } + + ~ .cimo-webp-quality-range-control { + order: 1; + } + + ~ .cimo-reset-button { + order: 10; + } +} + +.cimo-is-premium .cimo-smart-optimization-toggle { + order: 1; +} + @keyframes cimo-bulk-optimizer-blink { 0%, 100% { opacity: 1; } diff --git a/src/admin/js/page/admin-settings.js b/src/admin/js/page/admin-settings.js index 405c120..06aefd6 100644 --- a/src/admin/js/page/admin-settings.js +++ b/src/admin/js/page/admin-settings.js @@ -576,20 +576,29 @@ const AdminSettings = () => {
{ /* Smart Optimization */ } -
+
+ { __( 'Smart Optimization', 'cimo-image-optimizer' ) } + { buildType === 'free' && ( + + { __( 'Premium', 'cimo-image-optimizer' ) } + + ) } + + } + checked={ buildType === 'free' ? false : ( settings.smartOptimization === 1 ) } disabled={ buildType === 'free' } onChange={ checked => handleInputChange( 'smartOptimization', checked ? 1 : 0 ) } - help={ __( 'Automatically optimizes images to achieve the best possible quality while keeping file sizes as small as possible for faster performance.', 'cimo-image-optimizer' ) } + help={ __( 'Smart Optimization uses our advanced algorithms to choose the best compression and quality settings per image. This adds a small overhead to the upload process, but the results are even smaller file sizes and faster loading times.', 'cimo-image-optimizer' ) } />
{ /* WebP Image Quality */ } { ( buildType === 'free' || settings.smartOptimization === 0 ) && ( -
+
Date: Tue, 7 Apr 2026 12:50:23 +0800 Subject: [PATCH 5/7] fix: updated label when smart optimized --- src/admin/class-meta-box.php | 19 ++++++++++++++----- src/admin/class-metadata.php | 7 +++++++ src/admin/js/media-manager/sidebar-info.js | 16 +++++++++++++++- src/shared/converters/image-converter.js | 8 ++++++++ 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/admin/class-meta-box.php b/src/admin/class-meta-box.php index ef40c66..74caa50 100644 --- a/src/admin/class-meta-box.php +++ b/src/admin/class-meta-box.php @@ -185,11 +185,20 @@ function cimo_get_media_type_label( $mimetype ) { // Converted format echo '
  • '; - echo '🏞️ ' . sprintf( - /* translators: %s: converted format */ - esc_html__( 'Converted to %s', 'cimo-image-optimizer' ), - '' . esc_html( $converted_format ) . '' - ); + $converted_format_markup = '' . esc_html( $converted_format ) . ''; + if ( ! empty( $cimo['smartOptimized'] ) ) { + echo '🏞️ ' . sprintf( + /* translators: %s: converted format */ + esc_html__( 'Smart optimized to %s', 'cimo-image-optimizer' ), + $converted_format_markup + ); + } else { + echo '🏞️ ' . sprintf( + /* translators: %s: converted format */ + esc_html__( 'Converted to %s', 'cimo-image-optimizer' ), + $converted_format_markup + ); + } echo '
  • '; // Conversion time diff --git a/src/admin/class-metadata.php b/src/admin/class-metadata.php index da6932a..bb96be0 100644 --- a/src/admin/class-metadata.php +++ b/src/admin/class-metadata.php @@ -46,6 +46,7 @@ public function register_rest_route() { 'convertedFilesize', 'conversionTime', 'compressionSavings', + 'smartOptimized', ]; if ( ! is_array( $value ) ) { // translators: The %s is the parameter name. @@ -85,6 +86,7 @@ public function register_rest_route() { 'convertedFilesize', 'conversionTime', 'compressionSavings', + 'smartOptimized', ]; $sanitized = []; if ( is_array( $value ) ) { @@ -98,6 +100,8 @@ public function register_rest_route() { $entry[ $key ] = intval( $item[ $key ] ); } elseif ( in_array( $key, [ 'conversionTime', 'compressionSavings' ], true ) ) { $entry[ $key ] = floatval( $item[ $key ] ); + } elseif ( $key === 'smartOptimized' ) { + $entry[ $key ] = ! empty( $item[ $key ] ) ? 1 : 0; } else { $entry[ $key ] = sanitize_text_field( $item[ $key ] ); } @@ -137,6 +141,7 @@ public function save_metadata( $request ) { 'convertedFilesize', 'conversionTime', 'compressionSavings', + 'smartOptimized', ]; $sanitized_metadata = []; foreach ( $metadata_array as $item ) { @@ -149,6 +154,8 @@ public function save_metadata( $request ) { $entry[ $key ] = intval( $item[ $key ] ); } elseif ( in_array( $key, [ 'conversionTime', 'compressionSavings' ], true ) ) { $entry[ $key ] = floatval( $item[ $key ] ); + } elseif ( $key === 'smartOptimized' ) { + $entry[ $key ] = ! empty( $item[ $key ] ) ? 1 : 0; } else { $entry[ $key ] = sanitize_text_field( $item[ $key ] ); } diff --git a/src/admin/js/media-manager/sidebar-info.js b/src/admin/js/media-manager/sidebar-info.js index 65ca224..0d7d6bf 100644 --- a/src/admin/js/media-manager/sidebar-info.js +++ b/src/admin/js/media-manager/sidebar-info.js @@ -194,9 +194,23 @@ function injectCimoMetadata( { } if ( ! isBulkOptimized ) { + const formatLabel = convertMimetypeToFormat( customMetadata.convertedFormat ) + const convertedFormatSpan = `${ escape( formatLabel ) }` + const convertedLineText = customMetadata.smartOptimized + ? sprintf( + /* translators: %s: image format name (e.g. WebP) */ + __( 'Smart optimized to %s', 'cimo-image-optimizer' ), + convertedFormatSpan + ) + : sprintf( + /* translators: %s: image format name (e.g. WebP) */ + __( 'Converted to %s', 'cimo-image-optimizer' ), + convertedFormatSpan + ) + html += `
  • - 🏞️ Converted to ${ escape( convertMimetypeToFormat( customMetadata.convertedFormat ) ) } + 🏞️ ${ convertedLineText }
  • ` diff --git a/src/shared/converters/image-converter.js b/src/shared/converters/image-converter.js index e8f2660..aa5b28c 100644 --- a/src/shared/converters/image-converter.js +++ b/src/shared/converters/image-converter.js @@ -363,6 +363,14 @@ class ImageConverter extends Converter { if ( ! result || result.reason === 'no-optimizer' ) { result = await this.convert() + } else if ( result.metadata && typeof result.metadata === 'object' ) { + result = { + ...result, + metadata: { + ...result.metadata, + smartOptimized: 1, + }, + } } return result } From 89c1cd781ce87620da4f1624d36ab6ac0ea0a284 Mon Sep 17 00:00:00 2001 From: bfintal Date: Tue, 7 Apr 2026 12:53:08 +0800 Subject: [PATCH 6/7] fix: smart optimization should be false if on free --- src/shared/converters/image-converter.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/shared/converters/image-converter.js b/src/shared/converters/image-converter.js index aa5b28c..04d5839 100644 --- a/src/shared/converters/image-converter.js +++ b/src/shared/converters/image-converter.js @@ -350,7 +350,9 @@ class ImageConverter extends Converter { } async optimize() { - const smartOptimization = String( window.cimoSettings?.smartOptimization ?? '1' ) !== '0' + const smartOptimization = + Boolean( window.cimoSettings?.isPremium ) && + String( window.cimoSettings?.smartOptimization ?? '1' ) !== '0' let result = null if ( smartOptimization ) { From 769e1d4ccc0edce5505e12f69519b8e077d0ca22 Mon Sep 17 00:00:00 2001 From: bfintal Date: Tue, 7 Apr 2026 14:13:24 +0800 Subject: [PATCH 7/7] fix: smart optimization is off if free --- src/admin/class-script-loader.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/admin/class-script-loader.php b/src/admin/class-script-loader.php index db3b7e5..499c425 100644 --- a/src/admin/class-script-loader.php +++ b/src/admin/class-script-loader.php @@ -90,7 +90,9 @@ public static function enqueue_cimo_assets() { 'canManageOptions' => current_user_can( 'manage_options' ), 'optimizeAllMedia' => isset( $settings['optimize_all_media'] ) ? (int) $settings['optimize_all_media'] : 0, 'isPremium' => CIMO_BUILD === 'premium', - 'smartOptimization' => isset( $settings['smart_optimization'] ) ? (int) $settings['smart_optimization'] : 1, + 'smartOptimization' => CIMO_BUILD === 'premium' + ? ( isset( $settings['smart_optimization'] ) ? (int) $settings['smart_optimization'] : 1 ) + : 0, 'webpQuality' => ! empty( $settings['webp_quality'] ) ? (int) $settings['webp_quality'] : 80, 'maxImageDimension' => ! empty( $settings['max_image_dimension'] ) ? (int) $settings['max_image_dimension'] : 0, 'videoOptimizationEnabled' => isset( $settings['video_optimization_enabled'] ) ? (int) $settings['video_optimization_enabled'] : 1,