Repository navigation
Add HEIC upload support via client-side conversion - #12
Conversation
- New includes/heic-support.php: MIME type registration, file type detection, and script enqueuing for HEIC support - New js/heic-support.js: Client-side HEIC to JPEG conversion using dynamically loaded heic2any library from CDN - Updated settings.php: Added HEIC support toggle in Settings > Media - Updated main plugin file to include heic-support.php - Updated test bootstrap to load new files - New tests/Test_Heic_Support.php with comprehensive unit tests - Updated readme.txt with HEIC documentation and license notes Co-authored-by: adamsilverstein <2676022+adamsilverstein@users.noreply.github.com>
Co-authored-by: adamsilverstein <2676022+adamsilverstein@users.noreply.github.com>
@copilot make it enabled by default |
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
|
Important Review skippedBot user detected. To trigger a single review, invoke the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR adds HEIC/HEIF image upload support to the plugin. It includes server-side MIME type registration and file type detection, a user-configurable settings option (enabled by default), client-side JavaScript for converting HEIC files to JPEG via a dynamically-loaded CDN library (heic2any), and comprehensive PHPUnit tests validating all functionality. Changes
Sequence DiagramsequenceDiagram
participant User
participant Editor as Block Editor
participant JS as heic-support.js
participant CDN as CDN (heic2any)
participant WP as WordPress Upload
participant PHP as PHP Filters
User->>Editor: Upload HEIC file
Editor->>JS: mediaUpload(files)
JS->>JS: Detect HEIC by MIME/filename
alt Is HEIC
JS->>CDN: Load heic2any library
CDN-->>JS: Library loaded
JS->>JS: Convert HEIC to JPEG
JS->>JS: Create new File object (image/jpeg)
else Not HEIC
JS->>JS: Pass through unchanged
end
JS->>WP: mediaUpload(converted files)
WP->>PHP: apply_filters('upload_mimes')
PHP-->>WP: Include heic/heif MIME types
WP->>PHP: apply_filters('wp_check_filetype_and_ext')
PHP-->>WP: Validate file type
WP->>User: Upload complete
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@copilot can we also update the project readme or docs to reflect this new feature? |
Co-authored-by: adamsilverstein <2676022+adamsilverstein@users.noreply.github.com>
Done in 908ec4f — changed the default for |
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
Co-authored-by: adamsilverstein <2676022+adamsilverstein@users.noreply.github.com>
Added a Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
client-side-media-experiments.php (1)
6-6:⚠️ Potential issue | 🟡 MinorUpdate plugin version to 0.2.0.
The plugin version in the header and the
CSME_VERSIONconstant (line 36) should be updated to0.2.0to reflect the new HEIC support feature added in this release.📝 Proposed fix for plugin header
- * Version: 0.1.0 + * Version: 0.2.0And for the constant at line 36:
-define( 'CSME_VERSION', '0.1.0' ); +define( 'CSME_VERSION', '0.2.0' );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@client-side-media-experiments.php` at line 6, Update the plugin version string in the file header and the CSME_VERSION constant to match the new release: change the header "Version:" value to 0.2.0 and update the CSME_VERSION constant (CSME_VERSION) from 0.1.0 to 0.2.0 so both the plugin metadata and the CSME_VERSION constant reflect the new HEIC support release.readme.txt (1)
6-6:⚠️ Potential issue | 🟡 MinorUpdate stable tag to match the new version.
The stable tag is still
0.1.0, but the changelog introduces version0.2.0with the HEIC support feature. This should be updated for consistency.📝 Proposed fix
-Stable tag: 0.1.0 +Stable tag: 0.2.0🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@readme.txt` at line 6, Update the stable tag entry in readme.txt from "Stable tag: 0.1.0" to "Stable tag: 0.2.0" so it matches the changelog/version that introduces HEIC support; locate the "Stable tag" line and replace the version string accordingly.
🧹 Nitpick comments (2)
js/heic-support.js (1)
125-147: Consider providing user feedback when conversion fails.If
convertHeicToJpegfails for one file in a batch, the entire upload is aborted andonErroris called. This is reasonable, but the error message from the library may be cryptic to users. Consider wrapping with a more descriptive message.💡 Optional: Improve error messaging
.catch( function ( error ) { if ( args.onError ) { - args.onError( error ); + args.onError( new Error( 'Failed to convert HEIC image: ' + error.message ) ); } } );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@js/heic-support.js` around lines 125 - 147, The Promise.all error path currently forwards the raw error from convertHeicToJpeg to args.onError, which can be cryptic; change the catch handler in the Promise.all block that wraps convertHeicToJpeg/files to create and pass a clearer error (e.g., new Error('HEIC → JPEG conversion failed for one or more files: ' + originalError.message) or include which file failed) before calling args.onError, so originalMediaUpload isn't invoked and callers get a descriptive message; keep using args.onError but wrap or augment the original error from convertHeicToJpeg to provide context.tests/Test_Heic_Support.php (1)
217-230: Consider verifying the filtered URL is actually used.The test confirms the script is enqueued when the filter is applied, but doesn't verify that the custom URL is passed to the localized script data. This would provide stronger assurance the filter works end-to-end.
💡 Optional: Verify the filtered URL in localized 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' ) ); + + // Verify the filtered URL is passed to the script. + global $wp_scripts; + $data = $wp_scripts->get_data( 'csme-heic-support', 'data' ); + $this->assertStringContainsString( 'https://example.com/heic2any.min.js', $data ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/Test_Heic_Support.php` around lines 217 - 230, Extend test_heic_library_url_filterable to assert that the filtered URL from the 'csme_heic_library_url' filter is actually used in the localized/script data after calling csme_enqueue_heic_scripts; retrieve the localized data for the 'csme-heic-support' handle (e.g. via wp_scripts()->get_data or the mechanism your test suite uses to inspect localized/script data) and assert it contains 'https://example.com/heic2any.min.js' so the filter is validated end-to-end.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@client-side-media-experiments.php`:
- Line 6: Update the plugin version string in the file header and the
CSME_VERSION constant to match the new release: change the header "Version:"
value to 0.2.0 and update the CSME_VERSION constant (CSME_VERSION) from 0.1.0 to
0.2.0 so both the plugin metadata and the CSME_VERSION constant reflect the new
HEIC support release.
In `@readme.txt`:
- Line 6: Update the stable tag entry in readme.txt from "Stable tag: 0.1.0" to
"Stable tag: 0.2.0" so it matches the changelog/version that introduces HEIC
support; locate the "Stable tag" line and replace the version string
accordingly.
---
Nitpick comments:
In `@js/heic-support.js`:
- Around line 125-147: The Promise.all error path currently forwards the raw
error from convertHeicToJpeg to args.onError, which can be cryptic; change the
catch handler in the Promise.all block that wraps convertHeicToJpeg/files to
create and pass a clearer error (e.g., new Error('HEIC → JPEG conversion failed
for one or more files: ' + originalError.message) or include which file failed)
before calling args.onError, so originalMediaUpload isn't invoked and callers
get a descriptive message; keep using args.onError but wrap or augment the
original error from convertHeicToJpeg to provide context.
In `@tests/Test_Heic_Support.php`:
- Around line 217-230: Extend test_heic_library_url_filterable to assert that
the filtered URL from the 'csme_heic_library_url' filter is actually used in the
localized/script data after calling csme_enqueue_heic_scripts; retrieve the
localized data for the 'csme-heic-support' handle (e.g. via
wp_scripts()->get_data or the mechanism your test suite uses to inspect
localized/script data) and assert it contains
'https://example.com/heic2any.min.js' so the filter is validated end-to-end.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 625c68bd-cd2c-4402-a008-965e0e38922e
📒 Files selected for processing (7)
client-side-media-experiments.phpincludes/heic-support.phpincludes/settings.phpjs/heic-support.jsreadme.txttests/Test_Heic_Support.phptests/bootstrap.php
|
@copilot please address the feedback from Code Rabbit: Verify each finding against the current code and only fix it if needed. Outside diff comments:
In
Nitpick comments:
In
|
…or messages, strengthen filter test Co-authored-by: adamsilverstein <2676022+adamsilverstein@users.noreply.github.com>
All three items addressed in 5c3cf66:
Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
The test bootstrap defined CSME_VERSION as 0.1.0 while the main plugin file was bumped to 0.2.0, causing a mismatch.
The dynamically loaded heic2any library had no Subresource Integrity check. A compromised CDN could serve arbitrary JS in the admin context. The sha384 hash is now passed from PHP and applied to the script element along with the crossOrigin attribute.
Previously Promise.all rejected the entire batch when a single HEIC conversion failed, losing non-HEIC files and successfully converted files. Each conversion now catches errors individually so the remaining files still upload.
Users had no indication that conversion was in progress, which could lead to duplicate uploads or confusion. A non-dismissible info notice now appears while converting and is removed once complete.
Safari and future browsers can decode HEIC natively, making the CDN library load and conversion unnecessary. A quick createImageBitmap probe now detects native support and passes HEIC files through directly when available.
The quality was hardcoded at 0.92 with no way to adjust it. Sites that prioritize file size can now use the csme_heic_jpeg_quality filter to lower the value. The PHP side clamps it to [0, 1] before passing it to the JS.
Reviewers flagged that csme_heic_check_filetype trusts the file extension without magic-byte inspection. The comment explains this is intentional: client-side JS converts HEIC to JPEG before upload so the server rarely sees .heic files.
The previous messages gave no remediation guidance. Users now see suggestions to check network connectivity or use the csme_heic_library_url filter for self-hosting.
The settings code in settings.php had zero test coverage. Tests now verify the setting is registered with the correct type and default, the sanitizer normalizes inputs, and the field callback renders checked/unchecked states.
The upload_mimes filter was only tested by calling the function directly, not through apply_filters. The new integration test verifies proper hookup. A security test confirms esc_url strips javascript: URLs from the CDN filter to prevent script injection.
wp_localize_script JSON-encodes forward slashes as \/ so assertStringContainsString with a plain URL always failed. Split the assertion to check domain and filename separately to avoid the encoding mismatch.
do_action('admin_init') fires callbacks that attempt to
send HTTP headers, which fails in the test environment.
Call csme_register_settings() directly to test registration
without side effects from other admin_init hooks.
The previous assertion only matched checked='checked' but WordPress checked() can emit different attribute formats. Use a regex to match any checked attribute form.
The npm registry lists alexcorvi/heic2any as the canonical repository. Updated all references from the incorrect nicktomlin fork.
Without these filters HEIC files bypass client-side processing and are sent to the server, which rejects them. The client_side_supported_mime_types filter tells Gutenberg to route HEIC client-side, and image_editor_output_format maps HEIC to JPEG for transcoding.
The previous approach wrapped settings.mediaUpload in the block-editor store, but Gutenberg's provider overwrites it on every React re-render, losing the wrapper. Wrapping the upload-media store's addItems action is stable because Redux action creators are not recreated by React lifecycle.
Tests cover the client_side_supported_mime_types and image_editor_output_format filters, verifying HEIC types are added when enabled and omitted when disabled.
The previous approach converted HEIC to JPEG for everything, losing the original file. Now the JPEG is used for upload and subsize generation, then the original HEIC is sideloaded back to the attachment via the existing sideload endpoint so the raw file is preserved on the server.
The sideload endpoint stored the HEIC as original_image metadata but left the JPEG as the main file. On attachment deletion the orphaned JPEG was not cleaned up. The new csme/v1/replace-original endpoint properly replaces the main file with the HEIC and deletes the JPEG from disk.
Replaces the custom csme/v1/replace-original endpoint with the sideload endpoint's new replace_file parameter. This keeps the file replacement logic in Gutenberg's controller rather than duplicating it in the plugin.
HEIC support was removed from WordPress core's wasm-vips build due to LGPL/GPL license incompatibility. This adds it back as a plugin feature by converting HEIC→JPEG client-side using a library loaded dynamically from CDN at runtime (not bundled), sidestepping the license conflict.
Approach
mediaUploadsetting to intercept HEIC files, convert to JPEG via heic2any, then pass converted files to the standard upload pipeline. No server-side HEIC support needed.Changes
includes/heic-support.php— HEIC/HEIF MIME type registration,wp_check_filetype_and_extfilter, script enqueuing withwp_localize_scriptfor CDN URLjs/heic-support.js— Wrapscore/block-editorstore'smediaUploadviawp.data.subscribe, lazy-loads heic2any, converts HEIC blobs to JPEGFileobjects; descriptive error messages on conversion failureincludes/settings.php—csme_heic_enabledsetting and checkbox field (enabled by default)tests/Test_Heic_Support.php— 14 unit tests covering MIME registration, filetype detection, script enqueuing, setting behavior, and end-to-end CDN URL filter validation via localized script datareadme.txt— Documentation, FAQ, and license notesREADME.md— GitHub project README covering both features (COEP/COOP and HEIC), installation, configuration, requirements, and license notesclient-side-media-experiments.php— Plugin description updated to mention HEIC support; version bumped to 0.2.0Original prompt
🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.
Summary by CodeRabbit
New Features
Documentation