From a86d39214b0d1c63bf3e31d6cfdfc28bcc2136ab Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Thu, 16 Jul 2026 09:14:16 -0700 Subject: [PATCH] Remove HEIC conversion module in favor of WordPress 7.1 core support Core now registers HEIC MIME types, detects the format including magic bytes, maps HEIC to JPEG output, and converts client-side via the vips pipeline or a canvas fallback. The heic2any/CDN module duplicated all of that and hooked a client_side_supported_mime_types filter that no longer exists. The remaining gap (core not setting the __heicUploadSupport flag that gates Safari's canvas fallback) is being addressed directly in core, so the plugin ships no HEIC handling at all. --- .gitignore | 1 + README.md | 41 +-- client-side-media-experiments.php | 3 +- includes/heic-support.php | 194 --------------- includes/settings.php | 35 --- js/heic-support.js | 350 -------------------------- readme.txt | 42 ++-- tests/Test_Heic_Support.php | 399 ------------------------------ tests/bootstrap.php | 1 - 9 files changed, 27 insertions(+), 1039 deletions(-) delete mode 100644 includes/heic-support.php delete mode 100644 js/heic-support.js delete mode 100644 tests/Test_Heic_Support.php diff --git a/.gitignore b/.gitignore index 04aa3c6..30c85e2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ composer.lock .phpunit.cache/ .phpcs-cache dist/ +.phpunit.result.cache diff --git a/README.md b/README.md index cf69d2f..31472b0 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # Client-Side Media Experiments -Enables client-side media processing on Firefox and Safari via COEP/COOP cross-origin isolation headers, and adds HEIC/HEIF upload support with automatic client-side conversion to JPEG. +Enables client-side media processing on Firefox and Safari via COEP/COOP cross-origin isolation headers. ## Features ### Cross-Origin Isolation (COEP/COOP) -WordPress 7.0 and Gutenberg include client-side media processing powered by WebAssembly (wasm-vips). This requires cross-origin isolation, which is achieved via Document-Isolation-Policy (DIP) on Chrome 137+. +WordPress 7.1 and Gutenberg include client-side media processing powered by WebAssembly (wasm-vips). This requires cross-origin isolation, which is achieved via Document-Isolation-Policy (DIP) on Chrome 137+. However, Firefox and Safari do not yet support DIP, so client-side media processing is disabled on those browsers. @@ -17,50 +17,29 @@ This plugin restores support by sending the older COEP/COOP headers on browsers - Adds `credentialless` attribute to iframes so they continue working under COEP. - Filters embed previews for providers that do not support credentialless iframes (Facebook, SmugMug). -### HEIC/HEIF Upload Support +### HEIC uploads -HEIC support was removed from WordPress core's wasm-vips build due to LGPL/GPL license incompatibility. This plugin re-enables HEIC upload support by converting images client-side: - -- **Enabled by default** — can be toggled under **Settings → Media**. -- HEIC/HEIF images are automatically converted to JPEG in the browser before upload. -- Uses the [heic2any](https://github.com/alexcorvi/heic2any) library, loaded dynamically from an external CDN only when a HEIC file is detected. -- heic2any uses [libheif](https://github.com/strukturag/libheif) (LGPL-3.0) for decoding. Since it is loaded at runtime from a CDN rather than bundled with the plugin, it is treated as a separate work and does not affect the plugin's GPL-2.0-or-later license. +WordPress 7.1 handles HEIC/HEIF uploads in core: MIME types, file type detection, HEIC-to-JPEG output mapping, and client-side conversion via the vips WASM pipeline or a canvas-based fallback for browsers with native HEVC decoding (such as Safari). This plugin no longer ships any HEIC handling of its own. ## Requirements -- WordPress 6.8+ with the Gutenberg plugin, or WordPress 7.0+. -- The client-side media processing feature must be enabled. +- WordPress 6.8+ with the Gutenberg plugin, or WordPress 7.1+. +- The client-side media processing feature must be enabled (it is on by default in secure contexts). ## Installation 1. Upload the `client-side-media-experiments` folder to `/wp-content/plugins/`. 2. Activate the plugin through the **Plugins** menu in WordPress. -3. The COEP/COOP headers activate automatically on browsers that need them. HEIC support is enabled by default. +3. The COEP/COOP headers activate automatically on browsers that need them. ## Configuration ### Disable COEP/COOP headers -```php -add_filter( 'csme_use_coep_coop', '__return_false' ); -``` - -### Disable HEIC support - -Navigate to **Settings → Media** and uncheck **HEIC Support**, or programmatically: +Navigate to **Settings → Media** and uncheck **Enable**, or programmatically: ```php -update_option( 'csme_heic_enabled', 0 ); -``` - -### Self-host the HEIC conversion library - -Use the `csme_heic_library_url` filter to point to your own hosted copy of heic2any: - -```php -add_filter( 'csme_heic_library_url', function () { - return 'https://example.com/heic2any.min.js'; -} ); +add_filter( 'csme_use_coep_coop', '__return_false' ); ``` ## Development @@ -78,5 +57,3 @@ This produces `dist/client-side-media-experiments.zip` containing only the runti ## License This plugin is licensed under [GPL-2.0-or-later](https://www.gnu.org/licenses/gpl-2.0.html). - -The HEIC conversion library ([heic2any](https://github.com/alexcorvi/heic2any) / [libheif](https://github.com/strukturag/libheif)) is LGPL-3.0 licensed and loaded at runtime from an external CDN as a separate work. diff --git a/client-side-media-experiments.php b/client-side-media-experiments.php index 7a104c5..7f656c8 100644 --- a/client-side-media-experiments.php +++ b/client-side-media-experiments.php @@ -2,7 +2,7 @@ /** * Plugin Name: Client-Side Media Experiments * Plugin URI: https://github.com/adamsilverstein/client-side-media-experiments - * Description: Enables client-side media processing on Firefox and Safari via COEP/COOP cross-origin isolation headers, and adds HEIC/HEIF upload support with client-side conversion. + * Description: Enables client-side media processing on Firefox and Safari via COEP/COOP cross-origin isolation headers. * Version: 0.2.0 * Requires at least: 6.8 * Requires PHP: 7.4 @@ -46,6 +46,5 @@ function csme_init() { require_once CSME_PLUGIN_DIR . 'includes/settings.php'; require_once CSME_PLUGIN_DIR . 'includes/cross-origin-isolation.php'; - require_once CSME_PLUGIN_DIR . 'includes/heic-support.php'; } add_action( 'plugins_loaded', 'csme_init' ); diff --git a/includes/heic-support.php b/includes/heic-support.php deleted file mode 100644 index c3f4f8d..0000000 --- a/includes/heic-support.php +++ /dev/null @@ -1,194 +0,0 @@ - esc_url( $cdn_url ), - 'cdnIntegrity' => sanitize_text_field( $cdn_integrity ), - 'jpegQuality' => $jpeg_quality, - ) - ); -} -add_action( 'admin_enqueue_scripts', 'csme_enqueue_heic_scripts' ); diff --git a/includes/settings.php b/includes/settings.php index b154192..d1b42bd 100644 --- a/includes/settings.php +++ b/includes/settings.php @@ -44,24 +44,6 @@ function csme_register_settings() { 'media', 'csme_settings_section' ); - - register_setting( - 'media', - 'csme_heic_enabled', - array( - 'type' => 'integer', - 'default' => 1, - 'sanitize_callback' => 'csme_sanitize_enabled', - ) - ); - - add_settings_field( - 'csme_heic_enabled_field', - __( 'HEIC Support', 'client-side-media-experiments' ), - 'csme_heic_enabled_field_callback', - 'media', - 'csme_settings_section' - ); } add_action( 'admin_init', 'csme_register_settings' ); @@ -96,23 +78,6 @@ function csme_enabled_field_callback() { - - -

- -

- } Resolves true if native HEIC is supported. - */ - function checkNativeHeicSupport() { - if ( nativeHeicSupport !== null ) { - return Promise.resolve( nativeHeicSupport ); - } - - if ( typeof createImageBitmap === 'undefined' ) { - nativeHeicSupport = false; - return Promise.resolve( false ); - } - - return new Promise( function ( resolve ) { - var blob = new Blob( - [ - new Uint8Array( [ - 0x00, 0x00, 0x00, 0x24, 0x66, 0x74, 0x79, 0x70, - 0x68, 0x65, 0x69, 0x63, 0x00, 0x00, 0x00, 0x00, - 0x68, 0x65, 0x69, 0x63, 0x68, 0x65, 0x69, 0x78, - 0x6d, 0x69, 0x66, 0x31, 0x4d, 0x69, 0x48, 0x45, - 0x68, 0x65, 0x76, 0x63, - ] ), - ], - { type: 'image/heic' } - ); - createImageBitmap( blob ) - .then( function () { - nativeHeicSupport = true; - resolve( true ); - } ) - .catch( function () { - nativeHeicSupport = false; - resolve( false ); - } ); - } ); - } - - /** - * Shows an info notice while HEIC conversion is in progress. - */ - function showConversionNotice() { - try { - wp.data - .dispatch( 'core/notices' ) - .createInfoNotice( 'Converting HEIC image(s) to JPEG\u2026', { - id: NOTICE_ID, - isDismissible: false, - } ); - } catch ( e ) { - // Notices store may not be available. - } - } - - /** - * Removes the HEIC conversion notice. - */ - function removeConversionNotice() { - try { - wp.data.dispatch( 'core/notices' ).removeNotice( NOTICE_ID ); - } catch ( e ) { - // Notices store may not be available. - } - } - - /** - * Checks whether a file is HEIC or HEIF format. - * - * @param {File} file The file to check. - * @return {boolean} True if the file is HEIC/HEIF. - */ - function isHeicFile( file ) { - if ( file.type === 'image/heic' || file.type === 'image/heif' ) { - return true; - } - // Some browsers don't set the MIME type for HEIC files. - var name = file.name || ''; - return /\.heic$/i.test( name ) || /\.heif$/i.test( name ); - } - - /** - * Dynamically loads the heic2any library from CDN. - * - * @return {Promise} Resolves with the heic2any function. - */ - function loadHeic2Any() { - if ( heic2anyPromise ) { - return heic2anyPromise; - } - - heic2anyPromise = new Promise( function ( resolve, reject ) { - var script = document.createElement( 'script' ); - script.src = CDN_URL; - if ( CDN_INTEGRITY ) { - script.integrity = CDN_INTEGRITY; - script.crossOrigin = 'anonymous'; - } - script.onload = function () { - if ( window.heic2any ) { - resolve( window.heic2any ); - } else { - reject( - new Error( - 'heic2any was not available after loading the script. ' + - 'The CDN may be serving an unexpected response.' - ) - ); - } - }; - script.onerror = function () { - heic2anyPromise = null; - reject( - new Error( - 'Failed to load HEIC conversion library from CDN. ' + - 'Check your network connection or ask your site ' + - 'administrator to host the library locally using ' + - 'the csme_heic_library_url filter.' - ) - ); - }; - document.head.appendChild( script ); - } ); - - return heic2anyPromise; - } - - /** - * Converts a single HEIC/HEIF file to JPEG. - * - * @param {File} file The HEIC file to convert. - * @return {Promise} Resolves with the converted JPEG file. - */ - function convertHeicToJpeg( file ) { - return loadHeic2Any() - .then( function ( heic2any ) { - return heic2any( { - blob: file, - toType: 'image/jpeg', - quality: JPEG_QUALITY, - } ); - } ) - .then( function ( result ) { - var blob = Array.isArray( result ) ? result[ 0 ] : result; - var newName = file.name - .replace( /\.heic$/i, '.jpg' ) - .replace( /\.heif$/i, '.jpg' ); - return new File( [ blob ], newName, { - type: 'image/jpeg', - lastModified: file.lastModified, - } ); - } ); - } - - /** - * Replaces the main attached file with the original HEIC. - * - * After the JPEG version is uploaded and subsizes are generated, - * this uses the sideload endpoint with replace_file=true to swap - * the main file to the original HEIC and delete the orphaned JPEG. - * - * @param {File} heicFile The original HEIC file. - * @param {number} attachmentId The attachment ID to update. - */ - function replaceWithOriginalHeic( heicFile, attachmentId ) { - var formData = new FormData(); - formData.append( 'file', heicFile ); - formData.append( 'image_size', 'original' ); - formData.append( 'replace_file', 'true' ); - formData.append( 'convert_format', 'false' ); - - wp.apiFetch( { - path: '/wp/v2/media/' + attachmentId + '/sideload', - method: 'POST', - body: formData, - } ).catch( function ( error ) { - // eslint-disable-next-line no-console - console.warn( - 'Failed to replace main file with original HEIC:', - error - ); - } ); - } - - /** - * Wraps the upload-media store's addItems action to convert HEIC files. - * - * Converts HEIC to JPEG for upload and subsize generation, then - * sideloads the original HEIC back to the attachment so the raw - * file is preserved. This intercepts at the Redux action level - * rather than wrapping settings.mediaUpload, which is fragile - * because Gutenberg's provider overwrites it on re-renders. - */ - function wrapAddItems() { - var uploadStore; - try { - uploadStore = wp.data.dispatch( 'core/upload-media' ); - } catch ( e ) { - return false; - } - - if ( ! uploadStore || ! uploadStore.addItems ) { - return false; - } - - if ( uploadStore.addItems.__csmeHeicWrapped ) { - return true; - } - - var originalAddItems = uploadStore.addItems.bind( uploadStore ); - - uploadStore.addItems = function ( args ) { - var files = args.files || []; - var hasHeic = files.some( isHeicFile ); - - if ( ! hasHeic ) { - return originalAddItems( args ); - } - - // Skip conversion if the browser handles HEIC natively. - checkNativeHeicSupport().then( function ( supported ) { - if ( supported ) { - return originalAddItems( args ); - } - - showConversionNotice(); - - // Build a map of original HEIC files keyed by index. - var heicOriginals = {}; - var conversionPromises = files.map( function ( file, idx ) { - if ( ! isHeicFile( file ) ) { - return Promise.resolve( file ); - } - heicOriginals[ idx ] = file; - return convertHeicToJpeg( file ).catch( function ( error ) { - if ( args.onError ) { - args.onError( - new Error( - 'HEIC to JPEG conversion failed for "' + - file.name + '": ' + - ( error && error.message - ? error.message - : String( error ) ) - ) - ); - } - return null; - } ); - } ); - - Promise.all( conversionPromises ).then( - function ( convertedFiles ) { - removeConversionNotice(); - var successfulFiles = []; - var heicMap = {}; - - convertedFiles.forEach( function ( file, idx ) { - if ( file !== null ) { - var newIdx = successfulFiles.length; - successfulFiles.push( file ); - if ( heicOriginals[ idx ] ) { - heicMap[ newIdx ] = heicOriginals[ idx ]; - } - } - } ); - - if ( successfulFiles.length === 0 ) { - return; - } - - // Track which converted files had HEIC originals - // so we can sideload after upload completes. - var fileIndex = 0; - var originalOnSuccess = args.onSuccess; - var wrappedOnSuccess = function ( attachments ) { - // Sideload each original HEIC to its attachment. - attachments.forEach( function ( attachment ) { - if ( heicMap[ fileIndex ] && attachment.id ) { - replaceWithOriginalHeic( - heicMap[ fileIndex ], - attachment.id - ); - } - fileIndex++; - } ); - - if ( originalOnSuccess ) { - originalOnSuccess( attachments ); - } - }; - - originalAddItems( { - ...args, - files: successfulFiles, - onSuccess: wrappedOnSuccess, - } ); - } - ); - } ); - }; - - uploadStore.addItems.__csmeHeicWrapped = true; - return true; - } - - wp.domReady( function () { - if ( wrapAddItems() ) { - return; - } - - // Subscribe to store changes until upload-media store is available. - var unsubscribe = wp.data.subscribe( function () { - if ( wrapAddItems() ) { - unsubscribe(); - } - } ); - } ); -} )(); diff --git a/readme.txt b/readme.txt index fe0c7b6..6d2e9a7 100644 --- a/readme.txt +++ b/readme.txt @@ -2,17 +2,17 @@ Contributors: adamsilverstein Tags: media, performance, cross-origin, wasm Requires at least: 6.8 -Tested up to: 6.8 +Tested up to: 7.1 Stable tag: 0.2.0 Requires PHP: 7.4 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html -Enables client-side media processing on Firefox and Safari via COEP/COOP cross-origin isolation headers, and optional HEIC upload support. +Enables client-side media processing on Firefox and Safari via COEP/COOP cross-origin isolation headers. == Description == -WordPress 7.0 and Gutenberg include client-side media processing powered by WebAssembly (wasm-vips). This requires cross-origin isolation, which is achieved via Document-Isolation-Policy (DIP) on Chrome 137+. +WordPress 7.1 and Gutenberg include client-side media processing powered by WebAssembly (wasm-vips). This requires cross-origin isolation, which is achieved via Document-Isolation-Policy (DIP) on Chrome 137+. However, Firefox and Safari do not yet support DIP, so client-side media processing is disabled on those browsers. @@ -24,21 +24,15 @@ This plugin restores support for Firefox and Safari by sending the older COEP/CO * Adds `crossorigin="anonymous"` attributes to cross-origin resources. * Adds `credentialless` attribute to iframes so they continue working under COEP. * Filters embed previews for providers that do not support credentialless iframes (Facebook, SmugMug). -* **Optional HEIC support:** Enables uploading HEIC/HEIF images (e.g. from iPhones) by converting them to JPEG on the client side. -**HEIC Support:** +**HEIC uploads:** -HEIC support was removed from WordPress core due to patent restrictions that conflict with the GPL license. This plugin re-enables HEIC upload support (enabled by default, can be disabled under Settings > Media): - -* When enabled, HEIC/HEIF images are automatically converted to JPEG in the browser before upload. -* The conversion uses the [heic2any](https://github.com/alexcorvi/heic2any) library, which is loaded dynamically from an external CDN only when a HEIC file is detected. -* heic2any uses [libheif](https://github.com/strukturag/libheif) (LGPL-3.0 licensed) for decoding. Since the library is loaded at runtime from a CDN rather than bundled with the plugin, it is treated as a separate work and does not affect the plugin's GPL-2.0-or-later license. -* The CDN URL is filterable via the `csme_heic_library_url` filter for self-hosting or version changes. +WordPress 7.1 handles HEIC/HEIF uploads in core, including client-side conversion via the vips WASM pipeline or a canvas-based fallback for browsers with native HEVC decoding (such as Safari). This plugin no longer ships any HEIC handling of its own. **Requirements:** -* WordPress 6.8+ with the Gutenberg plugin (which provides the client-side media processing feature), or WordPress 7.0+. -* The client-side media processing feature must be enabled. +* WordPress 6.8+ with the Gutenberg plugin (which provides the client-side media processing feature), or WordPress 7.1+. +* The client-side media processing feature must be enabled (it is on by default in secure contexts). == Installation == @@ -58,26 +52,22 @@ The COEP/COOP headers are only sent on block editor admin pages, not on the fron = Can I disable the COEP/COOP behavior? = -Yes. Use the `csme_use_coep_coop` filter: +Yes. Uncheck **Enable** under Settings > Media, or use the `csme_use_coep_coop` filter: `add_filter( 'csme_use_coep_coop', '__return_false' );` -= How does HEIC support work? = - -When enabled in Settings > Media, the plugin converts HEIC/HEIF images to JPEG directly in the browser before uploading them to WordPress. The conversion library (heic2any) is loaded from an external CDN only when a HEIC file is detected, so there is no impact on normal uploads. - -= What about the HEIC license? = - -The heic2any library uses libheif (LGPL-3.0) for HEIC decoding. Since the library is loaded at runtime from a CDN and not bundled with the plugin, it does not affect the plugin's GPL-2.0-or-later license. HEIC support is enabled by default but can be disabled under Settings > Media. += How do HEIC uploads work? = -= Can I self-host the HEIC conversion library? = - -Yes. Use the `csme_heic_library_url` filter to point to your own hosted copy: - -`add_filter( 'csme_heic_library_url', function() { return 'https://example.com/heic2any.min.js'; } );` +WordPress 7.1 converts HEIC images client-side where possible and server-side otherwise. This plugin no longer includes any HEIC handling of its own. == Changelog == += Unreleased = +* Removed the HEIC conversion module (heic2any/CDN): WordPress 7.1 handles HEIC uploads in core. +* Fixed Chromium detection against WordPress 7.1 function names, so no COEP/COOP headers are sent where DIP applies. +* Deferred initialization to `plugins_loaded` so plugin activation order no longer matters. +* The `csme_enabled` setting default no longer depends on the browser saving the settings. + = 0.2.0 = * Added optional HEIC/HEIF upload support with client-side conversion to JPEG. * HEIC conversion library loaded dynamically from CDN (heic2any / libheif). diff --git a/tests/Test_Heic_Support.php b/tests/Test_Heic_Support.php deleted file mode 100644 index a25443d..0000000 --- a/tests/Test_Heic_Support.php +++ /dev/null @@ -1,399 +0,0 @@ -assertTrue( csme_is_heic_enabled() ); - } - - /** - * HEIC support can be enabled via option. - */ - public function test_heic_enabled_via_option() { - update_option( 'csme_heic_enabled', 1 ); - - $this->assertTrue( csme_is_heic_enabled() ); - } - - /** - * HEIC MIME types are not added when disabled. - */ - public function test_mime_types_not_added_when_disabled() { - update_option( 'csme_heic_enabled', 0 ); - - $mime_types = csme_add_heic_mime_types( array( 'jpg|jpeg|jpe' => 'image/jpeg' ) ); - - $this->assertArrayNotHasKey( 'heic', $mime_types ); - $this->assertArrayNotHasKey( 'heif', $mime_types ); - } - - /** - * HEIC MIME types are added when enabled. - */ - public function test_mime_types_added_when_enabled() { - update_option( 'csme_heic_enabled', 1 ); - - $mime_types = csme_add_heic_mime_types( array( 'jpg|jpeg|jpe' => 'image/jpeg' ) ); - - $this->assertArrayHasKey( 'heic', $mime_types ); - $this->assertArrayHasKey( 'heif', $mime_types ); - $this->assertSame( 'image/heic', $mime_types['heic'] ); - $this->assertSame( 'image/heif', $mime_types['heif'] ); - } - - /** - * Existing MIME types are preserved when HEIC types are added. - */ - public function test_existing_mime_types_preserved() { - update_option( 'csme_heic_enabled', 1 ); - - $mime_types = csme_add_heic_mime_types( array( 'jpg|jpeg|jpe' => 'image/jpeg' ) ); - - $this->assertArrayHasKey( 'jpg|jpeg|jpe', $mime_types ); - } - - /** - * File type detection returns early when HEIC is disabled. - */ - public function test_filetype_check_returns_early_when_disabled() { - update_option( 'csme_heic_enabled', 0 ); - - $data = array( - 'ext' => false, - 'type' => false, - 'proper_filename' => false, - ); - - $result = csme_heic_check_filetype( $data, '/tmp/test.heic', 'test.heic', array(), '' ); - - $this->assertFalse( $result['ext'] ); - $this->assertFalse( $result['type'] ); - } - - /** - * File type detection does not override already-detected types. - */ - public function test_filetype_check_does_not_override_existing() { - update_option( 'csme_heic_enabled', 1 ); - - $data = array( - 'ext' => 'jpg', - 'type' => 'image/jpeg', - 'proper_filename' => false, - ); - - $result = csme_heic_check_filetype( $data, '/tmp/test.jpg', 'test.jpg', array(), '' ); - - $this->assertSame( 'jpg', $result['ext'] ); - $this->assertSame( 'image/jpeg', $result['type'] ); - } - - /** - * File type detection correctly identifies HEIC files. - */ - public function test_filetype_check_detects_heic() { - update_option( 'csme_heic_enabled', 1 ); - - $data = array( - 'ext' => false, - 'type' => false, - 'proper_filename' => false, - ); - - $result = csme_heic_check_filetype( $data, '/tmp/test.heic', 'test.heic', array(), '' ); - - $this->assertSame( 'heic', $result['ext'] ); - $this->assertSame( 'image/heic', $result['type'] ); - } - - /** - * File type detection correctly identifies HEIF files. - */ - public function test_filetype_check_detects_heif() { - update_option( 'csme_heic_enabled', 1 ); - - $data = array( - 'ext' => false, - 'type' => false, - 'proper_filename' => false, - ); - - $result = csme_heic_check_filetype( $data, '/tmp/test.heif', 'test.heif', array(), '' ); - - $this->assertSame( 'heif', $result['ext'] ); - $this->assertSame( 'image/heif', $result['type'] ); - } - - /** - * Script is not enqueued when HEIC is disabled. - */ - public function test_script_not_enqueued_when_disabled() { - update_option( 'csme_heic_enabled', 0 ); - - csme_enqueue_heic_scripts( 'post.php' ); - - $this->assertFalse( wp_script_is( 'csme-heic-support', 'enqueued' ) ); - } - - /** - * Script is not enqueued on irrelevant admin pages. - */ - public function test_script_not_enqueued_on_irrelevant_pages() { - update_option( 'csme_heic_enabled', 1 ); - - csme_enqueue_heic_scripts( 'options-general.php' ); - - $this->assertFalse( wp_script_is( 'csme-heic-support', 'enqueued' ) ); - } - - /** - * Script is enqueued on post.php when HEIC is enabled. - */ - public function test_script_enqueued_on_post_php() { - update_option( 'csme_heic_enabled', 1 ); - - csme_enqueue_heic_scripts( 'post.php' ); - - $this->assertTrue( wp_script_is( 'csme-heic-support', 'enqueued' ) ); - } - - /** - * Script is enqueued on post-new.php when HEIC is enabled. - */ - public function test_script_enqueued_on_post_new_php() { - update_option( 'csme_heic_enabled', 1 ); - - csme_enqueue_heic_scripts( 'post-new.php' ); - - $this->assertTrue( wp_script_is( 'csme-heic-support', 'enqueued' ) ); - } - - /** - * Script is enqueued on site-editor.php when HEIC is enabled. - */ - public function test_script_enqueued_on_site_editor_php() { - update_option( 'csme_heic_enabled', 1 ); - - csme_enqueue_heic_scripts( 'site-editor.php' ); - - $this->assertTrue( wp_script_is( 'csme-heic-support', 'enqueued' ) ); - } - - /** - * Script is enqueued on widgets.php when HEIC is enabled. - */ - public function test_script_enqueued_on_widgets_php() { - update_option( 'csme_heic_enabled', 1 ); - - csme_enqueue_heic_scripts( 'widgets.php' ); - - $this->assertTrue( wp_script_is( 'csme-heic-support', 'enqueued' ) ); - } - - /** - * CDN URL can be filtered and is used in localized script data. - */ - public function test_heic_library_url_filterable() { - update_option( 'csme_heic_enabled', 1 ); - - add_filter( - 'csme_heic_library_url', - function () { - return 'https://example.com/heic2any.min.js'; - } - ); - - csme_enqueue_heic_scripts( 'post.php' ); - - $this->assertTrue( wp_script_is( 'csme-heic-support', 'enqueued' ) ); - - $data = wp_scripts()->get_data( 'csme-heic-support', 'data' ); - $this->assertIsString( $data ); - $this->assertStringContainsString( 'example.com', $data ); - $this->assertStringContainsString( 'heic2any.min.js', $data ); - } - - /** - * File type detection ignores non-HEIC files. - */ - public function test_filetype_check_ignores_non_heic() { - update_option( 'csme_heic_enabled', 1 ); - - $data = array( - 'ext' => false, - 'type' => false, - 'proper_filename' => false, - ); - - $result = csme_heic_check_filetype( $data, '/tmp/test.png', 'test.png', array(), '' ); - - $this->assertFalse( $result['ext'] ); - $this->assertFalse( $result['type'] ); - } - - /** - * The csme_heic_enabled setting is registered correctly. - */ - public function test_heic_setting_registered() { - csme_register_settings(); - - $registered = get_registered_settings(); - $this->assertArrayHasKey( 'csme_heic_enabled', $registered ); - $this->assertSame( 'integer', $registered['csme_heic_enabled']['type'] ); - $this->assertSame( 1, $registered['csme_heic_enabled']['default'] ); - } - - /** - * The sanitize callback normalizes values to 0 or 1. - */ - public function test_sanitize_enabled_callback() { - $this->assertSame( 1, csme_sanitize_enabled( 1 ) ); - $this->assertSame( 1, csme_sanitize_enabled( '1' ) ); - $this->assertSame( 1, csme_sanitize_enabled( 'yes' ) ); - $this->assertSame( 0, csme_sanitize_enabled( 0 ) ); - $this->assertSame( 0, csme_sanitize_enabled( '' ) ); - $this->assertSame( 0, csme_sanitize_enabled( null ) ); - } - - /** - * The HEIC enabled field callback renders a checked checkbox. - */ - public function test_heic_enabled_field_callback_checked() { - update_option( 'csme_heic_enabled', 1 ); - - ob_start(); - csme_heic_enabled_field_callback(); - $output = ob_get_clean(); - - $this->assertStringContainsString( 'name="csme_heic_enabled"', $output ); - $this->assertStringContainsString( 'type="checkbox"', $output ); - $this->assertStringContainsString( 'checked', $output ); - } - - /** - * The HEIC enabled field callback renders unchecked when disabled. - */ - public function test_heic_enabled_field_callback_unchecked() { - update_option( 'csme_heic_enabled', 0 ); - - ob_start(); - csme_heic_enabled_field_callback(); - $output = ob_get_clean(); - - $this->assertStringContainsString( 'name="csme_heic_enabled"', $output ); - // Match any checked attribute format: checked, checked='checked', checked="checked". - $this->assertDoesNotMatchRegularExpression( '/\schecked[\s=>\'"]/', $output ); - } - - /** - * The upload_mimes filter includes HEIC types when hooked. - */ - public function test_upload_mimes_filter_integration() { - update_option( 'csme_heic_enabled', 1 ); - - add_filter( 'upload_mimes', 'csme_add_heic_mime_types' ); - - $defaults = array( 'jpg|jpeg|jpe' => 'image/jpeg' ); - $mimes = apply_filters( 'upload_mimes', $defaults ); - - $this->assertArrayHasKey( 'heic', $mimes ); - $this->assertArrayHasKey( 'heif', $mimes ); - $this->assertArrayHasKey( 'jpg|jpeg|jpe', $mimes ); - } - - /** - * HEIC MIME types are added to client-side supported types when enabled. - */ - public function test_client_side_supported_mime_types_filter() { - update_option( 'csme_heic_enabled', 1 ); - - $defaults = array( 'image/jpeg', 'image/png' ); - $types = csme_add_heic_client_side_mime_types( $defaults ); - - $this->assertContains( 'image/heic', $types ); - $this->assertContains( 'image/heif', $types ); - $this->assertContains( 'image/jpeg', $types ); - } - - /** - * HEIC MIME types are not added to client-side types when disabled. - */ - public function test_client_side_supported_mime_types_disabled() { - update_option( 'csme_heic_enabled', 0 ); - - $defaults = array( 'image/jpeg', 'image/png' ); - $types = csme_add_heic_client_side_mime_types( $defaults ); - - $this->assertNotContains( 'image/heic', $types ); - $this->assertNotContains( 'image/heif', $types ); - } - - /** - * HEIC output format maps to JPEG when enabled. - */ - public function test_heic_output_format_filter() { - update_option( 'csme_heic_enabled', 1 ); - - $formats = csme_heic_output_format( array() ); - - $this->assertSame( 'image/jpeg', $formats['image/heic'] ); - $this->assertSame( 'image/jpeg', $formats['image/heif'] ); - } - - /** - * HEIC output format is not added when disabled. - */ - public function test_heic_output_format_filter_disabled() { - update_option( 'csme_heic_enabled', 0 ); - - $formats = csme_heic_output_format( array() ); - - $this->assertArrayNotHasKey( 'image/heic', $formats ); - $this->assertArrayNotHasKey( 'image/heif', $formats ); - } - - /** - * The csme_heic_library_url filter sanitizes javascript: URLs. - */ - public function test_malicious_library_url_sanitized() { - update_option( 'csme_heic_enabled', 1 ); - - add_filter( - 'csme_heic_library_url', - function () { - return 'javascript:alert(document.cookie)'; - } - ); - - csme_enqueue_heic_scripts( 'post.php' ); - - $this->assertTrue( wp_script_is( 'csme-heic-support', 'enqueued' ) ); - - $data = wp_scripts()->get_data( 'csme-heic-support', 'data' ); - $this->assertIsString( $data ); - $this->assertStringNotContainsString( 'javascript:', $data ); - } -} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 7abda29..b66d71f 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -38,7 +38,6 @@ function wp_is_client_side_media_processing_enabled() { function _manually_load_plugin() { require_once CSME_PLUGIN_DIR . 'includes/settings.php'; require_once CSME_PLUGIN_DIR . 'includes/cross-origin-isolation.php'; - require_once CSME_PLUGIN_DIR . 'includes/heic-support.php'; } tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );