Fix: feature image virtual media - #2103
Conversation
checkIfFeatureImage() hid the tab from the sidebar Featured image panel and the classic editor's frame. The panel needs nothing new -- its pick goes through editPost(), so the existing repoint handles it. The classic editor calls featuredImage.set() before we can repoint, and get-post-thumbnail-html casts the job docname with (int), silently attaching an unrelated image -- so park the pick and release the real ID once create-media-entry answers. Covered by 9 unit tests.
There was a problem hiding this comment.
Pull request overview
Adds reliable GoDAM featured-image handling across WordPress editors.
Changes:
- Replaces virtual IDs in block-editor post entities.
- Defers classic-editor updates until attachment creation.
- Adds classic-editor unit tests.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
godam-media-frame-shared.js |
Integrates featured-image ID resolution. |
classic-featured-image.js |
Implements classic-editor deferral. |
classic-featured-image.test.js |
Tests deferral and resolution behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| export function resolveClassicFeaturedImage( virtualMediaId, realId ) { | ||
| if ( null === deferredVirtualId || String( deferredVirtualId ) !== String( virtualMediaId ) ) { | ||
| return; |
🔍 WordPress Plugin Check Report
📊 Report
|
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
0 |
missing_composer_json_file | The "/vendor" directory using composer exists, but "composer.json" file is missing. |
📁 readme.txt (2 warnings)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
0 |
mismatched_plugin_name | Plugin name "GoDAM - Organize WordPress Media Library & File Manager with Unlimited Folders for Images, Videos & more" is different from the name declared in plugin header "GoDAM". |
0 |
trademarked_term | The plugin name includes a restricted term. Your chosen plugin name - "GoDAM - Organize WordPress Media Library & File Manager with Unlimited Folders for Images, Videos & more" - contains the restricted term "wordpress" which cannot be used at all in your plugin name. |
📁 assets/build/blocks/godam-gallery-v2/render.php (2 warnings)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
15 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$inner_block_video_ids". |
23 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$inner_block_video_ids". |
📁 assets/build/css/main.css (1 warning)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
0 |
EnqueuedStylesScope | This style is being loaded in all contexts. |
📁 assets/src/libs/analytics.min.js (5 warnings)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
0 |
EnqueuedScriptsScope | This script is being loaded in all frontend contexts. |
0 |
NonBlockingScripts.NoStrategy | This script on http://localhost:8880 (with handle analytics-library) is loaded in the footer. Consider a defer or async script loading strategy instead. |
0 |
NonBlockingScripts.NoStrategy | This script on http://localhost:8880/2026/09/01/hello-world/ (with handle analytics-library) is loaded in the footer. Consider a defer or async script loading strategy instead. |
0 |
NonBlockingScripts.NoStrategy | This script on http://localhost:8880/sample-page/ (with handle analytics-library) is loaded in the footer. Consider a defer or async script loading strategy instead. |
0 |
NonBlockingScripts.NoStrategy | This script on http://localhost:8880/demo-attachment-post/ (with handle analytics-library) is loaded in the footer. Consider a defer or async script loading strategy instead. |
📁 assets/build/js/main.min.js (5 warnings)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
0 |
EnqueuedScriptsScope | This script is being loaded in all frontend contexts. |
0 |
NonBlockingScripts.NoStrategy | This script on http://localhost:8880 (with handle rtgodam-script) is loaded in the footer. Consider a defer or async script loading strategy instead. |
0 |
NonBlockingScripts.NoStrategy | This script on http://localhost:8880/2026/09/01/hello-world/ (with handle rtgodam-script) is loaded in the footer. Consider a defer or async script loading strategy instead. |
0 |
NonBlockingScripts.NoStrategy | This script on http://localhost:8880/sample-page/ (with handle rtgodam-script) is loaded in the footer. Consider a defer or async script loading strategy instead. |
0 |
NonBlockingScripts.NoStrategy | This script on http://localhost:8880/demo-attachment-post/ (with handle rtgodam-script) is loaded in the footer. Consider a defer or async script loading strategy instead. |
🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check
subodhr258
left a comment
There was a problem hiding this comment.
Review notes on the featured-image virtual-media fix. The core-frame wiring, wp.media.frame reset path, and the sync-editPost / async-create ordering all check out. A few things worth a look, correctness first:
| // attributes, so it needs its own swap. The classic editor gets no | ||
| // placeholder at all — its pick was parked, and is released here. | ||
| replaceVirtualIdInFeaturedImage( data.id, attachment.id ); | ||
| resolveClassicFeaturedImage( data.id, attachment.id ); |
There was a problem hiding this comment.
Failed create-media-entry silently drops the classic featured-image pick. resolveClassicFeaturedImage() only runs inside the if ( response.success ) block. If the request fails or returns non-success, the outer catch at the bottom of processGoDAMItem swallows it, the parked id is never released, and deferredVirtualId stays stuck. The meta box keeps its previous value with no error surfaced, so the user believes a featured image was set when nothing changed. Consider releasing the park (or clearing it with a visible fallback) on the failure path too.
| return; | ||
| } | ||
|
|
||
| dispatch( 'core/editor' ).editPost( { featured_media: realId } ); |
There was a problem hiding this comment.
Guard this editPost the way the read above it is guarded. The getEditedPostAttribute read on line 131 sits in a try/catch, but this dispatch( 'core/editor' ).editPost() does not. A throw here unwinds into processGoDAMItem's swallow-all catch, which then skips resolveClassicFeaturedImage() and both the godam-virtual-attachment-created and godam-attachment-browser:changed dispatches for this item. replaceVirtualIdInCoreImageBlocks is self-guarded, so this is the one exposed call in that block. Wrapping it in the same defensive guard keeps a bad dispatch from taking out the events other code depends on.
|
|
||
| featuredImage.set = function( id ) { | ||
| if ( isDeferrableGoDAMId( id ) ) { | ||
| deferredVirtualId = id; |
There was a problem hiding this comment.
Save-before-resolve keeps the old featured image (classic editor). Parking suppresses the original set(), so the meta box _thumbnail_id hidden field still holds the previous value until create-media-entry answers and resolveClassicFeaturedImage() releases the real id. If the user clicks Update/Publish inside that sub-second window, the save persists the stale id and the newly chosen image is lost until they save again. Worth confirming that timing is acceptable, or showing a brief in-flight indicator while a pick is being resolved.
| }; | ||
|
|
||
| describe( 'classic editor featured image deferral', () => { | ||
| afterEach( () => { |
There was a problem hiding this comment.
deferredVirtualId is module state that afterEach never resets. It only deletes window.wp and restores mocks, so a test that parks a pick (e.g. 'parks a GoDAM pick') leaves deferredVirtualId set for the next test. That makes the suite order-dependent and able to pass or fail spuriously when tests are reordered or new ones are inserted before the existing ones. Drain the park in afterEach (call resolveClassicFeaturedImage to clear it, or export a small reset for tests).
| const state = this.state(); | ||
|
|
||
| // Hold back a classic-editor featured image pick until its attachment exists. | ||
| setupClassicFeaturedImage(); |
There was a problem hiding this comment.
Nit (altitude): this installs a global core patch on every GoDAM tab render, not just featured-image contexts. setupClassicFeaturedImage() permanently wraps the core global wp.media.featuredImage.set the first time any GoDAM tab renders, including the Insert-Media and gallery flows where no featured image is ever picked. It is idempotent and transparent for non-deferrable ids, so not a live bug, but it widens the blast radius of a core-global override past the context the fix targets. Consider only wrapping when the frame is actually a featured-image frame.
|
|
||
| const selected = frame.state?.()?.get?.( 'selection' )?.single?.(); | ||
|
|
||
| return !! selected && String( selected.id ) === String( id ); |
There was a problem hiding this comment.
Nit (reuse): the String( a ) === String( b ) id-equality idiom is repeated across isDeferrableGoDAMId, resolveClassicFeaturedImage, replaceVirtualIdInFeaturedImage, and replaceVirtualIdInCoreImageBlocks. A shared isSameId( a, b ) helper in the media-library utility module would keep the virtual-vs-real id comparison in one place if the matching rule ever needs to change (trimming, coercion, etc.).
Fixes: #1977
This pull request introduces robust support for using GoDAM media items as featured images in both the block and classic editors. The main changes ensure that placeholder GoDAM IDs are properly handled and swapped for real WordPress attachment IDs once the media entry is created, preventing issues where invalid IDs could be saved or displayed. The implementation includes new logic for the classic editor, improved handling in the block editor, and comprehensive unit tests.
Featured image handling improvements:
classic-featured-image.jsto intercept and defer setting GoDAM IDs as featured images in the classic editor, only applying the real attachment ID once it exists, and preventing invalid IDs from being saved or displayed.godam-media-frame-shared.jsto invoke the new classic editor logic (setupClassicFeaturedImageandresolveClassicFeaturedImage) during GoDAM tab activation and after attachment creation. [1] [2] [3]Block editor and post entity integration:
replaceVirtualIdInFeaturedImage()to swap the GoDAM placeholder ID for the real attachment ID on the post entity, ensuring the correct image is displayed and saved.Testing and reliability:
classic-featured-image.test.jswith comprehensive tests for the new classic editor featured image deferral and resolution logic, covering edge cases and ensuring correct integration.Codebase simplification:
checkIfFeatureImage()logic and related conditional checks, as the new approach directly handles both block and classic editor contexts. [1] [2]Demo
Screen.Recording.2026-09-01.at.11.19.22.AM.mov