feat: Add Virtual Media Registrar and Migration classes for handling virtual media integration#1784
feat: Add Virtual Media Registrar and Migration classes for handling virtual media integration#1784shreyasikhar wants to merge 7 commits intodevelopfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds support for registering a WordPress site as a “virtual media site” with GoDAM Central when virtual media attachments are created, and introduces a REST endpoint GoDAM Central can call to reconcile/migrate existing virtual attachments.
Changes:
- Add
Virtual_Media_Registrarto register/de-register the site with GoDAM Central based on attachment lifecycle events and_godam_original_id. - Add
Virtual_Media_MigrationREST endpoint to return callback/site details and a deduplicated list of existing virtual media job IDs. - Wire both components into the plugin bootstrap (
class-plugin.php).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| inc/classes/rest-api/class-virtual-media-migration.php | New REST endpoint for Central-triggered migration (job ID discovery + callback URL response). |
| inc/classes/class-virtual-media-registrar.php | New singleton that hooks attachment/meta lifecycle to add/remove virtual media site registration in Central. |
| inc/classes/class-plugin.php | Bootstraps the new registrar and REST controller. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
🔍 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 (34 warnings)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
212 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$gallery_mode". |
213 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$layout". |
214 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$view_ratio". |
215 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$allowed_ratios". |
216 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$view_ratio". |
217 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$item_width". |
218 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$show_title". |
219 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$enable_more_items". |
220 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$more_items_behavior". |
223 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$more_items_behavior". |
227 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$more_items_behavior". |
230 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$infinite_scroll". |
231 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$show_load_more_button". |
232 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$ratio_class". |
233 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$block_gap_raw". |
234 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$rest_query_args". |
235 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$total_query_items". |
238 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$block_gap". |
240 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$block_gap". |
243 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$inline_styles". |
249 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$wrapper_attributes". |
260 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$items". |
263 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$rest_query_args". |
281 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$query". |
282 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$total_query_items". |
285 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$video_post". |
286 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$item". |
289 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$items". |
296 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$inner_block". |
301 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$video_id". |
302 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$item". |
305 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$items". |
332 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$item". |
381 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$item". |
📁 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 (6 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/04/15/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. |
0 |
NonBlockingScripts.NoStrategy | This script on http://localhost:8880/?godam-video=demo-godam-video-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 (6 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/04/15/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. |
0 |
NonBlockingScripts.NoStrategy | This script on http://localhost:8880/?godam-video=demo-godam-video-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.
Reviewed the code, added some comments
| protected function setup_hooks() { | ||
| add_action( 'added_post_meta', array( $this, 'maybe_register_from_meta_change' ), 10, 3 ); | ||
| add_action( 'updated_post_meta', array( $this, 'maybe_register_from_meta_change' ), 10, 3 ); | ||
| add_action( 'add_attachment', array( $this, 'maybe_register_from_attachment' ), 22, 1 ); |
There was a problem hiding this comment.
WordPress fires the add_attachment hook before _godam_original_id is set, so register_site_for_attachment_if_needed will always early return with true here. I think we can remove this hook for maybe_register_from_attachment.
There was a problem hiding this comment.
We are setting the meta here https://github.com/rtCamp/godam/blob/main/inc/classes/rest-api/class-media-library.php#L1581-L1586 on the same add_attachment hook with priority 1.
| 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query | ||
| 'relation' => 'AND', | ||
| array( | ||
| 'key' => '_godam_original_id', |
There was a problem hiding this comment.
The hardcoded key can be replaced with this:
'key' => \RTGODAM\Inc\Virtual_Media_Registrar::META_ORIGINAL_ID,
| 'compare' => 'EXISTS', | ||
| ), | ||
| array( | ||
| 'key' => '_godam_original_id', |
There was a problem hiding this comment.
The hardcoded key can be replaced with this:
'key' => \RTGODAM\Inc\Virtual_Media_Registrar::META_ORIGINAL_ID,
| } | ||
|
|
||
| foreach ( $query->posts as $attachment_id ) { | ||
| $job_id = get_post_meta( $attachment_id, '_godam_original_id', true ); |
There was a problem hiding this comment.
Above we are getting all the IDs and then we're calling get_post_meta() for every single ID, O(N+1).
You can get the Job ID as well for this, in one single query, something like this:
global $wpdb;
$job_ids = $wpdb->get_col(
$wpdb->prepare(
"SELECT DISTINCT meta_value
FROM {$wpdb->postmeta} pm
INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
WHERE pm.meta_key = %s
AND pm.meta_value != ''
AND p.post_type = 'attachment'
AND p.post_status = 'inherit'",
'_godam_original_id'
)
);There was a problem hiding this comment.
This works, but it won't be cached.
Do you want me to use transients to avoid query hitting the DB directly.
$job_ids = get_transient( 'rtgodam_virtual_media_job_ids' );
if ( false === $job_ids ) {
global $wpdb;
$job_ids = $wpdb->get_col( $wpdb->prepare(
"SELECT DISTINCT meta_value FROM {$wpdb->postmeta} pm
INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
WHERE pm.meta_key = %s AND pm.meta_value != ''
AND p.post_type = 'attachment'",
'_godam_original_id'
) );
set_transient( 'rtgodam_virtual_media_job_ids', $job_ids, HOUR_IN_SECONDS );
}There was a problem hiding this comment.
Implemented the above
Virtual Media Site Registration and Migration
Summary
This PR introduces two classes that allow a WordPress site to integrate as a virtual media site with GoDAM Central. When a virtual media attachment (identified by
_godam_original_idmeta) is added to the Media Library, this site is automatically registered with GoDAM Central so it receives transcoder callbacks for that job. A companion REST API endpoint allows GoDAM Central to trigger a one-time migration that reconciles all existing virtual attachments.Changes
inc/classes/class-virtual-media-registrar.php(new)Singleton class that hooks into the WordPress attachment lifecycle to keep site registration with GoDAM Central in sync.
Registration (
add_virtual_media_site)added_post_meta/updated_post_meta(priority 10) when_godam_original_idis written, and onadd_attachment(priority 21) as a fallback — priority 22 ensures it runs after the transcoding is complete for the new attachment._godam_virtual_site_registeredattachment meta.rtgodam-api-keyoption andRTGODAM_API_BASEconstant; returnsWP_Errorearly if either is missing.godam_core.api.transcoder_job.add_virtual_media_siteon GoDAM Central withjob_name,site_url,callback_url, andapi_key._godam_virtual_site_registered = 1on the attachment on success.De-registration (
remove_virtual_media_site)before_delete_postto detect when a virtual attachment is deleted._godam_virtual_site_registeredis present (skips attachments that were never successfully registered).godam_core.api.transcoder_job.remove_virtual_media_siteon GoDAM Central withjob_name,site_url, andapi_key.error_logwithout blocking the delete operation.inc/classes/rest-api/class-virtual-media-migration.php(new)REST endpoint for GoDAM Central to trigger a migration pass over all existing virtual attachments on this site.
POST /godam/v1/virtual-media-migrationapi_keybody param validated withhash_equalsagainst the storedrtgodam-api-keyoption.site_url,callback_url, and a deduplicated array ofjob_ids— all_godam_original_idvalues from attachments that have a non-empty value for that meta key.get_job_ids_for_migration()queries attachments in batches of 100 (paginatedWP_Query) to avoid memory issues on large sites, then returnsarray_values( array_unique( $job_ids ) ).Meta keys used
_godam_original_id_godam_virtual_site_registeredTesting
_godam_original_idset) — confirmgodam_core.api.transcoder_job.add_virtual_media_siteis called and_godam_virtual_site_registeredis stored.godam_core.api.transcoder_job.remove_virtual_media_siteis called.POST /godam/v1/virtual-media-migrationwith a validapi_key— confirm the response includessite_url,callback_url, and a correct deduplicatedjob_idsarray.api_key— confirm a403is returned.Issue
#958 (comment)
#958 (comment)
Slack thread for reference: https://rtcamp.slack.com/archives/C25QZJ50B/p1776062860628749?thread_ts=1776051004.034789&cid=C25QZJ50B