Skip to content

Add HEIC upload support via client-side conversion - #12

Merged
adamsilverstein merged 26 commits into
mainfrom
copilot/investigate-heic-upload-support
Mar 17, 2026
Merged

adamsilverstein merged 26 commits into
mainfrom
copilot/investigate-heic-upload-support

Conversation

Copilot AI commented Mar 11, 2026 •

Copy link
Copy Markdown
Contributor

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

  • Client-side conversion: Wraps the block editor's mediaUpload setting to intercept HEIC files, convert to JPEG via heic2any, then pass converted files to the standard upload pipeline. No server-side HEIC support needed.
  • Dynamic CDN loading: heic2any (which uses libheif, LGPL-3.0) is loaded from jsdelivr only when a HEIC file is actually detected — not bundled with the plugin, keeping it a separate work from the GPL-licensed plugin.
  • Enabled by default: New toggle under Settings → Media to disable if needed. The CDN URL is filterable for self-hosting:
add_filter( 'csme_heic_library_url', function() {
    return 'https://example.com/heic2any.min.js';
} );

Changes

  • includes/heic-support.php — HEIC/HEIF MIME type registration, wp_check_filetype_and_ext filter, script enqueuing with wp_localize_script for CDN URL
  • js/heic-support.js — Wraps core/block-editor store's mediaUpload via wp.data.subscribe, lazy-loads heic2any, converts HEIC blobs to JPEG File objects; descriptive error messages on conversion failure
  • includes/settings.php — csme_heic_enabled setting 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 data
  • readme.txt — Documentation, FAQ, and license notes
  • README.md — GitHub project README covering both features (COEP/COOP and HEIC), installation, configuration, requirements, and license notes
  • client-side-media-experiments.php — Plugin description updated to mention HEIC support; version bumped to 0.2.0
Original prompt

This section details on the original issue you should resolve

<issue_title>Investigate adding HEIC upload support and license compatibility</issue_title>
<issue_description>Describe the feature
Investigate adding HEIC upload support to the plugin. HEIC support was removed from the core feature due to patent restrictions which conflicted with the WordPress GPL license (see discussion and related issues). The goal is for this plugin to re-enable HEIC support when installed in a way that is compatible with the licensing requirements and can be optionally loaded.

Key technical considerations include:

  • Dynamically loading a HEIC-supporting library at runtime (from a CDN or similar), or
  • Replacing the core-built wasm-vips with one enabled for HEIC

Additionally, the plugin's license should be reviewed to ensure compatibility with any bundled or remotely loaded HEIC library (e.g., libheif). If necessary, adjust the plugin license, load the HEIC library externally, or explore other approaches to maintain compliance.

Use case
Enabling upload of HEIC image files (such as those from modern iPhones) improves user experience and software parity, while allowing those who need HEIC support to enable it via a plugin despite its exclusion from core due to license reasons.

Additional context

<agent_instructions>ultrathink</agent_instructions>

Comments on the Issue (you are @copilot in this section)


🔒 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

    • Added HEIC/HEIF image upload support with automatic browser-based conversion to JPEG format
    • New HEIC support toggle in Settings > Media (enabled by default)
  • Documentation

    • Updated documentation with HEIC support details and configuration guidance

Copilot AI and others added 2 commits March 11, 2026 20:33
- 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 AI changed the title [WIP] Investigate adding HEIC upload support and license compatibility Add opt-in HEIC upload support via client-side conversion Mar 11, 2026
@adamsilverstein
adamsilverstein marked this pull request as ready for review March 13, 2026 03:47
@adamsilverstein

Copy link
Copy Markdown
Owner

Disabled by default: New toggle under Settings → Media.

@copilot make it enabled by default

@adamsilverstein

Copy link
Copy Markdown
Owner

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Mar 13, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Mar 13, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 38b50b22-c91d-43cc-9643-42035ad70ff9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This 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

Cohort / File(s) Summary
Core HEIC Implementation
client-side-media-experiments.php, includes/heic-support.php, includes/settings.php
Added plugin initialization hook to load HEIC support module; introduced four new functions (csme_is_heic_enabled(), csme_add_heic_mime_types(), csme_heic_check_filetype(), csme_enqueue_heic_scripts()) to manage MIME types, file detection, and script loading; added admin settings UI for HEIC toggle with sanitization and default enabled state (1).
Client-Side Conversion
js/heic-support.js
New JavaScript module that detects HEIC/HEIF uploads, lazily loads heic2any library from configurable CDN URL, converts files to JPEG in the browser, wraps block editor's mediaUpload function, and includes error handling with automatic retries on library load failure.
Testing & Bootstrap
tests/Test_Heic_Support.php, tests/bootstrap.php
Added comprehensive test suite (18 test methods) covering enabled state, MIME type behavior, file type detection, script enqueue on relevant admin pages, CDN URL filtering, and non-HEIC file handling; updated bootstrap to load heic-support and settings modules.
Documentation
readme.txt
Added user-facing guidance on HEIC/HEIF support, default enabled behavior, license rationale (libheif under LGPL v3), self-hosting via csme_heic_library_url filter, and 0.2.0 changelog entry.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PR #6: Both PRs modify tests/bootstrap.php to load plugin includes in the test environment; this PR extends that bootstrap pattern to also include heic-support.php and settings.php alongside the existing cross-origin-isolation.php load.

Poem

