diff --git a/docs/prd/conversion-engine.md b/docs/prd/conversion-engine.md index 5280fa9..e5de496 100644 --- a/docs/prd/conversion-engine.md +++ b/docs/prd/conversion-engine.md @@ -20,6 +20,7 @@ They do not know whether the implementation is image canvas, mediabunny video, S - Always return a Converter; never throw for unknown types - Reconstruct cross-realm `File` objects when `instanceof File` fails (iframes) - Images: ImageConverter when MIME supported; prefer WebP when the browser can encode it; otherwise keep source-friendly format +- When `skipWebpOptimization` is enabled, `image/webp` returns NullConverter so Cimo does not re-compress or resize it - Non-images: `applyFilters( 'cimo.getFileConverter', null, file )` then NullConverter - Settings read from `window.cimoSettings` at construction (quality, max dimension vs WP scaling threshold, smart flag) diff --git a/docs/prd/settings-and-freemium-ux.md b/docs/prd/settings-and-freemium-ux.md index c10b3dc..97a7255 100644 --- a/docs/prd/settings-and-freemium-ux.md +++ b/docs/prd/settings-and-freemium-ux.md @@ -23,6 +23,7 @@ The settings page shell, `cimo_options` schema surfacing, free vs premium contro - Disable WP big-image scaling - Thumbnail generation / per-size toggles - WebP quality +- Skip WebP optimization, including Cimo resizing - Max image dimension - Stats dashboard consumption diff --git a/e2e/test-utils/settings.ts b/e2e/test-utils/settings.ts index 44f5c19..300ee35 100644 --- a/e2e/test-utils/settings.ts +++ b/e2e/test-utils/settings.ts @@ -6,6 +6,7 @@ import { waitForCimoReady } from './media' export type CimoOptions = { webp_quality?: number; + skip_webp_optimization?: number; max_image_dimension?: number; disable_wp_scaling?: number; smart_optimization?: number; diff --git a/e2e/tests/quality-and-webp.spec.ts b/e2e/tests/quality-and-webp.spec.ts index e44e801..523424e 100644 --- a/e2e/tests/quality-and-webp.spec.ts +++ b/e2e/tests/quality-and-webp.spec.ts @@ -22,6 +22,7 @@ test.describe( 'WebP quality and already-WebP uploads', () => { disable_wp_scaling: 1, disable_thumbnail_generation: 1, smart_optimization: 0, + skip_webp_optimization: 0, } ) } ) @@ -31,6 +32,7 @@ test.describe( 'WebP quality and already-WebP uploads', () => { webp_quality: 80, max_image_dimension: 0, disable_thumbnail_generation: 0, + skip_webp_optimization: 0, } ) } ) @@ -110,4 +112,30 @@ test.describe( 'WebP quality and already-WebP uploads', () => { // Re-encode may shrink or skip if larger; either way upload must succeed. expect( uploadedSize ).toBeLessThanOrEqual( originalSize * 1.25 ) } ) + + test( 'can leave an oversized WebP unchanged', async ( { + page, + requestUtils, + } ) => { + await saveCimoOptions( requestUtils, { + max_image_dimension: 800, + skip_webp_optimization: 1, + } ) + await reloadCimoRuntime( page ) + + const originalSize = fs.statSync( SAMPLE_LARGE_WEBP ).size + const media = await uploadSampleViaMediaNew( + page, + requestUtils, + SAMPLE_LARGE_WEBP, + 'image/webp', + { + expectedMime: 'image/webp', + urlPattern: /\.webp(\?|$)/i, + } + ) + + expect( media.media_details?.width ?? 0 ).toBeGreaterThan( 800 ) + expect( await getMediaFileByteLength( page, requestUtils, media.id ) ).toBe( originalSize ) + } ) } ) diff --git a/e2e/tests/settings.spec.ts b/e2e/tests/settings.spec.ts index 018f26f..396e284 100644 --- a/e2e/tests/settings.spec.ts +++ b/e2e/tests/settings.spec.ts @@ -20,6 +20,7 @@ test.describe( 'Cimo settings', () => { webp_quality: 80, max_image_dimension: 0, disable_wp_scaling: 1, + skip_webp_optimization: 0, } ) } ) @@ -29,6 +30,7 @@ test.describe( 'Cimo settings', () => { webp_quality: 80, max_image_dimension: 0, disable_wp_scaling: 1, + skip_webp_optimization: 0, } ) } ) @@ -43,6 +45,7 @@ test.describe( 'Cimo settings', () => { await expect( page.getByRole( 'heading', { name: 'General Settings' } ) ).toBeVisible() await expect( page.getByRole( 'heading', { name: 'Image Optimization Settings' } ) ).toBeVisible() await expect( page.locator( '.cimo-webp-quality-range-control' ) ).toBeVisible() + await expect( page.getByRole( 'checkbox', { name: 'Skip WebP Optimization' } ) ).not.toBeChecked() await expect( page.getByLabel( 'Maximum Image Dimension' ) ).toBeVisible() await expect( page.locator( '.cimo-save-button' ) ).toBeVisible() } ) @@ -63,12 +66,14 @@ test.describe( 'Cimo settings', () => { // UI save smoke: tweak max dimension and persist via Save Changes. await page.getByLabel( 'Maximum Image Dimension' ).fill( '640' ) + await page.getByRole( 'checkbox', { name: 'Skip WebP Optimization' } ).check() await saveSettingsUi( page ) await reloadCimoRuntime( page ) const settings = await getCimoSettings( page ) expect( Number( settings.webpQuality ) ).toBe( 55 ) expect( Number( settings.maxImageDimension ) ).toBe( 640 ) + expect( Number( settings.skipWebpOptimization ) ).toBe( 1 ) const media = await uploadSampleViaMediaNew( page, diff --git a/src/admin/class-admin.php b/src/admin/class-admin.php index 4a64a64..5e6e189 100644 --- a/src/admin/class-admin.php +++ b/src/admin/class-admin.php @@ -153,6 +153,9 @@ public function register_settings() { 'webp_quality' => [ 'type' => 'integer', ], + 'skip_webp_optimization' => [ + 'type' => 'integer', + ], 'max_image_dimension' => [ 'type' => 'integer', ], @@ -363,6 +366,11 @@ public function sanitize_options( $options ) { $sanitized['webp_quality'] = $quality > 0 ? max( 1, min( 100, $quality ) ) : 0; } + // Sanitize skip WebP optimization. + if ( isset( $options['skip_webp_optimization'] ) ) { + $sanitized['skip_webp_optimization'] = $options['skip_webp_optimization'] ? 1 : 0; + } + // Sanitize max image dimension if ( isset( $options['max_image_dimension'] ) ) { $dimension = absint( $options['max_image_dimension'] ); diff --git a/src/admin/class-script-loader.php b/src/admin/class-script-loader.php index 76e80ed..9fa7f38 100644 --- a/src/admin/class-script-loader.php +++ b/src/admin/class-script-loader.php @@ -101,6 +101,7 @@ public static function enqueue_cimo_assets() { 'smartOptimization' => CIMO_BUILD === 'premium' ? ( isset( $settings['smart_optimization'] ) ? (int) $settings['smart_optimization'] : 1 ) : 0, + 'skipWebpOptimization' => isset( $settings['skip_webp_optimization'] ) ? (int) $settings['skip_webp_optimization'] : 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, diff --git a/src/admin/js/page/admin-settings.js b/src/admin/js/page/admin-settings.js index eb18518..496c205 100644 --- a/src/admin/js/page/admin-settings.js +++ b/src/admin/js/page/admin-settings.js @@ -32,6 +32,7 @@ const AdminSettings = () => { // Image optimization settings smartOptimization: 1, webpQuality: 80, + skipWebpOptimization: 0, maxImageDimension: '', // LQIP settings @@ -96,6 +97,7 @@ const AdminSettings = () => { // Image Optimization settings smartOptimization: cimoOptions.smart_optimization !== undefined ? cimoOptions.smart_optimization : 1, webpQuality: cimoOptions.webp_quality !== undefined ? cimoOptions.webp_quality : 80, + skipWebpOptimization: cimoOptions.skip_webp_optimization !== undefined ? cimoOptions.skip_webp_optimization : 0, maxImageDimension: cimoOptions.max_image_dimension || '', // LQIP settings @@ -192,6 +194,7 @@ const AdminSettings = () => { ...settings, smartOptimization: 1, webpQuality: 80, + skipWebpOptimization: 0, maxImageDimension: 1920, } } ) @@ -203,6 +206,7 @@ const AdminSettings = () => { ...settings, smartOptimization: 1, webpQuality: '', + skipWebpOptimization: 0, maxImageDimension: '', } } ) @@ -296,6 +300,7 @@ const AdminSettings = () => { // Image Optimization settings smart_optimization: settings.smartOptimization, webp_quality: parseInt( settings.webpQuality ) || 0, + skip_webp_optimization: settings.skipWebpOptimization, max_image_dimension: parseInt( settings.maxImageDimension ) || 0, // LQIP settings @@ -627,6 +632,17 @@ const AdminSettings = () => { ) } + { /* Skip WebP Optimization */ } +