🐰 Hop, hop! A bunny rejoices in sight,
For HEIC files now convert, smooth and tight—
From phone to WordPress, a journey so fine,
CDN-loaded magic makes images shine! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding HEIC upload support via client-side conversion, which is the primary objective of this pull request.
Linked Issues check ✅ Passed The PR fully addresses issue #11 requirements: implements dynamic CDN-based HEIC library loading for license compatibility, provides client-side HEIC-to-JPEG conversion, includes an optional toggle (enabled by default per reviewer request), and maintains GPL compliance by avoiding bundled LGPL code.
Out of Scope Changes check ✅ Passed All changes are directly scoped to HEIC support implementation: backend support (mime types, filetype detection, settings), frontend conversion script, comprehensive tests, documentation, and bootstrap integration. No unrelated changes detected.
Docstring Coverage ✅ Passed Docstring coverage is 96.88% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch copilot/investigate-heic-upload-support
📝 Coding Plan
  • Generate coding plan for human review comments

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@adamsilverstein

Copy link
Copy Markdown
Owner

@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>

Copilot AI commented Mar 13, 2026

Copy link
Copy Markdown
Contributor Author

Disabled by default: New toggle under Settings → Media.

@copilot make it enabled by default

Done in 908ec4f — changed the default for csme_heic_enabled from 0 to 1 in heic-support.php, settings.php, and readme.txt. Tests updated accordingly.

Copilot AI changed the title Add opt-in HEIC upload support via client-side conversion Add HEIC upload support via client-side conversion Mar 13, 2026
Copilot AI requested a review from adamsilverstein March 13, 2026 03:50
@adamsilverstein

adamsilverstein commented Mar 13, 2026 •

Copy link
Copy Markdown
Owner

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Mar 13, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Co-authored-by: adamsilverstein <2676022+adamsilverstein@users.noreply.github.com>

Copilot AI commented Mar 13, 2026 •

Copy link
Copy Markdown
Contributor Author

@copilot can we also update the project readme or docs to reflect this new feature?

Added a README.md at the project root in 5228085. It covers both features (COEP/COOP and HEIC), installation, configuration options (disabling HEIC, self-hosting the library, disabling COEP/COOP), requirements, and license notes. Also updated the plugin file's Description header to mention HEIC support.

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:

  • https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/b598aa890815b8df16363271b659d73280129101
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/c216317e96c8b3f5932808f9b0f1f7a14e3bbf55
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/845eb62303d2ca9b289ef216356568ccc075ffd1
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/244d7b04fc4bc2117c15f5abe23eb933b5f02bbf
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/7c8d18b4d90dac9e86b0869a608fa09158e168fa
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/7795ec6fa05663d716a549d0b44e47ffc8b0d4a6
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/1a6aecc9ebe4a9cea4e1047d0e6c496e52314c27
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b36f02317466907a230d3aa1d34467041271ef4a
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/wp-phpunit/wp-phpunit/zipball/15fd216bf6516670d8d07b938675925bfa5c15b0
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/N2cNCy /usr/bin/composer install --quiet (http block)

If you need me to access, download, or install something from one of these locations, you can either:

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟡 Minor

Update plugin version to 0.2.0.

The plugin version in the header and the CSME_VERSION constant (line 36) should be updated to 0.2.0 to reflect the new HEIC support feature added in this release.

📝 Proposed fix for plugin header
- * Version:     0.1.0
+ * Version:     0.2.0

And 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 | 🟡 Minor

Update stable tag to match the new version.

The stable tag is still 0.1.0, but the changelog introduces version 0.2.0 with 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 convertHeicToJpeg fails for one file in a batch, the entire upload is aborted and onError is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 1c4aef6 and 908ec4f.

📒 Files selected for processing (7)
  • client-side-media-experiments.php
  • includes/heic-support.php
  • includes/settings.php
  • js/heic-support.js
  • readme.txt
  • tests/Test_Heic_Support.php
  • tests/bootstrap.php

@adamsilverstein

Copy link
Copy Markdown
Owner

@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 @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.

…or messages, strengthen filter test

Co-authored-by: adamsilverstein <2676022+adamsilverstein@users.noreply.github.com>

Copilot AI commented Mar 15, 2026 •

Copy link
Copy Markdown
Contributor Author

@copilot please address the feedback from Code Rabbit:

Verify each finding against the current code and only fix it if needed.
...

All three items addressed in 5c3cf66:

  1. Version bump — Updated Version: header and CSME_VERSION constant in client-side-media-experiments.php from 0.1.0 to 0.2.0, and Stable tag in readme.txt to 0.2.0.
  2. Better error message — The Promise.all catch handler in js/heic-support.js now wraps the original error with new Error('HEIC to JPEG conversion failed: ' + error.message) for clearer context.
  3. Stronger filter test — test_heic_library_url_filterable now retrieves the localized data via wp_scripts()->get_data() and asserts the filtered URL https://example.com/heic2any.min.js appears in it.

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:

  • https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/b598aa890815b8df16363271b659d73280129101
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/c216317e96c8b3f5932808f9b0f1f7a14e3bbf55
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/845eb62303d2ca9b289ef216356568ccc075ffd1
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/244d7b04fc4bc2117c15f5abe23eb933b5f02bbf
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/7795ec6fa05663d716a549d0b44e47ffc8b0d4a6
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/1a6aecc9ebe4a9cea4e1047d0e6c496e52314c27
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b36f02317466907a230d3aa1d34467041271ef4a
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)
  • https://api.github.com/repos/wp-phpunit/wp-phpunit/zipball/15fd216bf6516670d8d07b938675925bfa5c15b0
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/hz0o8F /usr/bin/composer install --quiet (http block)

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.
@adamsilverstein
adamsilverstein merged commit 165d611 into main Mar 17, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Investigate adding HEIC upload support and license compatibility

2 participants